Merge branch 'Reporting' into 'master'

Implement reports for device enrollment and policy compliance

See merge request entgra/carbon-device-mgt!426
feature/appm-store/pbac
Dharmakeerthi Lasantha 5 years ago
commit ec0c5fde39

@ -148,7 +148,7 @@ class App extends React.Component {
<ConfigContext.Provider value={this.state.config}>
<div>
<Switch>
<Redirect exact from="/entgra" to="/entgra/devices" />
<Redirect exact from="/entgra" to="/entgra/reports" />
{this.props.routes.map(route => (
<RouteWithSubRoutes key={route.path} {...route} />
))}

@ -33,15 +33,13 @@ class DateRangePicker extends React.Component {
render() {
const { RangePicker } = DatePicker;
return (
<div>
<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()],
Yesterday: [moment().subtract(1, 'days'), moment()],
'Last 7 Days': [moment().subtract(7, 'days'), moment()],
'Last 30 Days': [moment().subtract(30, 'days'), moment()],
'This Month': [moment().startOf('month'), moment().endOf('month')],
'Last Month': [
moment()
@ -55,6 +53,7 @@ class DateRangePicker extends React.Component {
format="YYYY-MM-DD"
onChange={this.onChange}
/>
</div>
);
}
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
* Copyright (c) 2020, 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
@ -17,14 +17,16 @@
*/
import React from 'react';
import { PageHeader, Breadcrumb, Icon, Select, Button, Card } from 'antd';
import { PageHeader, Breadcrumb, Icon, Radio, Popover, Button } from 'antd';
import { Link } from 'react-router-dom';
import ReportDeviceTable from '../../../components/Devices/ReportDevicesTable';
import PieChart from '../../../components/Reports/Widgets/PieChart';
import { withConfigContext } from '../../../context/ConfigContext';
const { Option } = Select;
import axios from 'axios';
import DateRangePicker from '../DateRangePicker';
import moment from 'moment';
import { Chart, Geom, Axis, Tooltip, Legend } from 'bizcharts';
import DataSet from '@antv/data-set';
import { handleApiError } from '../../../js/Utils';
class DeviceStatusReport extends React.Component {
routes;
@ -32,37 +34,145 @@ class DeviceStatusReport extends React.Component {
constructor(props) {
super(props);
this.routes = props.routes;
const { reportData } = this.props.location;
this.state = {
selectedTags: ['Enrolled'],
paramsObject: {
from: reportData.duration[0],
to: reportData.duration[1],
from: moment()
.subtract(7, 'days')
.format('YYYY-MM-DD'),
to: moment().format('YYYY-MM-DD'),
},
statsObject: {},
statArray: [
{ item: 'ACTIVE', count: 0 },
{ item: 'INACTIVE', count: 0 },
{ item: 'REMOVED', count: 0 },
],
data: [],
fields: [],
durationMode: 'weekly',
visible: false,
};
}
onClickPieChart = value => {
console.log(value.data.point.item);
const chartValue = value.data.point.item;
let tempParamObj = this.state.paramsObject;
handleDurationModeChange = e => {
const durationMode = e.target.value;
switch (durationMode) {
case 'daily':
this.updateDurationValue(
moment().format('YYYY-MM-DD'),
moment()
.add(1, 'days')
.format('YYYY-MM-DD'),
);
break;
case 'weekly':
this.updateDurationValue(
moment()
.subtract(7, 'days')
.format('YYYY-MM-DD'),
moment().format('YYYY-MM-DD'),
);
break;
case 'monthly':
this.updateDurationValue(
moment()
.subtract(30, 'days')
.format('YYYY-MM-DD'),
moment().format('YYYY-MM-DD'),
);
break;
}
this.setState({ durationMode });
};
handlePopoverVisibleChange = visible => {
this.setState({ visible });
};
tempParamObj.status = chartValue;
componentDidMount() {
this.fetchData();
}
// 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 });
console.log(this.state.paramsObject);
this.fetchData();
};
// Call count APIs and get count for given parameters, then create data object to build pie chart
fetchData = () => {
this.setState({ loading: true });
const { paramsObject } = this.state;
const config = this.props.context;
const encodedExtraParams = Object.keys(paramsObject)
.map(key => key + '=' + paramsObject[key])
.join('&');
axios
.all([
axios.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/reports/count?status=ACTIVE&' +
encodedExtraParams,
'Active',
),
axios.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/reports/count?status=INACTIVE&' +
encodedExtraParams,
'Inactive',
),
axios.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/reports/count?status=REMOVED&' +
encodedExtraParams,
'Removed',
),
])
.then(res => {
let keys = Object.keys(res[0].data.data);
let active = res[0].data.data;
let inactive = res[1].data.data;
let removed = res[2].data.data;
if (Object.keys(active).length != 0) {
active.name = 'Active';
inactive.name = 'Inactive';
removed.name = 'Removed';
}
const finalData = [active, inactive, removed];
this.setState({
data: finalData,
fields: keys,
});
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to get device count.',
);
});
};
render() {
const { reportData } = this.props.location;
const { durationMode } = this.state;
const ds = new DataSet();
const dv = ds.createView().source(this.state.data);
dv.transform({
type: 'fold',
fields: this.state.fields,
key: 'Time',
value: 'Number of Devices',
});
const params = { ...this.state.paramsObject };
return (
<div>
<PageHeader style={{ paddingTop: 0 }}>
@ -75,45 +185,63 @@ class DeviceStatusReport extends React.Component {
<Breadcrumb.Item>Report</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap" style={{ marginBottom: '10px' }}>
<h3>Summary of enrollments</h3>
<div style={{ marginBottom: '10px' }}>
<Select
defaultValue="android"
style={{ width: 120, marginRight: 10 }}
>
<Option value="android">Android</Option>
<Option value="ios">IOS</Option>
<Option value="windows">Windows</Option>
</Select>
<Button
onClick={this.onSubmitReport}
style={{ marginLeft: 10 }}
type="primary"
<h3>Device Status Report</h3>
<Radio.Group
onChange={this.handleDurationModeChange}
defaultValue={'weekly'}
value={durationMode}
style={{ marginBottom: 8, marginRight: 5 }}
>
Generate Report
</Button>
</div>
</div>
<Radio.Button value={'daily'}>Today</Radio.Button>
<Radio.Button value={'weekly'}>Last Week</Radio.Button>
<Radio.Button value={'monthly'}>Last Month</Radio.Button>
</Radio.Group>
<Popover
trigger="hover"
content={
<div>
<Card
bordered={true}
hoverable={true}
<DateRangePicker
updateDurationValue={this.updateDurationValue}
/>
</div>
}
visible={this.state.visible}
onVisibleChange={this.handlePopoverVisibleChange}
>
<Button style={{ marginRight: 10 }}>Custom Date</Button>
</Popover>
<div
style={{
backgroundColor: '#ffffff',
borderRadius: 5,
marginBottom: 10,
height: window.innerHeight * 0.5,
marginTop: 10,
}}
>
<PieChart
onClickPieChart={this.onClickPieChart}
reportData={reportData}
<Chart height={400} data={dv} forceFit>
<Axis name="Time" />
<Axis name="Number of Devices" />
<Legend />
<Tooltip
crosshairs={{
type: 'y',
}}
/>
<Geom
type="interval"
position="Time*Number of Devices"
color={'name'}
adjust={[
{
type: 'dodge',
marginRatio: 1 / 32,
},
]}
/>
</Card>
</Chart>
</div>
<div style={{ backgroundColor: '#ffffff', borderRadius: 5 }}>
<ReportDeviceTable paramsObject={params} />
</div>
</PageHeader>
<div

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
* Copyright (c) 2020, 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
@ -17,12 +17,16 @@
*/
import React from 'react';
import { PageHeader, Breadcrumb, Icon, Card } from 'antd';
import { PageHeader, Breadcrumb, Icon, Radio, Popover, Button } from 'antd';
import { Link } from 'react-router-dom';
import ReportDeviceTable from '../../../components/Devices/ReportDevicesTable';
import PieChart from '../../../components/Reports/Widgets/PieChart';
import { withConfigContext } from '../../../context/ConfigContext';
import axios from 'axios';
import DateRangePicker from '../DateRangePicker';
import moment from 'moment';
import { Chart, Geom, Axis, Tooltip, Legend } from 'bizcharts';
import DataSet from '@antv/data-set';
import { handleApiError } from '../../../js/Utils';
class EnrollmentTypeReport extends React.Component {
routes;
@ -30,32 +34,134 @@ class EnrollmentTypeReport extends React.Component {
constructor(props) {
super(props);
this.routes = props.routes;
const { reportData } = this.props.location;
this.state = {
paramsObject: {
from: reportData.duration[0],
to: reportData.duration[1],
from: moment()
.subtract(7, 'days')
.format('YYYY-MM-DD'),
to: moment().format('YYYY-MM-DD'),
},
data: [],
fields: [],
durationMode: 'weekly',
visible: false,
};
}
componentDidMount() {
this.fetchData();
}
console.log(reportData.duration);
handleDurationModeChange = e => {
const durationMode = e.target.value;
switch (durationMode) {
case 'daily':
this.updateDurationValue(
moment().format('YYYY-MM-DD'),
moment()
.add(1, 'days')
.format('YYYY-MM-DD'),
);
break;
case 'weekly':
this.updateDurationValue(
moment()
.subtract(7, 'days')
.format('YYYY-MM-DD'),
moment().format('YYYY-MM-DD'),
);
break;
case 'monthly':
this.updateDurationValue(
moment()
.subtract(30, 'days')
.format('YYYY-MM-DD'),
moment().format('YYYY-MM-DD'),
);
break;
}
this.setState({ durationMode });
};
onClickPieChart = value => {
const chartValue = value.data.point.item;
handlePopoverVisibleChange = visible => {
this.setState({ visible });
};
// 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 });
this.fetchData();
};
console.log(chartValue);
// Call count APIs and get count for given parameters, then create data object to build pie chart
fetchData = () => {
this.setState({ loading: true });
tempParamObj.ownership = chartValue;
const { paramsObject } = this.state;
const config = this.props.context;
this.setState({ paramsObject: tempParamObj });
const encodedExtraParams = Object.keys(paramsObject)
.map(key => key + '=' + paramsObject[key])
.join('&');
axios
.all([
axios.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/reports/count?ownership=BYOD&' +
encodedExtraParams,
'BYOD',
),
axios.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/reports/count?ownership=COPE&' +
encodedExtraParams,
'COPE',
),
])
.then(res => {
let keys = Object.keys(res[0].data.data);
let byod = res[0].data.data;
let cope = res[1].data.data;
if (Object.keys(byod).length != 0) {
byod.name = 'BYOD';
cope.name = 'COPE';
}
const finalData = [byod, cope];
this.setState({
data: finalData,
fields: keys,
});
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to get device count.',
);
});
};
render() {
const { reportData } = this.props.location;
const { durationMode } = this.state;
const ds = new DataSet();
const dv = ds.createView().source(this.state.data);
dv.transform({
type: 'fold',
fields: this.state.fields,
key: 'Time',
value: 'Number of Devices',
});
const params = { ...this.state.paramsObject };
return (
<div>
<PageHeader style={{ paddingTop: 0 }}>
@ -68,28 +174,62 @@ class EnrollmentTypeReport extends React.Component {
<Breadcrumb.Item>Report</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap" style={{ marginBottom: '10px' }}>
<h3>Summary of enrollments</h3>
</div>
<h3>Device Enrollment Type Report</h3>
<Radio.Group
onChange={this.handleDurationModeChange}
defaultValue={'weekly'}
value={durationMode}
style={{ marginBottom: 8, marginRight: 5 }}
>
<Radio.Button value={'daily'}>Today</Radio.Button>
<Radio.Button value={'weekly'}>Last Week</Radio.Button>
<Radio.Button value={'monthly'}>Last Month</Radio.Button>
</Radio.Group>
<Popover
trigger="hover"
content={
<div>
<Card
bordered={true}
hoverable={true}
<DateRangePicker
updateDurationValue={this.updateDurationValue}
/>
</div>
}
visible={this.state.visible}
onVisibleChange={this.handlePopoverVisibleChange}
>
<Button style={{ marginRight: 10 }}>Custom Date</Button>
</Popover>
<div
style={{
backgroundColor: '#ffffff',
borderRadius: 5,
marginBottom: 10,
height: window.innerHeight * 0.5,
marginTop: 10,
}}
>
<PieChart
onClickPieChart={this.onClickPieChart}
reportData={reportData}
<Chart height={400} data={dv} forceFit>
<Axis name="Time" />
<Axis name="Number of Devices" />
<Legend />
<Tooltip
crosshairs={{
type: 'y',
}}
/>
</Card>
<Geom
type="interval"
position="Time*Number of Devices"
color={'name'}
adjust={[
{
type: 'dodge',
marginRatio: 1 / 32,
},
]}
/>
</Chart>
</div>
<div style={{ backgroundColor: '#ffffff', borderRadius: 5 }}>
<ReportDeviceTable paramsObject={params} />
</div>
</PageHeader>
<div

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
* Copyright (c) 2020, 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
@ -17,12 +17,16 @@
*/
import React from 'react';
import { PageHeader, Breadcrumb, Icon, Card } from 'antd';
import { PageHeader, Breadcrumb, Icon, Radio, Popover, Button } from 'antd';
import { Link, Redirect } from 'react-router-dom';
import ReportDeviceTable from '../../../components/Devices/ReportDevicesTable';
import PieChart from '../../../components/Reports/Widgets/PieChart';
import { Link } from 'react-router-dom';
import { withConfigContext } from '../../../context/ConfigContext';
import axios from 'axios';
import DateRangePicker from '../DateRangePicker';
import moment from 'moment';
import { Chart, Geom, Axis, Tooltip, Legend } from 'bizcharts';
import DataSet from '@antv/data-set';
import { handleApiError } from '../../../js/Utils';
class EnrollmentsVsUnenrollmentsReport extends React.Component {
routes;
@ -30,58 +34,136 @@ class EnrollmentsVsUnenrollmentsReport extends React.Component {
constructor(props) {
super(props);
this.routes = props.routes;
const { reportData } = this.props.location;
this.state = {
paramsObject: {
from: reportData ? reportData.duration[0] : '2019-01-01',
to: reportData ? reportData.duration[1] : '2019-01-01',
from: moment()
.subtract(7, 'days')
.format('YYYY-MM-DD'),
to: moment().format('YYYY-MM-DD'),
},
redirect: false,
data: [],
fields: [],
durationMode: 'weekly',
visible: false,
};
}
this.redirectToHome();
console.log(this.state.paramsObject);
componentDidMount() {
this.fetchData();
}
redirectToHome = () => {
return <Redirect to="/entgra" />;
handleDurationModeChange = e => {
const durationMode = e.target.value;
switch (durationMode) {
case 'daily':
this.updateDurationValue(
moment().format('YYYY-MM-DD'),
moment()
.add(1, 'days')
.format('YYYY-MM-DD'),
);
break;
case 'weekly':
this.updateDurationValue(
moment()
.subtract(7, 'days')
.format('YYYY-MM-DD'),
moment().format('YYYY-MM-DD'),
);
break;
case 'monthly':
this.updateDurationValue(
moment()
.subtract(30, 'days')
.format('YYYY-MM-DD'),
moment().format('YYYY-MM-DD'),
);
break;
}
this.setState({ durationMode });
};
onClickPieChart = value => {
const chartValue = value.data.point.item;
let tempParamObj = this.state.paramsObject;
handlePopoverVisibleChange = visible => {
this.setState({ visible });
};
console.log(chartValue);
// 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 });
this.fetchData();
};
if (chartValue === 'Enrollments') {
tempParamObj.status = 'ACTIVE&status=INACTIVE';
} else {
tempParamObj.status = 'REMOVED';
// Call count APIs and get count for given parameters, then create data object to build pie chart
fetchData = () => {
this.setState({ loading: true });
const { paramsObject } = this.state;
const config = this.props.context;
const encodedExtraParams = Object.keys(paramsObject)
.map(key => key + '=' + paramsObject[key])
.join('&');
axios
.all([
axios.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/reports/count?status=ACTIVE&status=INACTIVE&' +
encodedExtraParams,
'Enrollments',
),
axios.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/reports/count?status=REMOVED&' +
encodedExtraParams,
'Unenrollments',
),
])
.then(res => {
let keys = Object.keys(res[0].data.data);
let enrollments = res[0].data.data;
let unenrollments = res[1].data.data;
if (Object.keys(enrollments).length != 0) {
enrollments.name = 'Enrollments';
unenrollments.name = 'Unenrollments';
}
this.setState({ paramsObject: tempParamObj });
const finalData = [enrollments, unenrollments];
this.setState({
data: finalData,
fields: keys,
});
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to get device count.',
);
});
};
render() {
const { reportData } = this.props.location;
console.log('======');
console.log(reportData);
console.log('======');
const { durationMode } = this.state;
let reportDataClone = {
params: ['ACTIVE'],
duration: ['2020-01-01', '2020-01-01'],
};
const ds = new DataSet();
const dv = ds.createView().source(this.state.data);
dv.transform({
type: 'fold',
fields: this.state.fields,
key: 'Time',
value: 'Number of Devices',
});
const params = { ...this.state.paramsObject };
return (
<div>
<div>
{!reportData ? (
<Redirect to="/entgra/reports" />
) : (
<PageHeader style={{ paddingTop: 0 }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
@ -92,32 +174,68 @@ class EnrollmentsVsUnenrollmentsReport extends React.Component {
<Breadcrumb.Item>Report</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap" style={{ marginBottom: '10px' }}>
<h3>Summary of enrollments</h3>
</div>
<h3>Enrollments vs Unenrollments Report</h3>
<Radio.Group
onChange={this.handleDurationModeChange}
defaultValue={'weekly'}
value={durationMode}
style={{ marginBottom: 8, marginRight: 5 }}
>
<Radio.Button value={'daily'}>Today</Radio.Button>
<Radio.Button value={'weekly'}>Last Week</Radio.Button>
<Radio.Button value={'monthly'}>Last Month</Radio.Button>
</Radio.Group>
<Popover
trigger="hover"
content={
<div>
<Card
bordered={true}
hoverable={true}
<DateRangePicker
updateDurationValue={this.updateDurationValue}
/>
</div>
}
visible={this.state.visible}
onVisibleChange={this.handlePopoverVisibleChange}
>
<Button style={{ marginRight: 10 }}>Custom Date</Button>
</Popover>
<div
style={{
backgroundColor: '#ffffff',
borderRadius: 5,
marginBottom: 10,
height: window.innerHeight * 0.5,
marginTop: 10,
}}
>
<PieChart
onClickPieChart={this.onClickPieChart}
reportData={reportData ? reportData : reportDataClone}
<Chart height={400} data={dv} forceFit>
<Axis name="Time" />
<Axis name="Number of Devices" />
<Legend />
<Tooltip
crosshairs={{
type: 'y',
}}
/>
</Card>
<Geom
type="interval"
position="Time*Number of Devices"
color={'name'}
adjust={[
{
type: 'dodge',
marginRatio: 1 / 32,
},
]}
/>
</Chart>
</div>
<div style={{ backgroundColor: '#ffffff', borderRadius: 5 }}>
<ReportDeviceTable paramsObject={params} />
</div>
</PageHeader>
)}
</div>
<div
style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}
></div>
</div>
);
}

@ -0,0 +1,189 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { PageHeader, Breadcrumb, Icon, Radio, Popover, Button } from 'antd';
import { Link } from 'react-router-dom';
import { withConfigContext } from '../../../context/ConfigContext';
import PolicyDevicesTable from '../Widgets/PolicyDevicesTable';
import moment from 'moment';
import DateRangePicker from '../DateRangePicker';
import SelectPolicyDropDown from '../Widgets/SelectPolicyDropDown';
// eslint-disable-next-line no-unused-vars
let config = null;
class PolicyReport extends React.Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
config = this.props.context;
this.state = {
durationMode: 'weekly',
isCompliant: true,
// This object contains parameters which pass into API endpoint
policyReportData: {
from: moment()
.subtract(7, 'days')
.format('YYYY-MM-DD'),
to: moment().format('YYYY-MM-DD'),
},
visible: false,
};
}
handleModeChange = e => {
const isCompliant = e.target.value;
this.setState({ isCompliant });
};
handleDurationModeChange = e => {
const durationMode = e.target.value;
switch (durationMode) {
case 'daily':
this.updateDurationValue(
moment()
.subtract(1, 'days')
.format('YYYY-MM-DD'),
moment().format('YYYY-MM-DD'),
);
break;
case 'weekly':
this.updateDurationValue(
moment()
.subtract(7, 'days')
.format('YYYY-MM-DD'),
moment().format('YYYY-MM-DD'),
);
break;
case 'monthly':
this.updateDurationValue(
moment()
.subtract(30, 'days')
.format('YYYY-MM-DD'),
moment().format('YYYY-MM-DD'),
);
break;
}
this.setState({ durationMode });
};
hidePopover = () => {
this.setState({
visible: false,
});
};
handlePopoverVisibleChange = visible => {
this.setState({ visible });
};
getPolicyId = policyId => {
let tempParamObj = this.state.policyReportData;
if (policyId === 'all') {
delete tempParamObj.policy;
} else {
tempParamObj.policy = policyId;
}
this.setState({ policyReportData: tempParamObj });
};
// Get modified value from datepicker and set it to paramsObject
updateDurationValue = (modifiedFromDate, modifiedToDate) => {
let tempParamObj = this.state.policyReportData;
tempParamObj.from = modifiedFromDate;
tempParamObj.to = modifiedToDate;
this.setState({ policyReportData: tempParamObj });
};
render() {
const { isCompliant, durationMode } = this.state;
const policyData = { ...this.state.policyReportData };
return (
<div>
<PageHeader style={{ paddingTop: 0 }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/entgra">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Report</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap" style={{ marginBottom: '10px' }}>
<h3>Policy Report</h3>
<Radio.Group
onChange={this.handleModeChange}
defaultValue={true}
value={isCompliant}
style={{ marginBottom: 8, marginRight: 10 }}
>
<Radio.Button value={true}>Policy Compliant Devices</Radio.Button>
<Radio.Button value={false}>
Policy Non-Compliant Devices
</Radio.Button>
</Radio.Group>
<Radio.Group
onChange={this.handleDurationModeChange}
defaultValue={'weekly'}
value={durationMode}
style={{ marginBottom: 8, marginRight: 5 }}
>
<Radio.Button value={'daily'}>Today</Radio.Button>
<Radio.Button value={'weekly'}>Last Week</Radio.Button>
<Radio.Button value={'monthly'}>Last Month</Radio.Button>
</Radio.Group>
<Popover
trigger="hover"
content={
<div>
<DateRangePicker
updateDurationValue={this.updateDurationValue}
/>
</div>
}
visible={this.state.visible}
onVisibleChange={this.handlePopoverVisibleChange}
>
<Button style={{ marginRight: 10 }}>Custom Date</Button>
</Popover>
<SelectPolicyDropDown getPolicyId={this.getPolicyId} />
<div style={{ backgroundColor: '#ffffff', borderRadius: 5 }}>
<PolicyDevicesTable
policyReportData={policyData}
isCompliant={isCompliant}
/>
</div>
</div>
</PageHeader>
<div
style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}
></div>
</div>
);
}
}
export default withConfigContext(PolicyReport);

@ -33,8 +33,6 @@ class CountWidget extends React.Component {
<b>{data.item}</b>
</h2>
<h1>{data.count}</h1>
{/* <p>{data.duration}</p>*/}
{/* <ReportFilterModal/>*/}
</div>
</Card>
</Col>

@ -0,0 +1,128 @@
/*
* Copyright (c) 2020, 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 { Button, message, Modal, notification, List, Typography } from 'antd';
import axios from 'axios';
import { withConfigContext } from '../../../context/ConfigContext';
class FeatureListModal extends React.Component {
constructor(props) {
super(props);
this.state = {
modalVisible: false,
name: '',
description: '',
features: [],
};
}
fetchViolatedFeatures = id => {
const config = this.props.context;
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/devices/' +
id +
'/features',
)
.then(res => {
if (res.status === 200) {
this.setState({
features: JSON.parse(res.data.data),
});
}
})
.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 non compliance feature list.',
});
}
});
};
openModal = () => {
this.fetchViolatedFeatures(this.props.id);
this.setState({
modalVisible: true,
});
};
handleOk = e => {
this.setState({
modalVisible: false,
});
};
render() {
const { features, modalVisible } = this.state;
let featureList = features.map(data => (
<List.Item key={data.featureCodes}>
<Typography.Text key={data.featureCodes} mark>
{data.featureCode}
</Typography.Text>
</List.Item>
));
return (
<div>
<div>
<Button
type="primary"
size={'small'}
icon="book"
onClick={this.openModal}
>
Violated Features
</Button>
</div>
<div>
<Modal
title="VIOLATED FEATURES"
width="40%"
visible={modalVisible}
onOk={this.handleOk}
footer={[
<Button key="submit" type="primary" onClick={this.handleOk}>
OK
</Button>,
]}
>
<List size="large" bordered>
{featureList}
</List>
</Modal>
</div>
</div>
);
}
}
export default withConfigContext(FeatureListModal);

@ -39,8 +39,6 @@ class PieChart extends React.Component {
duration: reportData.duration,
};
console.log(urlSet);
if (reportData.params[0] === 'Enrollments') {
this.getEnrollmentsVsUnenrollmentsCount(params, urlSet);
} else if (reportData.params[0] === 'BYOD') {
@ -50,10 +48,6 @@ class PieChart extends React.Component {
}
}
clicked = () => {
console.log('Clicked...!!');
};
onChartChange = data => {
this.props.onClickPieChart(data);
};
@ -66,8 +60,6 @@ class PieChart extends React.Component {
let { statArray } = this.state;
console.log(urlSet);
const urlArray = [];
urlSet.paramsList.map(data => {
@ -76,7 +68,6 @@ class PieChart extends React.Component {
from: urlSet.duration[0],
to: urlSet.duration[1],
};
// console.log(paramsObj)
const encodedExtraParams = Object.keys(paramsObj)
.map(key => key + '=' + paramsObj[key])
.join('&');
@ -90,8 +81,6 @@ class PieChart extends React.Component {
urlArray.push(axios.get(apiUrl, data));
});
console.log(urlArray);
axios
.all(urlArray)
.then(res => {
@ -128,8 +117,6 @@ class PieChart extends React.Component {
let { statArray } = this.state;
console.log(urlSet);
const urlArray = [];
urlSet.paramsList.map(data => {
@ -161,8 +148,6 @@ class PieChart extends React.Component {
urlArray.push(axios.get(apiUrl, data));
});
console.log(urlArray);
axios
.all(urlArray)
.then(res => {
@ -199,8 +184,6 @@ class PieChart extends React.Component {
let { statArray } = this.state;
console.log(urlSet);
const urlArray = [];
urlSet.paramsList.map(data => {
@ -222,8 +205,6 @@ class PieChart extends React.Component {
urlArray.push(axios.get(apiUrl, data));
});
console.log(urlArray);
axios
.all(urlArray)
.then(res => {

@ -0,0 +1,352 @@
/*
* Copyright (c) 2020, 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 { Button, Icon, Table, Tooltip } from 'antd';
import TimeAgo from 'javascript-time-ago';
import moment from 'moment';
// Load locale-specific relative date/time formatting rules.
import en from 'javascript-time-ago/locale/en';
import { withConfigContext } from '../../../context/ConfigContext';
import FeatureListModal from './FeatureListModal';
import { handleApiError } from '../../../js/Utils';
let config = null;
// Table columns for non compliant devices
const columnsNonCompliant = [
{
title: 'Device',
dataIndex: 'deviceName',
width: 100,
sorter: (a, b) => a.deviceName.localeCompare(b.deviceName),
},
{
title: 'Type',
dataIndex: 'deviceType',
key: 'type',
// eslint-disable-next-line react/display-name
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>
);
},
},
{
title: 'Owner',
dataIndex: 'owner',
key: 'owner',
},
{
title: 'Policy',
dataIndex: 'policyName',
key: 'policy',
sorter: (a, b) => a.policyName.localeCompare(b.policyName),
},
{
title: 'Last Failed Time',
dataIndex: 'lastFailedTime',
key: 'lastFailedTime',
render: data => {
if (data) {
return (
<Tooltip title={new Date(data).toString()}>
{moment(data).fromNow()}
</Tooltip>
);
}
return 'Not available';
},
},
{
title: 'Last Success Time',
dataIndex: 'lastSucceededTime',
key: 'lastSucceededTime',
render: data => {
if (data) {
return (
<Tooltip title={new Date(data).toString()}>
{moment(data).fromNow()}
</Tooltip>
);
}
return 'Not available';
},
},
{
title: 'Attempts',
dataIndex: 'attempts',
key: 'attempts',
},
{
title: 'Violated Features',
dataIndex: 'id',
key: 'violated_features',
// eslint-disable-next-line react/display-name
render: id => <FeatureListModal id={id} />,
},
{
title: 'Device Details',
dataIndex: 'id',
key: 'device_details',
// eslint-disable-next-line react/display-name
render: id => (
<Button type="primary" size={'small'} icon="book">
Device Details
</Button>
),
},
];
// Table columns for compliant devices
const columnsCompliant = [
{
title: 'Device',
dataIndex: 'deviceName',
width: 100,
sorter: (a, b) => a.deviceName.localeCompare(b.deviceName),
},
{
title: 'Type',
dataIndex: 'deviceType',
key: 'type',
// eslint-disable-next-line react/display-name
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>
);
},
},
{
title: 'Owner',
dataIndex: 'owner',
key: 'owner',
},
{
title: 'Policy',
dataIndex: 'policyName',
key: 'policy',
sorter: (a, b) => a.policyName.localeCompare(b.policyName),
},
{
title: 'Last Success Time',
dataIndex: 'lastSucceededTime',
key: 'lastSucceededTime',
render: data => {
if (data) {
return (
<Tooltip title={new Date(data).toString()}>
{moment(data).fromNow()}
</Tooltip>
);
}
return 'Not available';
},
},
{
title: 'Attempts',
dataIndex: 'attempts',
key: 'attempts',
},
{
title: 'Device Details',
dataIndex: 'id',
key: 'device_details',
// eslint-disable-next-line react/display-name
render: id => (
<Button type="primary" size={'small'} icon="book">
Device Details
</Button>
),
},
];
class PolicyDevicesTable 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.fetchData();
}
// Rerender component when parameters change
componentDidUpdate(prevProps, prevState, snapshot) {
if (
prevProps.isCompliant !== this.props.isCompliant ||
prevProps.policyReportData !== this.props.policyReportData
) {
this.fetchData();
}
}
// fetch data from api
fetchData = (params = {}) => {
// const policyReportData = this.props;
this.setState({ loading: true });
// get current page
const currentPage = params.hasOwnProperty('page') ? params.page : 1;
let extraParams;
if (this.props.policyReportData.policy) {
extraParams = {
policy: this.props.policyReportData.policy,
from: this.props.policyReportData.from,
to: this.props.policyReportData.to,
offset: 10 * (currentPage - 1), // calculate the offset
limit: 10,
};
} else {
extraParams = {
from: this.props.policyReportData.from,
to: this.props.policyReportData.to,
offset: 10 * (currentPage - 1), // calculate the offset
limit: 10,
};
}
const encodedExtraParams = Object.keys(extraParams)
.map(key => key + '=' + extraParams[key])
.join('&');
let apiUrl;
if (this.props.isCompliant) {
apiUrl =
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/devices/compliance/true?' +
encodedExtraParams;
} else {
apiUrl =
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/devices/compliance/false?' +
encodedExtraParams;
}
// send request to the invoker
axios
.get(apiUrl)
.then(res => {
if (res.status === 200) {
const pagination = { ...this.state.pagination };
this.setState({
loading: false,
data: res.data.data,
pagination,
});
}
})
.catch(error => {
handleApiError(error, 'Error occurred while trying to load devices.');
this.setState({ loading: false });
});
};
handleTableChange = (pagination, filters, sorter) => {
const pager = { ...this.state.pagination };
pager.current = pagination.current;
this.setState({
pagination: pager,
});
this.fetchData({
results: pagination.pageSize,
page: pagination.current,
sortField: sorter.field,
sortOrder: sorter.order,
...filters,
});
};
render() {
let { data, pagination, loading } = this.state;
const { isCompliant } = this.props;
return (
<div>
<Table
columns={isCompliant ? columnsCompliant : columnsNonCompliant}
rowKey={record => record.id}
dataSource={data.complianceData}
pagination={{
...pagination,
size: 'small',
// position: "top",
total: data.count,
showTotal: (total, range) =>
`showing ${range[0]}-${range[1]} of ${total} devices`,
// showQuickJumper: true
}}
loading={loading}
onChange={this.handleTableChange}
rowSelection={this.rowSelection}
/>
</div>
);
}
}
export default withConfigContext(PolicyDevicesTable);

@ -0,0 +1,111 @@
/*
* Copyright (c) 2020, 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, message, notification } from 'antd';
import { withConfigContext } from '../../../context/ConfigContext';
import axios from 'axios';
const { Option } = Select;
class SelectPolicyDropDown extends React.Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
this.state = {
isOpen: false,
currentPage: 1,
data: [],
pagination: {},
loading: false,
};
}
componentDidMount() {
this.fetchPolicies();
}
// fetch data from api
fetchPolicies = (params = {}) => {
const config = this.props.context;
this.setState({ loading: true });
// send request to the invokerss
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/policies',
)
.then(res => {
if (res.status === 200) {
this.setState({
loading: false,
data: JSON.parse(res.data.data),
});
}
})
.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 policies.',
});
}
this.setState({ loading: false });
});
};
handleChange = value => {
this.props.getPolicyId(value);
};
render() {
let item;
if (this.state.data) {
item = this.state.data.map(data => (
<Select.Option value={data.id} key={data.id}>
{data.profile.profileName}
</Select.Option>
));
}
return (
<Select
defaultValue="all"
style={{ width: 200 }}
onChange={this.handleChange}
>
<Option value="all">All</Option>
{item}
</Select>
);
}
}
export default withConfigContext(SelectPolicyDropDown);

@ -23,21 +23,14 @@ import App from './App';
import Login from './pages/Login';
import Dashboard from './pages/Dashboard/Dashboard';
import './index.css';
import Devices from './pages/Dashboard/Devices/Devices';
import Reports from './pages/Dashboard/Reports/Reports';
import Geo from './pages/Dashboard/Geo/Geo';
import Groups from './pages/Dashboard/Groups/Groups';
import Users from './pages/Dashboard/Users/Users';
import Policies from './pages/Dashboard/Policies/Policies';
import Roles from './pages/Dashboard/Roles/Roles';
import DeviceTypes from './pages/Dashboard/DeviceTypes/DeviceTypes';
import DeviceEnroll from './pages/Dashboard/Devices/DeviceEnroll';
import AddNewPolicy from './pages/Dashboard/Policies/AddNewPolicy';
import Certificates from './pages/Dashboard/Configurations/Certificates/Certificates';
import ReportDurationItemList from './pages/Dashboard/Reports/ReportDurationItemList';
import EnrollmentsVsUnenrollmentsReport from './components/Reports/Templates/EnrollmentsVsUnenrollmentsReport';
import EnrollmentTypeReport from './components/Reports/Templates/EnrollmentTypeReport';
import PolicyReport from './components/Reports/Templates/PolicyReport';
import DeviceStatusReport from './components/Reports/Templates/DeviceStatusReport';
import PolicyReportHome from './pages/Dashboard/Reports/PolicyReportHome';
import ReportDurationItemList from './pages/Dashboard/Reports/ReportDurationItemList';
const routes = [
{
@ -50,16 +43,16 @@ const routes = [
exact: false,
component: Dashboard,
routes: [
{
path: '/entgra/devices',
component: Devices,
exact: true,
},
{
path: '/entgra/devices/enroll',
component: DeviceEnroll,
exact: true,
},
// {
// path: '/entgra/devices',
// component: Devices,
// exact: true,
// },
// {
// path: '/entgra/devices/enroll',
// component: DeviceEnroll,
// exact: true,
// },
{
path: '/entgra/geo',
component: Geo,
@ -70,58 +63,68 @@ const routes = [
component: Reports,
exact: true,
},
{
path: '/entgra/groups',
component: Groups,
exact: true,
},
{
path: '/entgra/users',
component: Users,
exact: true,
},
{
path: '/entgra/policies',
component: Policies,
exact: true,
},
{
path: '/entgra/policy/add',
component: AddNewPolicy,
exact: true,
},
{
path: '/entgra/roles',
component: Roles,
exact: true,
},
{
path: '/entgra/devicetypes',
component: DeviceTypes,
// {
// path: '/entgra/groups',
// component: Groups,
// exact: true,
// },
// {
// path: '/entgra/users',
// component: Users,
// exact: true,
// },
// {
// path: '/entgra/policies',
// component: Policies,
// exact: true,
// },
// {
// path: '/entgra/policy/add',
// component: AddNewPolicy,
// exact: true,
// },
// {
// path: '/entgra/roles',
// component: Roles,
// exact: true,
// },
// {
// path: '/entgra/devicetypes',
// component: DeviceTypes,
// exact: true,
// },
// {
// path: '/entgra/certificates',
// component: Certificates,
// exact: true,
// },
{
path: '/entgra/reports/list',
component: ReportDurationItemList,
exact: true,
},
{
path: '/entgra/certificates',
component: Certificates,
path: '/entgra/reports/enrollments',
component: EnrollmentsVsUnenrollmentsReport,
exact: true,
},
{
path: '/entgra/reportList',
component: ReportDurationItemList,
path: '/entgra/reports/enrollment-type',
component: EnrollmentTypeReport,
exact: true,
},
{
path: '/entgra/enrollmentsvsunenrollments',
component: EnrollmentsVsUnenrollmentsReport,
path: '/entgra/reports/policy',
component: PolicyReportHome,
exact: true,
},
{
path: '/entgra/enrollmenttype',
component: EnrollmentTypeReport,
path: '/entgra/reports/policy/compliance',
component: PolicyReport,
exact: true,
},
{
path: '/entgra/devicestatus',
path: '/entgra/reports/device-status',
component: DeviceStatusReport,
exact: true,
},

@ -0,0 +1,45 @@
/*
* Copyright (c) 2020, 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 { message, notification } from 'antd';
export const handleApiError = (
error,
errorMessage,
isForbiddenMessageSilent = false,
) => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
message.error('You are not logged in');
const redirectUrl = encodeURI(window.location.href);
window.location.href =
window.location.origin + `/entgra/login?redirect=${redirectUrl}`;
// silence 403 forbidden message
} else if (
!(
isForbiddenMessageSilent &&
error.hasOwnProperty('response') &&
error.response.status === 403
)
) {
notification.error({
message: 'There was a problem',
duration: 10,
description: errorMessage,
});
}
};

@ -59,7 +59,7 @@ class Dashboard extends React.Component {
<Layout>
<Header style={{ background: '#fff', padding: 0 }}>
<div className="logo-image">
<Link to="/entgra/devices">
<Link to="/entgra/reports">
<img alt="logo" src={this.logo} />
</Link>
</div>
@ -72,26 +72,26 @@ class Dashboard extends React.Component {
marginRight: 110,
}}
>
<SubMenu
key="devices"
title={
<span>
<Icon type="appstore" />
<span>Devices</span>
</span>
}
>
<Menu.Item key="devices">
<Link to="/entgra/devices">
<span>View</span>
</Link>
</Menu.Item>
<Menu.Item key="deviceEnroll">
<Link to="/entgra/devices/enroll">
<span>Enroll</span>
</Link>
</Menu.Item>
</SubMenu>
{/* <SubMenu*/}
{/* key="devices"*/}
{/* title={*/}
{/* <span>*/}
{/* <Icon type="appstore" />*/}
{/* <span>Devices</span>*/}
{/* </span>*/}
{/* }*/}
{/* >*/}
{/* <Menu.Item key="devices">*/}
{/* <Link to="/entgra/devices">*/}
{/* <span>View</span>*/}
{/* </Link>*/}
{/* </Menu.Item>*/}
{/* <Menu.Item key="deviceEnroll">*/}
{/* <Link to="/entgra/devices/enroll">*/}
{/* <span>Enroll</span>*/}
{/* </Link>*/}
{/* </Menu.Item>*/}
{/* </SubMenu>*/}
<SubMenu
key="geo"
title={
@ -118,65 +118,65 @@ class Dashboard extends React.Component {
<span>Reports</span>
</Link>
</Menu.Item>
<Menu.Item key="groups">
<Link to="/entgra/groups">
<Icon type="deployment-unit" />
<span>Groups</span>
</Link>
</Menu.Item>
<Menu.Item key="users">
<Link to="/entgra/users">
<Icon type="user" />
<span>Users</span>
</Link>
</Menu.Item>
<SubMenu
key="policies"
title={
<span>
<Icon type="audit" />
<span>Policies</span>
</span>
}
>
<Menu.Item key="policiesList">
<Link to="/entgra/policies">
<span>View</span>
</Link>
</Menu.Item>
<Menu.Item key="addPolicy">
<Link to="/entgra/policy/add">
<span>Add New Policy</span>
</Link>
</Menu.Item>
</SubMenu>
<Menu.Item key="roles">
<Link to="/entgra/roles">
<Icon type="book" />
<span>Roles</span>
</Link>
</Menu.Item>
<Menu.Item key="devicetypes">
<Link to="/entgra/devicetypes">
<Icon type="desktop" />
<span>Device Types</span>
</Link>
</Menu.Item>
<SubMenu
key="configurations"
title={
<span>
<Icon type="setting" />
<span>Configurations</span>
</span>
}
>
<Menu.Item key="certificates">
<Link to="/entgra/certificates">
<span>Certificates</span>
</Link>
</Menu.Item>
</SubMenu>
{/* <Menu.Item key="groups">*/}
{/* <Link to="/entgra/groups">*/}
{/* <Icon type="deployment-unit" />*/}
{/* <span>Groups</span>*/}
{/* </Link>*/}
{/* </Menu.Item>*/}
{/* <Menu.Item key="users">*/}
{/* <Link to="/entgra/users">*/}
{/* <Icon type="user" />*/}
{/* <span>Users</span>*/}
{/* </Link>*/}
{/* </Menu.Item>*/}
{/* <SubMenu*/}
{/* key="policies"*/}
{/* title={*/}
{/* <span>*/}
{/* <Icon type="audit" />*/}
{/* <span>Policies</span>*/}
{/* </span>*/}
{/* }*/}
{/* >*/}
{/* <Menu.Item key="policiesList">*/}
{/* <Link to="/entgra/policies">*/}
{/* <span>View</span>*/}
{/* </Link>*/}
{/* </Menu.Item>*/}
{/* <Menu.Item key="addPolicy">*/}
{/* <Link to="/entgra/policy/add">*/}
{/* <span>Add New Policy</span>*/}
{/* </Link>*/}
{/* </Menu.Item>*/}
{/* </SubMenu>*/}
{/* <Menu.Item key="roles">*/}
{/* <Link to="/entgra/roles">*/}
{/* <Icon type="book" />*/}
{/* <span>Roles</span>*/}
{/* </Link>*/}
{/* </Menu.Item>*/}
{/* <Menu.Item key="devicetypes">*/}
{/* <Link to="/entgra/devicetypes">*/}
{/* <Icon type="desktop" />*/}
{/* <span>Device Types</span>*/}
{/* </Link>*/}
{/* </Menu.Item>*/}
{/* <SubMenu*/}
{/* key="configurations"*/}
{/* title={*/}
{/* <span>*/}
{/* <Icon type="setting" />*/}
{/* <span>Configurations</span>*/}
{/* </span>*/}
{/* }*/}
{/* >*/}
{/* <Menu.Item key="certificates">*/}
{/* <Link to="/entgra/certificates">*/}
{/* <span>Certificates</span>*/}
{/* </Link>*/}
{/* </Menu.Item>*/}
{/* </SubMenu>*/}
<Menu.Item key="trigger"></Menu.Item>
<SubMenu
className="profile"
@ -192,9 +192,9 @@ class Dashboard extends React.Component {
</Menu>
</Header>
<Content style={{ marginTop: 10 }}>
<Content>
<Switch>
<Redirect exact from="/entgra" to="/entgra/devices" />
<Redirect exact from="/entgra/devices" to="/entgra/reports" />
{this.state.routes.map(route => (
<RouteWithSubRoutes key={route.path} {...route} />
))}

@ -0,0 +1,156 @@
/*
* Copyright (c) 2020, 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 { Icon, Col, Row, Card } from 'antd';
import { Link } from 'react-router-dom';
class PolicyReportHome extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<div>
<div style={{ borderRadius: 5 }}>
<Row gutter={16}>
<Col span={8}>
<Link
to={{
// Path to respective report page
pathname: '/entgra/reports/policy/compliance',
data: {
name: 'all_policy_compliance_report',
},
}}
>
<Card
bordered={true}
hoverable={true}
style={{ borderRadius: 10, marginBottom: 16 }}
>
<div align="center">
<Icon
type="desktop"
style={{ fontSize: '25px', color: '#08c' }}
/>
<h2>
<b>Policy Compliance Report</b>
</h2>
<p>Policy compliance details of all enrolled devices</p>
</div>
</Card>
</Link>
</Col>
<Col span={8}>
<Link
to={{
// Path to respective report page
pathname: '/entgra/reports/enrollments',
data: {
name: 'enrollments_vs_unenrollments_report',
},
}}
>
<Card
bordered={true}
hoverable={true}
style={{ borderRadius: 10, marginBottom: 16 }}
>
<div align="center">
<Icon
type="desktop"
style={{ fontSize: '25px', color: '#08c' }}
/>
<h2>
<b>Enrollments vs Unenrollments</b>
</h2>
<p>Details on device enrollments vs unenrollments</p>
</div>
</Card>
</Link>
</Col>
<Col span={8}>
<Link
to={{
// Path to respective report page
pathname: '/entgra/reports/device-status',
data: {
name: 'enrollment_status_report',
},
}}
>
<Card
bordered={true}
hoverable={true}
style={{ borderRadius: 10, marginBottom: 16 }}
>
<div align="center">
<Icon
type="desktop"
style={{ fontSize: '25px', color: '#08c' }}
/>
<h2>
<b>Device Status Report</b>
</h2>
<p>Report based on device status</p>
</div>
</Card>
</Link>
</Col>
<Col span={8}>
<Link
to={{
// Path to respective report page
pathname: '/entgra/reports/enrollment-type',
data: {
name: 'enrollemt_type_report',
},
}}
>
<Card
bordered={true}
hoverable={true}
style={{ borderRadius: 10, marginBottom: 16 }}
>
<div align="center">
<Icon
type="desktop"
style={{ fontSize: '25px', color: '#08c' }}
/>
<h2>
<b>Device Type Report</b>
</h2>
<p>Report for all device types</p>
</div>
</Card>
</Link>
</Col>
</Row>
</div>
</div>
);
}
}
export default PolicyReportHome;

@ -17,7 +17,7 @@
*/
import React from 'react';
import { Icon, Col, Row, Card } from 'antd';
import { Icon, Col, Row, Card, PageHeader, Breadcrumb } from 'antd';
import { Link } from 'react-router-dom';
import moment from 'moment';
@ -32,10 +32,11 @@ class ReportDurationItemList extends React.Component {
};
}
// Array for pre defined date durations
durationItemArray = [
{
name: 'Daily Report',
description: 'Enrollments of today',
description: 'Report of today',
duration: [
moment().format('YYYY-MM-DD'),
moment()
@ -45,7 +46,7 @@ class ReportDurationItemList extends React.Component {
},
{
name: 'Weekly Report',
description: 'Enrollments of last 7 days',
description: 'Report of last 7 days',
duration: [
moment()
.subtract(6, 'days')
@ -57,7 +58,7 @@ class ReportDurationItemList extends React.Component {
},
{
name: 'Monthly Report',
description: 'Enrollments of last month',
description: 'Report of last month',
duration: [
moment()
.subtract(29, 'days')
@ -69,7 +70,104 @@ class ReportDurationItemList extends React.Component {
},
];
// Map durationItemArray and additional parameters to antd cards
mapDurationCards = data => {
return this.durationItemArray.map(item => (
<Col key={item.name} span={6}>
<Link
to={{
// Path to respective report page
pathname: '/entgra/reports/policy',
reportData: {
duration: item.duration,
data: data,
},
}}
>
<Card
key={item.name}
bordered={true}
hoverable={true}
style={{ borderRadius: 10, marginBottom: 16 }}
>
<div align="center">
<Icon
type="desktop"
style={{ fontSize: '25px', color: '#08c' }}
/>
<h2>
<b>{item.name}</b>
</h2>
<p>{item.description}</p>
</div>
</Card>
</Link>
</Col>
));
};
itemAllPolicyCompliance = this.durationItemArray.map(data => (
<Col key={data.name} span={6}>
<Link
to={{
// Path to respective report page
pathname: '/entgra/policyreport',
reportData: {
duration: data.duration,
},
}}
>
<Card
key={data.name}
bordered={true}
hoverable={true}
style={{ borderRadius: 10, marginBottom: 16 }}
>
<div align="center">
<Icon type="desktop" style={{ fontSize: '25px', color: '#08c' }} />
<h2>
<b>{data.name}</b>
</h2>
<p>{data.description}</p>
</div>
</Card>
</Link>
</Col>
));
itemPerPolicyCompliance = this.durationItemArray.map(data => (
<Col key={data.name} span={6}>
<Link
to={{
// Path to respective report page
pathname: '/entgra/policyreport',
reportData: {
duration: data.duration,
policyId: 6,
},
}}
>
<Card
key={data.name}
bordered={true}
hoverable={true}
style={{ borderRadius: 10, marginBottom: 16 }}
>
<div align="center">
<Icon type="desktop" style={{ fontSize: '25px', color: '#08c' }} />
<h2>
<b>{data.name}</b>
</h2>
<p>{data.description}</p>
</div>
</Card>
</Link>
</Col>
));
render() {
const { data } = this.props.location;
let itemStatus = this.durationItemArray.map(data => (
<Col key={data.name} span={6}>
<Link
@ -175,18 +273,53 @@ class ReportDurationItemList extends React.Component {
</Link>
</Col>
));
let cardItem = this.itemAllPolicyCompliance;
switch (data.name) {
case 'all_policy_compliance_report':
cardItem = this.itemAllPolicyCompliance;
// cardItem = this.mapDurationCards({});
break;
case 'per_policy_compliance_report':
cardItem = this.itemPerPolicyCompliance;
// cardItem = this.mapDurationCards({
// policyId: 6,
// });
break;
case 'enrollments_vs_unenrollments_report':
cardItem = itemEnrollmentsVsUnenrollments;
break;
case 'enrollment_status_report':
cardItem = itemStatus;
break;
case 'enrollemt_type_report':
cardItem = itemEnrollmentType;
break;
}
return (
<div>
<div>
<PageHeader style={{ paddingTop: 0 }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/entgra">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Reports</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap">
<h3>Reports</h3>
<div style={{ borderRadius: 5 }}>
<Row gutter={16}>{itemStatus}</Row>
<Row gutter={16}>{cardItem}</Row>
</div>
<div style={{ borderRadius: 5 }}>
<Row gutter={16}>{itemEnrollmentsVsUnenrollments}</Row>
</div>
<div style={{ borderRadius: 5 }}>
<Row gutter={16}>{itemEnrollmentType}</Row>
</PageHeader>
<div
style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}
></div>
</div>
</div>
);

@ -19,7 +19,7 @@
import React from 'react';
import { PageHeader, Breadcrumb, Icon } from 'antd';
import { Link } from 'react-router-dom';
import ReportDurationItemList from './ReportDurationItemList';
import PolicyReportHome from './PolicyReportHome';
class Reports extends React.Component {
routes;
@ -70,7 +70,7 @@ class Reports extends React.Component {
</Breadcrumb>
<div className="wrap">
<h3>Reports</h3>
<ReportDurationItemList />
<PolicyReportHome />
</div>
</PageHeader>
<div

@ -0,0 +1,50 @@
/*
* Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. 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.
*/
package org.wso2.carbon.device.mgt.jaxrs.beans;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.ComplianceData;
import java.util.ArrayList;
import java.util.List;
public class ComplianceDeviceList extends BasePaginatedResult{
private List<ComplianceData> complianceData = new ArrayList<>();
@ApiModelProperty(value = "List of devices returned")
@JsonProperty("devices")
public List<ComplianceData> getList() {
return complianceData;
}
public void setList(List<ComplianceData> complianceData) {
this.complianceData = complianceData;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("{\n");
sb.append(" count: ").append(getCount()).append(",\n");
sb.append(" devices: [").append(complianceData).append("\n");
sb.append("]}\n");
return sb.toString();
}
}

@ -1906,4 +1906,110 @@ public interface DeviceManagementService {
@PathParam("status") String status,
@ApiParam(name = "deviceList", value = "The payload containing the new name of the device.", required = true)
@Valid List<String> deviceList);
@GET
@Path("/compliance/{compliance-status}")
@ApiOperation(
produces = MediaType.APPLICATION_JSON,
httpMethod = "GET",
value = "Getting Policy Compliance Status of all devices",
notes = "A policy is enforced on the devices that register with Entgra IoTS. " +
"The server checks if the settings in the device comply with the policy that is enforced on the device using this REST API.",
tags = "Device Management",
extensions = {
@Extension(properties = {
@ExtensionProperty(name = Constants.SCOPE, value = "perm:devices:compliance-data")
})
}
)
@ApiResponses(
value = {
@ApiResponse(
code = 200,
message = "OK",
response = NonComplianceData.class),
@ApiResponse(
code = 400,
message = "Bad Request. \n Invalid request or validation error.",
response = ErrorResponse.class),
@ApiResponse(
code = 500,
message = "Error occurred while getting the compliance data.",
response = ErrorResponse.class)
})
Response getPolicyCompliance(
@ApiParam(
name = "compliance-status",
value = "Compliance status for devices. If true, devices which are compliant with policies. " +
"If false, devices which are not compliant",
required = true)
@PathParam("compliance-status")
boolean complianceStatus,
@ApiParam(
name = "policy",
value = "Policy ID")
@QueryParam("policy") String policyId,
@ApiParam(
name = "is-pending",
value = "Check for devices in pending status")
@QueryParam("pending") boolean isPending,
@ApiParam(
name = "fromDate",
value = "Start date of the duration")
@QueryParam("from") String fromDate,
@ApiParam(
name = "toDate",
value = "end date of the duration")
@QueryParam("to") String toDate,
@ApiParam(
name = "offset",
value = "The starting pagination index for the complete list of qualified items.",
defaultValue = "0")
@QueryParam("offset")
int offset,
@ApiParam(
name = "limit",
value = "Provide how many device details you require from the starting pagination index/offset.",
defaultValue = "5")
@QueryParam("limit")
int limit);
@GET
@Path("/{id}/features")
@ApiOperation(
produces = MediaType.APPLICATION_JSON,
httpMethod = "GET",
value = "Getting Policy Compliance Status of all devices",
notes = "A policy is enforced on the devices that register with Entgra IoTS. " +
"The server checks if the settings in the device comply with the policy that is enforced on the device using this REST API.",
tags = "Device Management",
extensions = {
@Extension(properties = {
@ExtensionProperty(name = Constants.SCOPE, value = "perm:devices:compliance-data")
})
}
)
@ApiResponses(
value = {
@ApiResponse(
code = 200,
message = "OK",
response = NonComplianceData.class),
@ApiResponse(
code = 400,
message = "Bad Request. \n Invalid request or validation error.",
response = ErrorResponse.class),
@ApiResponse(
code = 500,
message = "Error occurred while getting the compliance data.",
response = ErrorResponse.class)
})
Response getNoneComplianceFeatures(
@ApiParam(
name = "id",
value = "The device identifier of the device you wish to get details.\n" +
"INFO: Make sure to add the ID of a device that is already registered with Entgra IoTS.",
required = true)
@PathParam("id")
int id);
}

@ -82,7 +82,7 @@ public interface ReportManagementService {
produces = MediaType.APPLICATION_JSON,
httpMethod = "GET",
value = "Getting Details of Registered Devices",
notes = "Provides details of all the devices enrolled with WSO2 IoT Server.",
notes = "Provides details of all the devices enrolled with Entgra IoT Server.",
tags = "Device Management",
extensions = {
@Extension(properties = {
@ -161,12 +161,12 @@ public interface ReportManagementService {
@GET
@Path("/devices/count")
@Path("/count")
@ApiOperation(
produces = MediaType.APPLICATION_JSON,
httpMethod = "GET",
value = "Getting Details of Registered Devices",
notes = "Provides details of all the devices enrolled with WSO2 IoT Server.",
notes = "Provides details of all the devices enrolled with Entgra IoT Server.",
tags = "Device Management",
extensions = {
@Extension(properties = {
@ -230,4 +230,83 @@ public interface ReportManagementService {
value = "end date of the duration",
required = true)
@QueryParam("to") String toDate) throws ReportManagementException;
@GET
@Path("/devices/count")
@ApiOperation(
produces = MediaType.APPLICATION_JSON,
httpMethod = "GET",
value = "Getting Details of Registered Devices",
notes = "Provides details of all the devices enrolled with Entgra IoT Server.",
tags = "Device Management",
extensions = {
@Extension(properties = {
@ExtensionProperty(name = Constants.SCOPE, value = "perm:devices:view")
})
}
)
@ApiResponses(
value = {
@ApiResponse(
code = 200,
message = "OK. \n Successfully fetched the list of devices.",
response = DeviceList.class,
responseHeaders = {
@ResponseHeader(
name = "Content-Type",
description = "The content type of the body"),
@ResponseHeader(
name = "ETag",
description = "Entity Tag of the response resource.\n" +
"Used by caches, or in conditional requests."),
@ResponseHeader(
name = "Last-Modified",
description = "Date and time the resource was last modified.\n" +
"Used by caches, or in conditional requests."),
}),
@ApiResponse(
code = 400,
message = "Bad Request. \n Invalid device status type received. \n" +
"Valid status types are NEW | CHECKED",
response = ErrorResponse.class),
@ApiResponse(
code = 500,
message = "Internal Server Error. " +
"\n Server error occurred while fetching the device list.",
response = ErrorResponse.class)
})
Response getCountOfDevicesByDuration(
@ApiParam(
name = "status",
value = "Provide the device status details, such as active or inactive.")
@QueryParam("status") List<String> status,
@ApiParam(
name = "ownership",
allowableValues = "BYOD, COPE",
value = "Provide the ownership status of the device. The following values can be assigned:\n" +
"- BYOD: Bring Your Own Device\n" +
"- COPE: Corporate-Owned, Personally-Enabled")
@QueryParam("ownership") String ownership,
@ApiParam(
name = "fromDate",
value = "Start date of the duration",
required = true)
@QueryParam("from") String fromDate,
@ApiParam(
name = "toDate",
value = "end date of the duration",
required = true)
@QueryParam("to") String toDate,
@ApiParam(
name = "offset",
value = "The starting pagination index for the complete list of qualified items.",
defaultValue = "0")
@QueryParam("offset")
int offset,
@ApiParam(
name = "limit",
value = "Provide how many device details you require from the starting pagination index/offset.")
@QueryParam("limit")
int limit) throws ReportManagementException;
}

@ -66,6 +66,8 @@ import org.wso2.carbon.device.mgt.common.operation.mgt.Activity;
import org.wso2.carbon.device.mgt.common.operation.mgt.Operation;
import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManagementException;
import org.wso2.carbon.device.mgt.common.policy.mgt.Policy;
import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.ComplianceData;
import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.ComplianceFeature;
import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.NonComplianceData;
import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.PolicyComplianceException;
import org.wso2.carbon.device.mgt.common.search.PropertyMap;
@ -84,6 +86,7 @@ import org.wso2.carbon.device.mgt.jaxrs.beans.DeviceList;
import org.wso2.carbon.device.mgt.jaxrs.beans.ErrorResponse;
import org.wso2.carbon.device.mgt.jaxrs.beans.OperationList;
import org.wso2.carbon.device.mgt.jaxrs.beans.OperationRequest;
import org.wso2.carbon.device.mgt.jaxrs.beans.ComplianceDeviceList;
import org.wso2.carbon.device.mgt.jaxrs.service.api.DeviceManagementService;
import org.wso2.carbon.device.mgt.jaxrs.service.impl.util.InputValidationException;
import org.wso2.carbon.device.mgt.jaxrs.service.impl.util.RequestValidationUtil;
@ -106,13 +109,12 @@ import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
@ -1056,4 +1058,66 @@ public class DeviceManagementServiceImpl implements DeviceManagementService {
}
return Response.status(Response.Status.OK).build();
}
@GET
@Override
@Path("/compliance/{compliance-status}")
public Response getPolicyCompliance(
@PathParam("compliance-status") boolean complianceStatus,
@QueryParam("policy") String policyId,
@DefaultValue("false")
@QueryParam("pending") boolean isPending,
@QueryParam("from") String fromDate,
@QueryParam("to") String toDate,
@DefaultValue("0")
@QueryParam("offset") int offset,
@DefaultValue("10")
@QueryParam("limit") int limit) {
PaginationRequest request = new PaginationRequest(offset, limit);
ComplianceDeviceList complianceDeviceList = new ComplianceDeviceList();
PaginationResult paginationResult;
try {
PolicyManagerService policyManagerService = DeviceMgtAPIUtils.getPolicyManagementService();
paginationResult = policyManagerService.getPolicyCompliance(request, policyId, complianceStatus, isPending, fromDate, toDate);
if (paginationResult.getData().isEmpty()) {
return Response.status(Response.Status.OK)
.entity("No policy compliance or non compliance devices are available").build();
} else {
complianceDeviceList.setList((List<ComplianceData>) paginationResult.getData());
complianceDeviceList.setCount(paginationResult.getRecordsTotal());
return Response.status(Response.Status.OK).entity(complianceDeviceList).build();
}
} catch (PolicyComplianceException e) {
String msg = "Error occurred while retrieving compliance data";
log.error(msg, e);
return Response.serverError().entity(
new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build();
}
}
@GET
@Override
@Path("/{id}/features")
public Response getNoneComplianceFeatures(
@PathParam("id") int id) {
List<ComplianceFeature> complianceFeatureList;
try {
PolicyManagerService policyManagerService = DeviceMgtAPIUtils.getPolicyManagementService();
complianceFeatureList = policyManagerService.getNoneComplianceFeatures(id);
if (complianceFeatureList.isEmpty()) {
return Response.status(Response.Status.OK).entity("No non compliance features are available").build();
} else {
return Response.status(Response.Status.OK).entity(complianceFeatureList).build();
}
} catch (PolicyComplianceException e) {
String msg = "Error occurred while retrieving non compliance features";
log.error(msg, e);
return Response.serverError().entity(
new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build();
}
}
}

@ -169,9 +169,14 @@ public class PolicyManagementServiceImpl implements PolicyManagementService {
try {
PolicyAdministratorPoint policyAdministratorPoint = policyManagementService.getPAP();
policies = policyAdministratorPoint.getPolicies();
if(offset == 0 && limit == 0){
return Response.status(Response.Status.OK).entity(policies).build();
}else{
targetPolicies.setCount(policies.size());
filteredPolicies = FilteringUtil.getFilteredList(policies, offset, limit);
targetPolicies.setList(filteredPolicies);
return Response.status(Response.Status.OK).entity(targetPolicies).build();
}
} catch (PolicyManagementException e) {
String msg = "Error occurred while retrieving all available policies";
log.error(msg, e);
@ -179,7 +184,7 @@ public class PolicyManagementServiceImpl implements PolicyManagementService {
new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build();
}
return Response.status(Response.Status.OK).entity(targetPolicies).build();
}
@GET

@ -15,15 +15,16 @@
* specific language governing permissions and limitations
* under the License.
*/
package org.wso2.carbon.device.mgt.jaxrs.service.impl;
import com.google.gson.JsonObject;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.device.mgt.common.Device;
import org.wso2.carbon.device.mgt.common.PaginationRequest;
import org.wso2.carbon.device.mgt.common.PaginationResult;
import org.wso2.carbon.device.mgt.common.exceptions.DeviceManagementException;
import org.wso2.carbon.device.mgt.common.exceptions.ReportManagementException;
import org.wso2.carbon.device.mgt.jaxrs.beans.DeviceList;
import org.wso2.carbon.device.mgt.jaxrs.beans.ErrorResponse;
@ -103,7 +104,8 @@ public class ReportManagementServiceImpl implements ReportManagementService {
@QueryParam("to") String toDate) {
int deviceCount;
try {
deviceCount = DeviceMgtAPIUtils.getReportManagementService().getDevicesByDurationCount(status, ownership, fromDate, toDate);
deviceCount = DeviceMgtAPIUtils.getReportManagementService()
.getDevicesByDurationCount(status, ownership, fromDate, toDate);
return Response.status(Response.Status.OK).entity(deviceCount).build();
} catch (ReportManagementException e) {
String errorMessage = "Error while retrieving device count.";
@ -112,4 +114,39 @@ public class ReportManagementServiceImpl implements ReportManagementService {
new ErrorResponse.ErrorResponseBuilder().setMessage(errorMessage).build()).build();
}
}
@GET
@Path("/count")
@Override
public Response getCountOfDevicesByDuration(
@QueryParam("status") List<String> status,
@QueryParam("ownership") String ownership,
@QueryParam("from") String fromDate,
@QueryParam("to") String toDate,
@DefaultValue("0")
@QueryParam("offset") int offset,
@QueryParam("limit") int limit) {
try {
RequestValidationUtil.validatePaginationParameters(offset, limit);
PaginationRequest request = new PaginationRequest(offset, limit);
if (!StringUtils.isBlank(ownership)) {
request.setOwnership(ownership);
}
JsonObject countList = DeviceMgtAPIUtils.getReportManagementService()
.getCountOfDevicesByDuration(request, status, fromDate, toDate);
if (countList.isJsonNull()) {
return Response.status(Response.Status.OK)
.entity("No devices have been enrolled between the given date range").build();
} else {
return Response.status(Response.Status.OK).entity(countList).build();
}
} catch (ReportManagementException e) {
String msg = "Error occurred while retrieving device list";
log.error(msg, e);
return Response.serverError().entity(
new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build();
}
}
}

@ -0,0 +1,45 @@
/*
* Copyright (c) 2020, 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.
*/
package org.wso2.carbon.device.mgt.common;
public class Count {
private String date;
private int count;
public Count(String date, int count) {
this.date = date;
this.count = count;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}

@ -0,0 +1,186 @@
/*
* Copyright (c) 2020, 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.
*/
package org.wso2.carbon.device.mgt.common.policy.mgt.monitor;
import org.wso2.carbon.device.mgt.common.policy.mgt.Policy;
import java.sql.Timestamp;
import java.util.List;
public class ComplianceData {
private int id;
private int deviceId;
private String deviceName;
private String deviceType;
private String owner;
private int enrolmentId;
private int policyId;
private String policyName;
List<ComplianceFeature> complianceFeatures;
private boolean status;
private Timestamp lastRequestedTime;
private Timestamp lastSucceededTime;
private Timestamp lastFailedTime;
private int attempts;
private String message;
/**
* This parameter is to inform the policy core, weather related device type plugins does need the full policy or
* the part which is none compliance.
*/
private boolean completePolicy;
private Policy policy;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getEnrolmentId() {
return enrolmentId;
}
public void setEnrolmentId(int enrolmentId) {
this.enrolmentId = enrolmentId;
}
public Timestamp getLastRequestedTime() {
return lastRequestedTime;
}
public void setLastRequestedTime(Timestamp lastRequestedTime) {
this.lastRequestedTime = lastRequestedTime;
}
public Timestamp getLastSucceededTime() {
return lastSucceededTime;
}
public void setLastSucceededTime(Timestamp lastSucceededTime) {
this.lastSucceededTime = lastSucceededTime;
}
public Timestamp getLastFailedTime() {
return lastFailedTime;
}
public void setLastFailedTime(Timestamp lastFailedTime) {
this.lastFailedTime = lastFailedTime;
}
public int getAttempts() {
return attempts;
}
public void setAttempts(int attempts) {
this.attempts = attempts;
}
public int getDeviceId() {
return deviceId;
}
public void setDeviceId(int deviceId) {
this.deviceId = deviceId;
}
public int getPolicyId() {
return policyId;
}
public void setPolicyId(int policyId) {
this.policyId = policyId;
}
public List<ComplianceFeature> getComplianceFeatures() {
return complianceFeatures;
}
public void setComplianceFeatures(List<ComplianceFeature> complianceFeatures) {
this.complianceFeatures = complianceFeatures;
}
public boolean isStatus() {
return status;
}
public void setStatus(boolean status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public boolean isCompletePolicy() {
return completePolicy;
}
public void setCompletePolicy(boolean completePolicy) {
this.completePolicy = completePolicy;
}
public Policy getPolicy() {
return policy;
}
public void setPolicy(Policy policy) {
this.policy = policy;
}
public String getDeviceName() {
return deviceName;
}
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
public String getPolicyName() {
return policyName;
}
public void setPolicyName(String policyName) {
this.policyName = policyName;
}
public String getDeviceType() {
return deviceType;
}
public void setDeviceType(String deviceType) {
this.deviceType = deviceType;
}
}

@ -17,6 +17,7 @@
*/
package org.wso2.carbon.device.mgt.common.report.mgt;
import com.google.gson.JsonObject;
import org.wso2.carbon.device.mgt.common.PaginationRequest;
import org.wso2.carbon.device.mgt.common.PaginationResult;
import org.wso2.carbon.device.mgt.common.exceptions.ReportManagementException;
@ -43,4 +44,7 @@ public interface ReportManagementService {
int getDevicesByDurationCount(List<String> statusList, String ownership, String fromDate, String toDate)
throws ReportManagementException;
JsonObject getCountOfDevicesByDuration(PaginationRequest request, List<String> statusList, String fromDate, String toDate)
throws ReportManagementException;
}

@ -35,11 +35,8 @@
package org.wso2.carbon.device.mgt.core.dao;
import org.wso2.carbon.device.mgt.common.Device;
import org.wso2.carbon.device.mgt.common.DeviceIdentifier;
import org.wso2.carbon.device.mgt.common.EnrolmentInfo;
import org.wso2.carbon.device.mgt.common.*;
import org.wso2.carbon.device.mgt.common.EnrolmentInfo.Status;
import org.wso2.carbon.device.mgt.common.PaginationRequest;
import org.wso2.carbon.device.mgt.common.device.details.DeviceLocationHistory;
import org.wso2.carbon.device.mgt.common.configuration.mgt.DevicePropertyInfo;
import org.wso2.carbon.device.mgt.common.device.details.DeviceData;
@ -561,6 +558,23 @@ public interface DeviceDAO {
List<String> statusList, String ownership, String fromDate, String toDate, int tenantId)
throws DeviceManagementDAOException;
/**
* This method is used to get the device count to generate the report graph within a specific time periode
*
* @param request Pagination request to get paginated result
* @param statusList Status list to filter data
* @param tenantId ID of the current tenant
* @param fromDate Start date to filter devices(YYYY-MM-DD)
* @param toDate End date to filter devices(YYYY-MM-DD)
* @return returns a list of Count objects
* @throws DeviceManagementDAOException
*/
List<Count> getCountOfDevicesByDuration(PaginationRequest request,
List<String> statusList,
int tenantId,
String fromDate,
String toDate) throws DeviceManagementDAOException;
/**
* Retrieve device location information
* @param deviceIdentifier Device Identifier object

@ -21,6 +21,7 @@ package org.wso2.carbon.device.mgt.core.dao.impl.device;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.device.mgt.common.Count;
import org.wso2.carbon.device.mgt.common.Device;
import org.wso2.carbon.device.mgt.common.PaginationRequest;
import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOException;
@ -529,7 +530,7 @@ public class GenericDeviceDAOImpl extends AbstractDeviceDAOImpl {
@Override
public int getDevicesByDurationCount(List<String> statusList, String ownership, String fromDate, String toDate, int tenantId) throws DeviceManagementDAOException {
int deviceCount = 0;
boolean isStatusProvided = false;
boolean isStatusProvided;
String sql = "SELECT " +
"COUNT(d.ID) AS DEVICE_COUNT " +
@ -576,6 +577,70 @@ public class GenericDeviceDAOImpl extends AbstractDeviceDAOImpl {
return deviceCount;
}
@Override
public List<Count> getCountOfDevicesByDuration(PaginationRequest request, List<String> statusList, int tenantId,
String fromDate, String toDate)
throws DeviceManagementDAOException {
List<Count> countList = new ArrayList<>();
String ownership = request.getOwnership();
boolean isStatusProvided;
String sql =
"SELECT " +
"SUBSTRING(e.DATE_OF_ENROLMENT, 1, 10) AS ENROLMENT_DATE, " +
"COUNT(SUBSTRING(e.DATE_OF_ENROLMENT, 1, 10)) AS ENROLMENT_COUNT " +
"FROM DM_DEVICE AS d " +
"INNER JOIN DM_ENROLMENT AS e ON d.ID = e.DEVICE_ID " +
"INNER JOIN DM_DEVICE_TYPE AS t ON d.DEVICE_TYPE_ID = t.ID " +
"AND e.TENANT_ID = ? " +
"AND e.DATE_OF_ENROLMENT " +
"BETWEEN ? AND ? ";
//Add the query for status
StringBuilder sqlBuilder = new StringBuilder(sql);
isStatusProvided = buildStatusQuery(statusList, sqlBuilder);
sql = sqlBuilder.toString();
if (ownership != null) {
sql = sql + " AND e.OWNERSHIP = ?";
}
sql = sql + " GROUP BY SUBSTRING(e.DATE_OF_ENROLMENT, 1, 10) LIMIT ?,?";
try (Connection conn = this.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {
int paramIdx = 1;
stmt.setInt(paramIdx++, tenantId);
stmt.setString(paramIdx++, fromDate);
stmt.setString(paramIdx++, toDate);
if (isStatusProvided) {
for (String status : statusList) {
stmt.setString(paramIdx++, status);
}
}
if (ownership != null) {
stmt.setString(paramIdx++, ownership);
}
stmt.setInt(paramIdx++, request.getStartIndex());
stmt.setInt(paramIdx, request.getRowCount());
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
Count count = new Count(
rs.getString("ENROLMENT_DATE"),
rs.getInt("ENROLMENT_COUNT")
);
countList.add(count);
}
}
} catch (SQLException e) {
String msg = "Error occurred while retrieving information of all " +
"registered devices under tenant id " + tenantId + " between " + fromDate + " to " + toDate;
log.error(msg, e);
throw new DeviceManagementDAOException(msg, e);
}
return countList;
}
protected boolean buildStatusQuery(List<String> statusList, StringBuilder sqlBuilder) {
if (statusList != null && !statusList.isEmpty() && !statusList.get(0).isEmpty()) {
sqlBuilder.append(" AND e.STATUS IN(");

@ -21,6 +21,7 @@ package org.wso2.carbon.device.mgt.core.dao.impl.device;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.device.mgt.common.Count;
import org.wso2.carbon.device.mgt.common.Device;
import org.wso2.carbon.device.mgt.common.PaginationRequest;
import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOException;
@ -527,6 +528,85 @@ public class OracleDeviceDAOImpl extends AbstractDeviceDAOImpl {
return 0;
}
@Override
public List<Count> getCountOfDevicesByDuration(PaginationRequest request, List<String> statusList, int tenantId,
String fromDate, String toDate)
throws DeviceManagementDAOException {
List<Count> countList = new ArrayList<>();
String ownership = request.getOwnership();
boolean isStatusProvided;
String sql =
"SELECT " +
"SUBSTRING(e.DATE_OF_ENROLMENT, 1, 10) AS ENROLMENT_DATE, " +
"COUNT(SUBSTRING(e.DATE_OF_ENROLMENT, 1, 10)) AS ENROLMENT_COUNT " +
"FROM DM_DEVICE AS d " +
"INNER JOIN DM_ENROLMENT AS e ON d.ID = e.DEVICE_ID " +
"INNER JOIN DM_DEVICE_TYPE AS t ON d.DEVICE_TYPE_ID = t.ID " +
"AND e.TENANT_ID = ? " +
"AND e.DATE_OF_ENROLMENT BETWEEN ? AND ? ";
//Add the query for status
StringBuilder sqlBuilder = new StringBuilder(sql);
isStatusProvided = buildStatusQuery(statusList, sqlBuilder);
sql = sqlBuilder.toString();
if (ownership != null) {
sql = sql + " AND e.OWNERSHIP = ?";
}
sql = sql + " GROUP BY SUBSTRING(e.DATE_OF_ENROLMENT, 1, 10) OFFSET ? ROWS FETCH NEXT ? ROWS ONLY";
try (Connection conn = this.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {
int paramIdx = 1;
stmt.setInt(paramIdx++, tenantId);
stmt.setString(paramIdx++, fromDate);
stmt.setString(paramIdx++, toDate);
if (isStatusProvided) {
for (String status : statusList) {
stmt.setString(paramIdx++, status);
}
}
if (ownership != null) {
stmt.setString(paramIdx++, ownership);
}
stmt.setInt(paramIdx++, request.getStartIndex());
stmt.setInt(paramIdx, request.getRowCount());
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
Count count = new Count(
rs.getString("ENROLMENT_DATE"),
rs.getInt("ENROLMENT_COUNT")
);
countList.add(count);
}
}
} catch (SQLException e) {
String msg = "Error occurred while retrieving information of all " +
"registered devices under tenant id " + tenantId + " between " + fromDate + " to " + toDate;
log.error(msg, e);
throw new DeviceManagementDAOException(msg, e);
}
return countList;
}
protected boolean buildStatusQuery(List<String> statusList, StringBuilder sqlBuilder) {
if (statusList != null && !statusList.isEmpty() && !statusList.get(0).isEmpty()) {
sqlBuilder.append(" AND e.STATUS IN(");
for (int i = 0; i < statusList.size(); i++) {
sqlBuilder.append("?");
if (i != statusList.size() - 1) {
sqlBuilder.append(",");
}
}
sqlBuilder.append(")");
return true;
}else {
return false;
}
}
/**
* Get the list of devices that matches with the given device name and (or) device type.
*

@ -21,6 +21,7 @@ package org.wso2.carbon.device.mgt.core.dao.impl.device;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.device.mgt.common.Count;
import org.wso2.carbon.device.mgt.common.Device;
import org.wso2.carbon.device.mgt.common.PaginationRequest;
import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOException;
@ -506,6 +507,86 @@ public class PostgreSQLDeviceDAOImpl extends AbstractDeviceDAOImpl {
return 0;
}
@Override
public List<Count> getCountOfDevicesByDuration(PaginationRequest request, List<String> statusList, int tenantId,
String fromDate, String toDate)
throws DeviceManagementDAOException {
List<Count> countList = new ArrayList<>();
String ownership = request.getOwnership();
boolean isStatusProvided;
String sql =
"SELECT " +
"SUBSTRING(e.DATE_OF_ENROLMENT, 1, 10) AS ENROLMENT_DATE, " +
"COUNT(SUBSTRING(e.DATE_OF_ENROLMENT, 1, 10)) AS ENROLMENT_COUNT " +
"FROM DM_DEVICE AS d " +
"INNER JOIN DM_ENROLMENT AS e ON d.ID = e.DEVICE_ID " +
"INNER JOIN DM_DEVICE_TYPE AS t ON d.DEVICE_TYPE_ID = t.ID " +
"AND e.TENANT_ID = ? " +
"AND e.DATE_OF_ENROLMENT BETWEEN ? AND ? ";
//Add the query for status
StringBuilder sqlBuilder = new StringBuilder(sql);
isStatusProvided = buildStatusQuery(statusList, sqlBuilder);
sql = sqlBuilder.toString();
if (ownership != null) {
sql = sql + " AND e.OWNERSHIP = ?";
}
sql = sql + " GROUP BY SUBSTRING(e.DATE_OF_ENROLMENT, 1, 10) LIMIT ? OFFSET ?";
try (Connection conn = this.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {
int paramIdx = 1;
stmt.setInt(paramIdx++, tenantId);
stmt.setString(paramIdx++, fromDate);
stmt.setString(paramIdx++, toDate);
if (isStatusProvided) {
for (String status : statusList) {
stmt.setString(paramIdx++, status);
}
}
if (ownership != null) {
stmt.setString(paramIdx++, ownership);
}
stmt.setInt(paramIdx++, request.getStartIndex());
stmt.setInt(paramIdx, request.getRowCount());
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
Count count = new Count(
rs.getString("ENROLMENT_DATE"),
rs.getInt("ENROLMENT_COUNT")
);
countList.add(count);
}
}
} catch (SQLException e) {
String msg = "Error occurred while retrieving information of all " +
"registered devices under tenant id " + tenantId + " between " + fromDate + " to " + toDate;
log.error(msg, e);
throw new DeviceManagementDAOException(msg, e);
}
return countList;
}
protected boolean buildStatusQuery(List<String> statusList, StringBuilder sqlBuilder) {
if (statusList != null && !statusList.isEmpty() && !statusList.get(0).isEmpty()) {
sqlBuilder.append(" AND e.STATUS IN(");
for (int i = 0; i < statusList.size(); i++) {
sqlBuilder.append("?");
if (i != statusList.size() - 1) {
sqlBuilder.append(",");
}
}
sqlBuilder.append(")");
return true;
}else {
return false;
}
}
/**
* Get the list of devices that matches with the given device name and (or) device type.
*

@ -21,6 +21,7 @@ package org.wso2.carbon.device.mgt.core.dao.impl.device;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.device.mgt.common.Count;
import org.wso2.carbon.device.mgt.common.Device;
import org.wso2.carbon.device.mgt.common.PaginationRequest;
import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOException;
@ -671,6 +672,85 @@ public class SQLServerDeviceDAOImpl extends AbstractDeviceDAOImpl {
return 0;
}
@Override
public List<Count> getCountOfDevicesByDuration(PaginationRequest request, List<String> statusList, int tenantId,
String fromDate, String toDate)
throws DeviceManagementDAOException {
List<Count> countList = new ArrayList<>();
String ownership = request.getOwnership();
boolean isStatusProvided;
String sql =
"SELECT " +
"SUBSTRING(e.DATE_OF_ENROLMENT, 1, 10) AS ENROLMENT_DATE, " +
"COUNT(SUBSTRING(e.DATE_OF_ENROLMENT, 1, 10)) AS ENROLMENT_COUNT " +
"FROM DM_DEVICE AS d " +
"INNER JOIN DM_ENROLMENT AS e ON d.ID = e.DEVICE_ID " +
"INNER JOIN DM_DEVICE_TYPE AS t ON d.DEVICE_TYPE_ID = t.ID " +
"AND e.TENANT_ID = ? " +
"AND e.DATE_OF_ENROLMENT BETWEEN ? AND ? ";
//Add the query for status
StringBuilder sqlBuilder = new StringBuilder(sql);
isStatusProvided = buildStatusQuery(statusList, sqlBuilder);
sql = sqlBuilder.toString();
if (ownership != null) {
sql = sql + " AND e.OWNERSHIP = ?";
}
sql = sql + " GROUP BY SUBSTRING(e.DATE_OF_ENROLMENT, 1, 10) OFFSET ? ROWS FETCH NEXT ? ROWS ONLY";
try (Connection conn = this.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {
int paramIdx = 1;
stmt.setInt(paramIdx++, tenantId);
stmt.setString(paramIdx++, fromDate);
stmt.setString(paramIdx++, toDate);
if (isStatusProvided) {
for (String status : statusList) {
stmt.setString(paramIdx++, status);
}
}
if (ownership != null) {
stmt.setString(paramIdx++, ownership);
}
stmt.setInt(paramIdx++, request.getStartIndex());
stmt.setInt(paramIdx, request.getRowCount());
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
Count count = new Count(
rs.getString("ENROLMENT_DATE"),
rs.getInt("ENROLMENT_COUNT")
);
countList.add(count);
}
}
} catch (SQLException e) {
String msg = "Error occurred while retrieving information of all " +
"registered devices under tenant id " + tenantId + " between " + fromDate + " to " + toDate;
log.error(msg, e);
throw new DeviceManagementDAOException(msg, e);
}
return countList;
}
protected boolean buildStatusQuery(List<String> statusList, StringBuilder sqlBuilder) {
if (statusList != null && !statusList.isEmpty() && !statusList.get(0).isEmpty()) {
sqlBuilder.append(" AND e.STATUS IN(");
for (int i = 0; i < statusList.size(); i++) {
sqlBuilder.append("?");
if (i != statusList.size() - 1) {
sqlBuilder.append(",");
}
}
sqlBuilder.append(")");
return true;
}else {
return false;
}
}
@Override
public int getSubscribedDeviceCount(List<Integer> deviceIds, int tenantId, String status)
throws DeviceManagementDAOException {

@ -17,8 +17,10 @@
*/
package org.wso2.carbon.device.mgt.core.report.mgt;
import com.google.gson.JsonObject;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.device.mgt.common.Count;
import org.wso2.carbon.device.mgt.common.Device;
import org.wso2.carbon.device.mgt.common.exceptions.DeviceManagementException;
import org.wso2.carbon.device.mgt.common.PaginationRequest;
@ -32,7 +34,13 @@ import org.wso2.carbon.device.mgt.core.dao.util.DeviceManagementDAOUtil;
import org.wso2.carbon.device.mgt.core.util.DeviceManagerUtil;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Calendar;
import java.util.concurrent.TimeUnit;
/**
* This is the service class for reports which calls dao classes and its method which are used for
@ -92,7 +100,8 @@ public class ReportManagementServiceImpl implements ReportManagementService {
throws ReportManagementException {
try {
DeviceManagementDAOFactory.openConnection();
return deviceDAO.getDevicesByDurationCount(statusList, ownership, fromDate, toDate, DeviceManagementDAOUtil.getTenantId());
return deviceDAO.getDevicesByDurationCount(
statusList, ownership, fromDate, toDate, DeviceManagementDAOUtil.getTenantId());
} catch (DeviceManagementDAOException e) {
String msg = "Error occurred in while retrieving device count by status for " + statusList + "devices.";
log.error(msg, e);
@ -105,4 +114,98 @@ public class ReportManagementServiceImpl implements ReportManagementService {
DeviceManagementDAOFactory.closeConnection();
}
}
@Override
public JsonObject getCountOfDevicesByDuration(PaginationRequest request, List<String> statusList, String fromDate,
String toDate)
throws ReportManagementException {
try {
request = DeviceManagerUtil.validateDeviceListPageSize(request);
} catch (DeviceManagementException e) {
String msg = "Error occurred while validating device list page size";
log.error(msg, e);
throw new ReportManagementException(msg, e);
}
try {
DeviceManagementDAOFactory.openConnection();
List<Count> dateList = deviceDAO.getCountOfDevicesByDuration(
request,
statusList,
DeviceManagementDAOUtil.getTenantId(),
fromDate,
toDate
);
return buildCount(fromDate, toDate, dateList);
} catch (SQLException e) {
String msg = "Error occurred while opening a connection " +
"to the data source";
log.error(msg, e);
throw new ReportManagementException(msg, e);
} catch (DeviceManagementDAOException e) {
String msg = "Error occurred while retrieving Tenant ID between " + fromDate + " to " + toDate;
log.error(msg, e);
throw new ReportManagementException(msg, e);
} catch (ParseException e) {
String msg = "Error occurred while building count";
log.error(msg, e);
throw new ReportManagementException(msg, e);
} finally {
DeviceManagementDAOFactory.closeConnection();
}
}
//NOTE: This is just a temporary method for retrieving device counts
public JsonObject buildCount(String start, String end, List<Count> countList) throws ParseException {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
int prevDateAmount = 0;
boolean isDaily = false;
Date startDate = dateFormat.parse(start);
Date endDate = dateFormat.parse(end);
//Check duration between two given dates
long gap = endDate.getTime() - startDate.getTime();
long diffInDays = TimeUnit.MILLISECONDS.toDays(gap);
if (diffInDays < 7) {
isDaily = true;
} else if (diffInDays < 30) {
prevDateAmount = -7;
} else {
prevDateAmount = -30;
}
JsonObject resultObject = new JsonObject();
if (!isDaily) {
//Divide date duration into week or month blocks
while (endDate.after(startDate)) {
int sum = 0;
Calendar calendar = Calendar.getInstance();
calendar.setTime(endDate);
calendar.add(Calendar.DAY_OF_YEAR, prevDateAmount);
Date previousDate = calendar.getTime();
if (startDate.after(previousDate)) {
previousDate = startDate;
}
//Loop count list which came from database to add them into week or month blocks
for (Count count : countList) {
if (dateFormat.parse(
count.getDate()).after(previousDate) &&
dateFormat.parse(count.getDate()).before(endDate
)) {
sum = sum + count.getCount();
}
}
//Map date blocks and counts
resultObject.addProperty(
dateFormat.format(endDate) + " - " + dateFormat.format(previousDate), sum);
endDate = previousDate;
}
} else {
for (Count count : countList) {
resultObject.addProperty(count.getDate(), count.getCount());
}
}
return resultObject;
}
}

@ -21,6 +21,8 @@ package org.wso2.carbon.policy.mgt.core;
import org.wso2.carbon.device.mgt.common.DeviceIdentifier;
import org.wso2.carbon.device.mgt.common.Feature;
import org.wso2.carbon.device.mgt.common.PaginationRequest;
import org.wso2.carbon.device.mgt.common.PaginationResult;
import org.wso2.carbon.device.mgt.common.policy.mgt.Policy;
import org.wso2.carbon.device.mgt.common.policy.mgt.Profile;
import org.wso2.carbon.device.mgt.common.policy.mgt.ProfileFeature;
@ -81,4 +83,10 @@ public interface PolicyManagerService {
NonComplianceData getDeviceCompliance(DeviceIdentifier deviceIdentifier) throws PolicyComplianceException;
boolean isCompliant(DeviceIdentifier deviceIdentifier) throws PolicyComplianceException;
PaginationResult getPolicyCompliance(
PaginationRequest paginationRequest, String policyId, boolean complianceStatus, boolean isPending, String fromDate, String toDate)
throws PolicyComplianceException;
List<ComplianceFeature> getNoneComplianceFeatures(int complianceStatusId) throws PolicyComplianceException;
}

@ -39,6 +39,8 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.device.mgt.common.DeviceIdentifier;
import org.wso2.carbon.device.mgt.common.Feature;
import org.wso2.carbon.device.mgt.common.PaginationRequest;
import org.wso2.carbon.device.mgt.common.PaginationResult;
import org.wso2.carbon.device.mgt.common.exceptions.InvalidDeviceException;
import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManagementException;
import org.wso2.carbon.device.mgt.common.policy.mgt.Policy;
@ -248,4 +250,16 @@ public class PolicyManagerServiceImpl implements PolicyManagerService {
public boolean isCompliant(DeviceIdentifier deviceIdentifier) throws PolicyComplianceException {
return monitoringManager.isCompliant(deviceIdentifier);
}
@Override
public PaginationResult getPolicyCompliance(
PaginationRequest paginationRequest, String policyId, boolean complianceStatus, boolean isPending, String fromDate, String toDate)
throws PolicyComplianceException {
return monitoringManager.getPolicyCompliance(paginationRequest, policyId, complianceStatus, isPending, fromDate, toDate);
}
@Override
public List<ComplianceFeature> getNoneComplianceFeatures(int complianceStatusId) throws PolicyComplianceException {
return monitoringManager.getNoneComplianceFeatures(complianceStatusId);
}
}

@ -19,6 +19,8 @@
package org.wso2.carbon.policy.mgt.core.dao;
import org.wso2.carbon.device.mgt.common.PaginationRequest;
import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.ComplianceData;
import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.NonComplianceData;
import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.ComplianceFeature;
import org.wso2.carbon.policy.mgt.common.monitor.PolicyDeviceWrapper;
@ -55,6 +57,10 @@ public interface MonitoringDAO {
List<NonComplianceData> getCompliance() throws MonitoringDAOException;
List<ComplianceData> getAllComplianceDevices(
PaginationRequest paginationRequest, String policyId, boolean complianceStatus, boolean isPending, String fromDate, String toDate)
throws MonitoringDAOException;
List<ComplianceFeature> getNoneComplianceFeatures(int policyComplianceStatusId) throws MonitoringDAOException;
void deleteNoneComplianceData(int policyComplianceStatusId) throws MonitoringDAOException;

@ -22,6 +22,8 @@ package org.wso2.carbon.policy.mgt.core.dao.impl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.device.mgt.common.PaginationRequest;
import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.ComplianceData;
import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.NonComplianceData;
import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.ComplianceFeature;
import org.wso2.carbon.policy.mgt.common.monitor.PolicyDeviceWrapper;
@ -347,6 +349,97 @@ public class MonitoringDAOImpl implements MonitoringDAO {
}
}
@Override
public List<ComplianceData> getAllComplianceDevices(
PaginationRequest paginationRequest,
String policyId,
boolean complianceStatus,
boolean isPending,
String fromDate,
String toDate)
throws MonitoringDAOException {
List<ComplianceData> complianceDataList = new ArrayList<>();
int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId();
String query =
"SELECT " +
"DEVICE.NAME, " +
"DM_DEVICE_TYPE.NAME AS DEVICE_TYPE, " +
"ENROLLMENT.OWNER, " +
"DM_POLICY.NAME AS POLICY_NAME, " +
"POLICY.* " +
"FROM DM_POLICY_COMPLIANCE_STATUS AS POLICY, DM_DEVICE AS DEVICE, " +
"DM_ENROLMENT AS ENROLLMENT, DM_POLICY, DM_DEVICE_TYPE " +
"WHERE DEVICE.ID=POLICY.DEVICE_ID " +
"AND DEVICE.ID=ENROLLMENT.DEVICE_ID " +
"AND POLICY.POLICY_ID=DM_POLICY.ID " +
"AND DEVICE.DEVICE_TYPE_ID=DM_DEVICE_TYPE.ID " +
"AND POLICY.TENANT_ID = ? AND POLICY.STATUS = ?";
if (isPending) {
query = query + " AND POLICY.LAST_SUCCESS_TIME IS NULL " +
"AND POLICY.LAST_FAILED_TIME IS NULL";
} else {
query = query + " AND (POLICY.LAST_SUCCESS_TIME IS NOT NULL " +
"OR POLICY.LAST_FAILED_TIME IS NOT NULL)";
}
if (policyId != null) {
query = query + " AND POLICY.POLICY_ID = ?";
}
if (fromDate != null && toDate != null) {
if (!complianceStatus) {
query = query + " AND POLICY.LAST_FAILED_TIME BETWEEN ? AND ?";
} else {
query = query + " AND POLICY.LAST_SUCCESS_TIME BETWEEN ? AND ?";
}
}
query = query + " LIMIT ?,?";
try (Connection conn = this.getConnection();
PreparedStatement stmt = conn.prepareStatement(query);) {
int paramIdx = 1;
stmt.setInt(paramIdx++, tenantId);
stmt.setBoolean(paramIdx++, complianceStatus);
if (policyId != null) {
stmt.setInt(paramIdx++, Integer.parseInt(policyId));
}
if (fromDate != null && toDate != null) {
stmt.setString(paramIdx++, fromDate);
stmt.setString(paramIdx++, toDate);
}
stmt.setInt(paramIdx++, paginationRequest.getStartIndex());
stmt.setInt(paramIdx, paginationRequest.getRowCount());
try (ResultSet resultSet = stmt.executeQuery()) {
while (resultSet.next()) {
ComplianceData complianceData = new ComplianceData();
complianceData.setId(resultSet.getInt("ID"));
complianceData.setDeviceId(resultSet.getInt("DEVICE_ID"));
complianceData.setDeviceName(resultSet.getString("NAME"));
complianceData.setDeviceType(resultSet.getString("DEVICE_TYPE"));
complianceData.setOwner(resultSet.getString("OWNER"));
complianceData.setEnrolmentId(resultSet.getInt("ENROLMENT_ID"));
complianceData.setPolicyId(resultSet.getInt("POLICY_ID"));
complianceData.setPolicyName(resultSet.getString("POLICY_NAME"));
complianceData.setStatus(resultSet.getBoolean("STATUS"));
complianceData.setAttempts(resultSet.getInt("ATTEMPTS"));
complianceData.setLastRequestedTime(resultSet.getTimestamp("LAST_REQUESTED_TIME"));
complianceData.setLastFailedTime(resultSet.getTimestamp("LAST_FAILED_TIME"));
complianceData.setLastSucceededTime(resultSet.getTimestamp("LAST_SUCCESS_TIME"));
complianceDataList.add(complianceData);
}
}
} catch (SQLException e) {
String msg = "Unable to retrieve compliance data from database.";
log.error(msg, e);
throw new MonitoringDAOException(msg, e);
}
return complianceDataList;
}
@Override
public List<ComplianceFeature> getNoneComplianceFeatures(int policyComplianceStatusId) throws
MonitoringDAOException {

@ -21,6 +21,8 @@ package org.wso2.carbon.policy.mgt.core.mgt;
import org.wso2.carbon.device.mgt.common.Device;
import org.wso2.carbon.device.mgt.common.DeviceIdentifier;
import org.wso2.carbon.device.mgt.common.PaginationRequest;
import org.wso2.carbon.device.mgt.common.PaginationResult;
import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.NonComplianceData;
import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.ComplianceFeature;
import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.PolicyComplianceException;
@ -41,4 +43,11 @@ public interface MonitoringManager {
List<String> getDeviceTypes() throws PolicyComplianceException;
PaginationResult getPolicyCompliance(
PaginationRequest paginationRequest, String policyId, boolean complianceStatus, boolean isPending, String fromDate, String toDate)
throws PolicyComplianceException;
List<ComplianceFeature> getNoneComplianceFeatures(int complianceStatusId)
throws PolicyComplianceException;
}

@ -22,11 +22,14 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.device.mgt.common.Device;
import org.wso2.carbon.device.mgt.common.DeviceIdentifier;
import org.wso2.carbon.device.mgt.common.PaginationRequest;
import org.wso2.carbon.device.mgt.common.PaginationResult;
import org.wso2.carbon.device.mgt.common.exceptions.DeviceManagementException;
import org.wso2.carbon.device.mgt.common.exceptions.InvalidDeviceException;
import org.wso2.carbon.device.mgt.common.operation.mgt.Operation;
import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManagementException;
import org.wso2.carbon.device.mgt.common.policy.mgt.PolicyMonitoringManager;
import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.ComplianceData;
import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.ComplianceFeature;
import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.NonComplianceData;
import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.PolicyComplianceException;
@ -371,6 +374,52 @@ public class MonitoringManagerImpl implements MonitoringManager {
return deviceTypes;
}
@Override
public PaginationResult getPolicyCompliance(
PaginationRequest paginationRequest, String policyId,
boolean complianceStatus, boolean isPending, String fromDate, String toDate)
throws PolicyComplianceException {
PaginationResult paginationResult = new PaginationResult();
try {
PolicyManagementDAOFactory.openConnection();
List<ComplianceData> complianceDataList = monitoringDAO
.getAllComplianceDevices(paginationRequest, policyId, complianceStatus, isPending, fromDate, toDate);
paginationResult.setData(complianceDataList);
paginationResult.setRecordsTotal(complianceDataList.size());
} catch (MonitoringDAOException e) {
String msg = "Unable to retrieve compliance data";
log.error(msg, e);
throw new PolicyComplianceException(msg, e);
} catch (SQLException e) {
String msg = "Error occurred while opening a connection to the data source";
log.error(msg, e);
throw new PolicyComplianceException(msg, e);
} finally {
PolicyManagementDAOFactory.closeConnection();
}
return paginationResult;
}
@Override
public List<ComplianceFeature> getNoneComplianceFeatures(int complianceStatusId) throws PolicyComplianceException {
List<ComplianceFeature> complianceFeatureList;
try {
PolicyManagementDAOFactory.openConnection();
complianceFeatureList = monitoringDAO.getNoneComplianceFeatures(complianceStatusId);
} catch (MonitoringDAOException e) {
String msg = "Unable to retrieve non compliance features";
log.error(msg, e);
throw new PolicyComplianceException(msg, e);
} catch (SQLException e) {
String msg = "Error occurred while opening a connection to the data source";
log.error(msg, e);
throw new PolicyComplianceException(msg, e);
} finally {
PolicyManagementDAOFactory.closeConnection();
}
return complianceFeatureList;
}
private void addMonitoringOperationsToDatabase(List<Device> devices)
throws PolicyComplianceException, OperationManagementException, InvalidDeviceException {

Loading…
Cancel
Save