UI to generate reports

Get devices which are enrolled between two date according to device status and ownership
merge-requests/275/merge
Shamalka Navod 5 years ago committed by Dharmakeerthi Lasantha
parent ca26098da1
commit fdf660014f

@ -6,15 +6,19 @@
"license": "Apache License 2.0", "license": "Apache License 2.0",
"dependencies": { "dependencies": {
"acorn": "^6.2.0", "acorn": "^6.2.0",
"antd": "^3.22.0", "antd": "^3.23.5",
"axios": "^0.18.1", "axios": "^0.18.1",
"bootstrap": "^4.3.1",
"javascript-time-ago": "^2.0.1", "javascript-time-ago": "^2.0.1",
"keymirror": "^0.1.1", "keymirror": "^0.1.1",
"lodash.debounce": "^4.0.8", "lodash.debounce": "^4.0.8",
"moment": "^2.24.0",
"rc-viewer": "0.0.9", "rc-viewer": "0.0.9",
"react-bootstrap": "^1.0.0-beta.12",
"react-highlight-words": "^0.16.0", "react-highlight-words": "^0.16.0",
"react-image-viewer-zoom": "^1.0.36", "react-image-viewer-zoom": "^1.0.36",
"react-infinite-scroller": "^1.2.4", "react-infinite-scroller": "^1.2.4",
"react-moment": "^0.9.2",
"react-router": "^5.0.1", "react-router": "^5.0.1",
"react-router-config": "^5.0.1", "react-router-config": "^5.0.1",
"react-router-dom": "^5.0.1", "react-router-dom": "^5.0.1",

@ -225,7 +225,7 @@ class DeviceTable extends React.Component {
<div> <div>
<Table <Table
columns={columns} columns={columns}
rowKey={record => record.deviceIdentifier} rowKey={record => (record.deviceIdentifier + record.enrolmentInfo.owner + record.enrolmentInfo.ownership)}
dataSource={data} dataSource={data}
pagination={{ pagination={{
...pagination, ...pagination,

@ -0,0 +1,260 @@
/*
* 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} 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;
let apiUrl;
const columns = [
{
title: 'Device',
dataIndex: 'name',
width: 100,
},
{
title: 'Type',
dataIndex: 'type',
key: 'type',
render: type => {
const defaultPlatformIcons = config.defaultPlatformIcons;
let icon = defaultPlatformIcons.default.icon;
let color = defaultPlatformIcons.default.color;
let theme = defaultPlatformIcons.default.theme;
if (defaultPlatformIcons.hasOwnProperty(type)) {
icon = defaultPlatformIcons[type].icon;
color = defaultPlatformIcons[type].color;
theme = defaultPlatformIcons[type].theme;
}
return (
<span style={{fontSize: 20, color: color, textAlign: "center"}}>
<Icon type={icon} theme={theme}/>
</span>
);
}
// todo add filtering options
},
{
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
},
{
title: 'Action',
key: 'action',
render: () => (
<span>
<a><Icon type="edit" /></a>
<Divider type="vertical" />
<a><Text type="danger"><Icon type="delete" /></Text></a>
</span>
),
},
];
const getTimeAgo = (time) => {
const timeAgo = new TimeAgo('en-US');
return timeAgo.format(time);
};
class ReportDeviceTable extends React.Component {
constructor(props) {
super(props);
config = this.props.context;
TimeAgo.addLocale(en);
this.state = {
data: [],
pagination: {},
loading: false,
selectedRows: [],
paramsObj:{}
};
}
rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
this.setState({
selectedRows: selectedRows
})
}
};
componentDidMount() {
this.fetch();
}
//Rerender component when parameters change
componentDidUpdate(prevProps, prevState, snapshot) {
if(prevProps.paramsObject !== this.props.paramsObject){
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;
this.props.paramsObject.offset = 10 * (currentPage -1); //calculate the offset
this.props.paramsObject.limit = 10;
const encodedExtraParams = Object.keys(this.props.paramsObject)
.map(key => key + '=' + this.props.paramsObject[key]).join('&');
if(this.props.paramsObject.from==null && this.props.paramsObject.to==null){
apiUrl = window.location.origin + config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
"/devices?" + encodedExtraParams;
}else{
apiUrl = window.location.origin + config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
"/reports/devices?" + encodedExtraParams;
}
//send request to the invokerss
axios.get(apiUrl).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});
});
};
handleTableChange = (pagination, filters, sorter) => {
const pager = {...this.state.pagination};
pager.current = pagination.current;
this.setState({
pagination: pager,
});
this.fetch({
results: pagination.pageSize,
page: pagination.current,
sortField: sorter.field,
sortOrder: sorter.order,
...filters,
});
};
render() {
const {data, pagination, loading, selectedRows} = this.state;
return (
<div>
<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}
onChange={this.handleTableChange}
rowSelection={this.rowSelection}
scroll={{x: 1000}}
/>
</div>
);
}
}
export default withConfigContext(ReportDeviceTable);

@ -0,0 +1,60 @@
/*
* 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 { DatePicker } from 'antd';
import moment from 'moment';
class DateRangePicker extends React.Component {
constructor(props){
super(props);
}
//Send updated date range to Reports.js when duration change
onChange = (dates, dateStrings) => {
this.props.updateDurationValue(dateStrings[0],dateStrings[1]);
}
render(){
const { RangePicker } = DatePicker;
return(
<RangePicker
ranges={{
'Today': [moment(), moment()],
'Yesterday': [moment()
.subtract(1, 'days'), moment()
.subtract(1, 'days')],
'Last 7 Days': [moment().subtract(6, 'days'), moment()],
'Last 30 Days': [moment().subtract(29, 'days'), moment()],
'This Month': [moment()
.startOf('month'), moment()
.endOf('month')],
'Last Month': [moment().subtract(1, 'month')
.startOf('month'), moment()
.subtract(1, 'month')
.endOf('month')]
}}
format="YYYY-MM-DD"
onChange={this.onChange}
/>
)
}
}
export default DateRangePicker;

@ -0,0 +1,67 @@
/*
* 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 { Select } from 'antd';
class Filter extends React.Component {
constructor(props){
super(props);
const { Option } = Select;
this.state = {
selectedItem:null
}
}
//Send updated filter value to Reports.js
onChange = value => {
this.setState({selectedItem:value},() => {
if(this.props.dropDownName=="Device Status"){
this.props.updateFiltersValue(this.state.selectedItem,"Device Status");
}else{
this.props.updateFiltersValue(this.state.selectedItem, "Device Ownership");
}
});
}
render(){
//Dynamically generate dropdown items from dropDownItems array
let item = this.props.dropDownItems.map((data) =>
<Select.Option
value={data}
key={data}>
{data}
</Select.Option>);
return(
<Select
showSearch
style={{ width: 200 }}
placeholder={this.props.dropDownName}
optionFilterProp="children"
onChange={this.onChange}
filterOption={(input, option) =>
option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}>
{item}
</Select>
)
}
}
export default Filter;

@ -21,11 +21,12 @@ import {
PageHeader, PageHeader,
Typography, Typography,
Breadcrumb, Breadcrumb,
Icon, Icon
Card
} from "antd"; } from "antd";
import {Link} from "react-router-dom"; import {Link} from "react-router-dom";
import DeviceTable from "../../../components/Devices/DevicesTable"; import ReportDeviceTable from "../../../components/Devices/ReportDevicesTable";
import Filter from "../../../components/Reports/Filter";
import DateRangePicker from "../../../components/Reports/DateRangePicker";
const {Paragraph} = Typography; const {Paragraph} = Typography;
@ -35,10 +36,43 @@ class Reports extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.routes = props.routes; this.routes = props.routes;
this.state = {
paramsObject:{}
}
}
//Get modified value from datepicker and set it to paramsObject
updateDurationValue = (modifiedFromDate,modifiedToDate) => {
let tempParamObj = this.state.paramsObject;
tempParamObj.from = modifiedFromDate;
tempParamObj.to = modifiedToDate;
this.setState({paramsObject:tempParamObj});
}
//Get modified value from filters and set it to paramsObject
updateFiltersValue = (modifiedValue,filterType) => {
let tempParamObj = this.state.paramsObject;
if(filterType=="Device Status"){
tempParamObj.status = modifiedValue;
if(modifiedValue=="ALL" && tempParamObj.status){
delete tempParamObj.status;
}
}else{
tempParamObj.ownership = modifiedValue;
if(modifiedValue=="ALL" && tempParamObj.ownership){
delete tempParamObj.ownership;
}
}
this.setState({paramsObject:tempParamObj});
} }
render() { render() {
//Arrays for filters
const statusObj = ['ALL','ACTIVE','INACTIVE','REMOVED'];
const ownershipObj = ['ALL','BYOD','COPE'];
const params = {...this.state.paramsObject};
return ( return (
<div> <div>
<PageHeader style={{paddingTop: 0}}> <PageHeader style={{paddingTop: 0}}>
@ -50,8 +84,41 @@ class Reports extends React.Component {
</Breadcrumb> </Breadcrumb>
<div className="wrap"> <div className="wrap">
<h3>Reports</h3> <h3>Reports</h3>
<Paragraph>Lorem ipsum dolor sit amet, est similique constituto at, quot inermis id mel, an <Paragraph>
illud incorrupte nam.</Paragraph> To generate a report, select a duration and apply filters
</Paragraph>
<div style={{paddingBottom:'5px'}}>
<table>
<tbody>
<tr style={{fontSize:'12px'}}>
<td>Select Duration</td>
<td>Device Status</td>
<td>Device Ownership</td>
</tr>
<tr>
<td>
<DateRangePicker
updateDurationValue={this.updateDurationValue}/>
</td>
<td>
<Filter
updateFiltersValue={this.updateFiltersValue}
dropDownItems={statusObj}
dropDownName={"Device Status"}/>
</td>
<td>
<Filter
updateFiltersValue={this.updateFiltersValue}
dropDownItems={ownershipObj}
dropDownName={"Device Ownership"}/>
</td>
</tr>
</tbody>
</table>
</div>
<div style={{backgroundColor:"#ffffff", borderRadius: 5}}>
<ReportDeviceTable paramsObject={params}/>
</div>
</div> </div>
</PageHeader> </PageHeader>
<div style={{background: '#f0f2f5', padding: 24, minHeight: 720}}> <div style={{background: '#f0f2f5', padding: 24, minHeight: 720}}>

Loading…
Cancel
Save