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}> <ConfigContext.Provider value={this.state.config}>
<div> <div>
<Switch> <Switch>
<Redirect exact from="/entgra" to="/entgra/devices" /> <Redirect exact from="/entgra" to="/entgra/reports" />
{this.props.routes.map(route => ( {this.props.routes.map(route => (
<RouteWithSubRoutes key={route.path} {...route} /> <RouteWithSubRoutes key={route.path} {...route} />
))} ))}

@ -33,28 +33,27 @@ class DateRangePicker extends React.Component {
render() { render() {
const { RangePicker } = DatePicker; const { RangePicker } = DatePicker;
return ( return (
<RangePicker <div>
ranges={{ <RangePicker
Today: [moment(), moment()], ranges={{
Yesterday: [ Today: [moment(), moment()],
moment().subtract(1, 'days'), Yesterday: [moment().subtract(1, 'days'), moment()],
moment().subtract(1, 'days'), 'Last 7 Days': [moment().subtract(7, 'days'), moment()],
], 'Last 30 Days': [moment().subtract(30, 'days'), moment()],
'Last 7 Days': [moment().subtract(6, 'days'), moment()], 'This Month': [moment().startOf('month'), moment().endOf('month')],
'Last 30 Days': [moment().subtract(29, 'days'), moment()], 'Last Month': [
'This Month': [moment().startOf('month'), moment().endOf('month')], moment()
'Last Month': [ .subtract(1, 'month')
moment() .startOf('month'),
.subtract(1, 'month') moment()
.startOf('month'), .subtract(1, 'month')
moment() .endOf('month'),
.subtract(1, 'month') ],
.endOf('month'), }}
], format="YYYY-MM-DD"
}} onChange={this.onChange}
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, * Entgra (pvt) Ltd. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except * Version 2.0 (the "License"); you may not use this file except
@ -17,14 +17,16 @@
*/ */
import React from 'react'; 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 { Link } from 'react-router-dom';
import ReportDeviceTable from '../../../components/Devices/ReportDevicesTable';
import PieChart from '../../../components/Reports/Widgets/PieChart';
import { withConfigContext } from '../../../context/ConfigContext'; import { withConfigContext } from '../../../context/ConfigContext';
import axios from 'axios';
const { Option } = Select; 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 { class DeviceStatusReport extends React.Component {
routes; routes;
@ -32,37 +34,145 @@ class DeviceStatusReport extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.routes = props.routes; this.routes = props.routes;
const { reportData } = this.props.location;
this.state = { this.state = {
selectedTags: ['Enrolled'],
paramsObject: { paramsObject: {
from: reportData.duration[0], from: moment()
to: reportData.duration[1], .subtract(7, 'days')
.format('YYYY-MM-DD'),
to: moment().format('YYYY-MM-DD'),
}, },
statsObject: {}, data: [],
statArray: [ fields: [],
{ item: 'ACTIVE', count: 0 }, durationMode: 'weekly',
{ item: 'INACTIVE', count: 0 }, visible: false,
{ item: 'REMOVED', count: 0 },
],
}; };
} }
onClickPieChart = value => { handleDurationModeChange = e => {
console.log(value.data.point.item); const durationMode = e.target.value;
const chartValue = value.data.point.item; switch (durationMode) {
let tempParamObj = this.state.paramsObject; 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 });
};
tempParamObj.status = chartValue; handlePopoverVisibleChange = visible => {
this.setState({ visible });
};
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 }); 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() { 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 ( return (
<div> <div>
<PageHeader style={{ paddingTop: 0 }}> <PageHeader style={{ paddingTop: 0 }}>
@ -75,45 +185,63 @@ class DeviceStatusReport extends React.Component {
<Breadcrumb.Item>Report</Breadcrumb.Item> <Breadcrumb.Item>Report</Breadcrumb.Item>
</Breadcrumb> </Breadcrumb>
<div className="wrap" style={{ marginBottom: '10px' }}> <div className="wrap" style={{ marginBottom: '10px' }}>
<h3>Summary of enrollments</h3> <h3>Device Status Report</h3>
<div style={{ marginBottom: '10px' }}>
<Select <Radio.Group
defaultValue="android" onChange={this.handleDurationModeChange}
style={{ width: 120, marginRight: 10 }} defaultValue={'weekly'}
> value={durationMode}
<Option value="android">Android</Option> style={{ marginBottom: 8, marginRight: 5 }}
<Option value="ios">IOS</Option> >
<Option value="windows">Windows</Option> <Radio.Button value={'daily'}>Today</Radio.Button>
</Select> <Radio.Button value={'weekly'}>Last Week</Radio.Button>
<Button <Radio.Button value={'monthly'}>Last Month</Radio.Button>
onClick={this.onSubmitReport} </Radio.Group>
style={{ marginLeft: 10 }}
type="primary" <Popover
> trigger="hover"
Generate Report content={
</Button> <div>
</div> <DateRangePicker
</div> updateDurationValue={this.updateDurationValue}
/>
</div>
}
visible={this.state.visible}
onVisibleChange={this.handlePopoverVisibleChange}
>
<Button style={{ marginRight: 10 }}>Custom Date</Button>
</Popover>
<div> <div
<Card
bordered={true}
hoverable={true}
style={{ style={{
backgroundColor: '#ffffff',
borderRadius: 5, borderRadius: 5,
marginBottom: 10, marginTop: 10,
height: window.innerHeight * 0.5,
}} }}
> >
<PieChart <Chart height={400} data={dv} forceFit>
onClickPieChart={this.onClickPieChart} <Axis name="Time" />
reportData={reportData} <Axis name="Number of Devices" />
/> <Legend />
</Card> <Tooltip
</div> crosshairs={{
type: 'y',
<div style={{ backgroundColor: '#ffffff', borderRadius: 5 }}> }}
<ReportDeviceTable paramsObject={params} /> />
<Geom
type="interval"
position="Time*Number of Devices"
color={'name'}
adjust={[
{
type: 'dodge',
marginRatio: 1 / 32,
},
]}
/>
</Chart>
</div>
</div> </div>
</PageHeader> </PageHeader>
<div <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, * Entgra (pvt) Ltd. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except * Version 2.0 (the "License"); you may not use this file except
@ -17,12 +17,16 @@
*/ */
import React from 'react'; 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 { Link } from 'react-router-dom';
import ReportDeviceTable from '../../../components/Devices/ReportDevicesTable';
import PieChart from '../../../components/Reports/Widgets/PieChart';
import { withConfigContext } from '../../../context/ConfigContext'; 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 { class EnrollmentTypeReport extends React.Component {
routes; routes;
@ -30,32 +34,134 @@ class EnrollmentTypeReport extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.routes = props.routes; this.routes = props.routes;
const { reportData } = this.props.location;
this.state = { this.state = {
paramsObject: { paramsObject: {
from: reportData.duration[0], from: moment()
to: reportData.duration[1], .subtract(7, 'days')
.format('YYYY-MM-DD'),
to: moment().format('YYYY-MM-DD'),
}, },
data: [],
fields: [],
durationMode: 'weekly',
visible: false,
}; };
}
console.log(reportData.duration); componentDidMount() {
this.fetchData();
} }
onClickPieChart = value => { handleDurationModeChange = e => {
const chartValue = value.data.point.item; 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 });
};
// Get modified value from datepicker and set it to paramsObject
updateDurationValue = (modifiedFromDate, modifiedToDate) => {
let tempParamObj = this.state.paramsObject; 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() { 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 ( return (
<div> <div>
<PageHeader style={{ paddingTop: 0 }}> <PageHeader style={{ paddingTop: 0 }}>
@ -68,28 +174,62 @@ class EnrollmentTypeReport extends React.Component {
<Breadcrumb.Item>Report</Breadcrumb.Item> <Breadcrumb.Item>Report</Breadcrumb.Item>
</Breadcrumb> </Breadcrumb>
<div className="wrap" style={{ marginBottom: '10px' }}> <div className="wrap" style={{ marginBottom: '10px' }}>
<h3>Summary of enrollments</h3> <h3>Device Enrollment Type Report</h3>
</div> <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>
<div> <Popover
<Card trigger="hover"
bordered={true} content={
hoverable={true} <div>
<DateRangePicker
updateDurationValue={this.updateDurationValue}
/>
</div>
}
visible={this.state.visible}
onVisibleChange={this.handlePopoverVisibleChange}
>
<Button style={{ marginRight: 10 }}>Custom Date</Button>
</Popover>
<div
style={{ style={{
backgroundColor: '#ffffff',
borderRadius: 5, borderRadius: 5,
marginBottom: 10, marginTop: 10,
height: window.innerHeight * 0.5,
}} }}
> >
<PieChart <Chart height={400} data={dv} forceFit>
onClickPieChart={this.onClickPieChart} <Axis name="Time" />
reportData={reportData} <Axis name="Number of Devices" />
/> <Legend />
</Card> <Tooltip
</div> crosshairs={{
type: 'y',
<div style={{ backgroundColor: '#ffffff', borderRadius: 5 }}> }}
<ReportDeviceTable paramsObject={params} /> />
<Geom
type="interval"
position="Time*Number of Devices"
color={'name'}
adjust={[
{
type: 'dodge',
marginRatio: 1 / 32,
},
]}
/>
</Chart>
</div>
</div> </div>
</PageHeader> </PageHeader>
<div <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, * Entgra (pvt) Ltd. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except * Version 2.0 (the "License"); you may not use this file except
@ -17,12 +17,16 @@
*/ */
import React from 'react'; 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 { Link } from 'react-router-dom';
import ReportDeviceTable from '../../../components/Devices/ReportDevicesTable';
import PieChart from '../../../components/Reports/Widgets/PieChart';
import { withConfigContext } from '../../../context/ConfigContext'; 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 { class EnrollmentsVsUnenrollmentsReport extends React.Component {
routes; routes;
@ -30,94 +34,208 @@ class EnrollmentsVsUnenrollmentsReport extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.routes = props.routes; this.routes = props.routes;
const { reportData } = this.props.location;
this.state = { this.state = {
paramsObject: { paramsObject: {
from: reportData ? reportData.duration[0] : '2019-01-01', from: moment()
to: reportData ? reportData.duration[1] : '2019-01-01', .subtract(7, 'days')
.format('YYYY-MM-DD'),
to: moment().format('YYYY-MM-DD'),
}, },
redirect: false, data: [],
fields: [],
durationMode: 'weekly',
visible: false,
}; };
}
this.redirectToHome(); componentDidMount() {
console.log(this.state.paramsObject); this.fetchData();
} }
redirectToHome = () => { handleDurationModeChange = e => {
return <Redirect to="/entgra" />; 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 => { handlePopoverVisibleChange = visible => {
const chartValue = value.data.point.item; this.setState({ visible });
let tempParamObj = this.state.paramsObject; };
console.log(chartValue);
if (chartValue === 'Enrollments') {
tempParamObj.status = 'ACTIVE&status=INACTIVE';
} else {
tempParamObj.status = 'REMOVED';
}
// 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.setState({ paramsObject: tempParamObj });
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&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';
}
const finalData = [enrollments, unenrollments];
this.setState({
data: finalData,
fields: keys,
});
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to get device count.',
);
});
}; };
render() { render() {
const { reportData } = this.props.location; const { durationMode } = this.state;
console.log('======'); const ds = new DataSet();
console.log(reportData); const dv = ds.createView().source(this.state.data);
console.log('======'); dv.transform({
type: 'fold',
fields: this.state.fields,
key: 'Time',
value: 'Number of Devices',
});
let reportDataClone = {
params: ['ACTIVE'],
duration: ['2020-01-01', '2020-01-01'],
};
const params = { ...this.state.paramsObject };
return ( return (
<div> <div>
<div> <PageHeader style={{ paddingTop: 0 }}>
{!reportData ? ( <Breadcrumb style={{ paddingBottom: 16 }}>
<Redirect to="/entgra/reports" /> <Breadcrumb.Item>
) : ( <Link to="/entgra">
<PageHeader style={{ paddingTop: 0 }}> <Icon type="home" /> Home
<Breadcrumb style={{ paddingBottom: 16 }}> </Link>
<Breadcrumb.Item> </Breadcrumb.Item>
<Link to="/entgra"> <Breadcrumb.Item>Report</Breadcrumb.Item>
<Icon type="home" /> Home </Breadcrumb>
</Link> <div className="wrap" style={{ marginBottom: '10px' }}>
</Breadcrumb.Item> <h3>Enrollments vs Unenrollments Report</h3>
<Breadcrumb.Item>Report</Breadcrumb.Item>
</Breadcrumb> <Radio.Group
<div className="wrap" style={{ marginBottom: '10px' }}> onChange={this.handleDurationModeChange}
<h3>Summary of enrollments</h3> defaultValue={'weekly'}
</div> value={durationMode}
style={{ marginBottom: 8, marginRight: 5 }}
<div> >
<Card <Radio.Button value={'daily'}>Today</Radio.Button>
bordered={true} <Radio.Button value={'weekly'}>Last Week</Radio.Button>
hoverable={true} <Radio.Button value={'monthly'}>Last Month</Radio.Button>
style={{ </Radio.Group>
borderRadius: 5,
marginBottom: 10, <Popover
height: window.innerHeight * 0.5, trigger="hover"
}} content={
> <div>
<PieChart <DateRangePicker
onClickPieChart={this.onClickPieChart} updateDurationValue={this.updateDurationValue}
reportData={reportData ? reportData : reportDataClone}
/> />
</Card> </div>
</div> }
visible={this.state.visible}
<div style={{ backgroundColor: '#ffffff', borderRadius: 5 }}> onVisibleChange={this.handlePopoverVisibleChange}
<ReportDeviceTable paramsObject={params} /> >
</div> <Button style={{ marginRight: 10 }}>Custom Date</Button>
</PageHeader> </Popover>
)}
</div> <div
style={{
backgroundColor: '#ffffff',
borderRadius: 5,
marginTop: 10,
}}
>
<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,
},
]}
/>
</Chart>
</div>
</div>
</PageHeader>
<div
style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}
></div>
</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> <b>{data.item}</b>
</h2> </h2>
<h1>{data.count}</h1> <h1>{data.count}</h1>
{/* <p>{data.duration}</p>*/}
{/* <ReportFilterModal/>*/}
</div> </div>
</Card> </Card>
</Col> </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, duration: reportData.duration,
}; };
console.log(urlSet);
if (reportData.params[0] === 'Enrollments') { if (reportData.params[0] === 'Enrollments') {
this.getEnrollmentsVsUnenrollmentsCount(params, urlSet); this.getEnrollmentsVsUnenrollmentsCount(params, urlSet);
} else if (reportData.params[0] === 'BYOD') { } else if (reportData.params[0] === 'BYOD') {
@ -50,10 +48,6 @@ class PieChart extends React.Component {
} }
} }
clicked = () => {
console.log('Clicked...!!');
};
onChartChange = data => { onChartChange = data => {
this.props.onClickPieChart(data); this.props.onClickPieChart(data);
}; };
@ -66,8 +60,6 @@ class PieChart extends React.Component {
let { statArray } = this.state; let { statArray } = this.state;
console.log(urlSet);
const urlArray = []; const urlArray = [];
urlSet.paramsList.map(data => { urlSet.paramsList.map(data => {
@ -76,7 +68,6 @@ class PieChart extends React.Component {
from: urlSet.duration[0], from: urlSet.duration[0],
to: urlSet.duration[1], to: urlSet.duration[1],
}; };
// console.log(paramsObj)
const encodedExtraParams = Object.keys(paramsObj) const encodedExtraParams = Object.keys(paramsObj)
.map(key => key + '=' + paramsObj[key]) .map(key => key + '=' + paramsObj[key])
.join('&'); .join('&');
@ -90,8 +81,6 @@ class PieChart extends React.Component {
urlArray.push(axios.get(apiUrl, data)); urlArray.push(axios.get(apiUrl, data));
}); });
console.log(urlArray);
axios axios
.all(urlArray) .all(urlArray)
.then(res => { .then(res => {
@ -128,8 +117,6 @@ class PieChart extends React.Component {
let { statArray } = this.state; let { statArray } = this.state;
console.log(urlSet);
const urlArray = []; const urlArray = [];
urlSet.paramsList.map(data => { urlSet.paramsList.map(data => {
@ -161,8 +148,6 @@ class PieChart extends React.Component {
urlArray.push(axios.get(apiUrl, data)); urlArray.push(axios.get(apiUrl, data));
}); });
console.log(urlArray);
axios axios
.all(urlArray) .all(urlArray)
.then(res => { .then(res => {
@ -199,8 +184,6 @@ class PieChart extends React.Component {
let { statArray } = this.state; let { statArray } = this.state;
console.log(urlSet);
const urlArray = []; const urlArray = [];
urlSet.paramsList.map(data => { urlSet.paramsList.map(data => {
@ -222,8 +205,6 @@ class PieChart extends React.Component {
urlArray.push(axios.get(apiUrl, data)); urlArray.push(axios.get(apiUrl, data));
}); });
console.log(urlArray);
axios axios
.all(urlArray) .all(urlArray)
.then(res => { .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 Login from './pages/Login';
import Dashboard from './pages/Dashboard/Dashboard'; import Dashboard from './pages/Dashboard/Dashboard';
import './index.css'; import './index.css';
import Devices from './pages/Dashboard/Devices/Devices';
import Reports from './pages/Dashboard/Reports/Reports'; import Reports from './pages/Dashboard/Reports/Reports';
import Geo from './pages/Dashboard/Geo/Geo'; 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 EnrollmentsVsUnenrollmentsReport from './components/Reports/Templates/EnrollmentsVsUnenrollmentsReport';
import EnrollmentTypeReport from './components/Reports/Templates/EnrollmentTypeReport'; import EnrollmentTypeReport from './components/Reports/Templates/EnrollmentTypeReport';
import PolicyReport from './components/Reports/Templates/PolicyReport';
import DeviceStatusReport from './components/Reports/Templates/DeviceStatusReport'; import DeviceStatusReport from './components/Reports/Templates/DeviceStatusReport';
import PolicyReportHome from './pages/Dashboard/Reports/PolicyReportHome';
import ReportDurationItemList from './pages/Dashboard/Reports/ReportDurationItemList';
const routes = [ const routes = [
{ {
@ -50,16 +43,16 @@ const routes = [
exact: false, exact: false,
component: Dashboard, component: Dashboard,
routes: [ routes: [
{ // {
path: '/entgra/devices', // path: '/entgra/devices',
component: Devices, // component: Devices,
exact: true, // exact: true,
}, // },
{ // {
path: '/entgra/devices/enroll', // path: '/entgra/devices/enroll',
component: DeviceEnroll, // component: DeviceEnroll,
exact: true, // exact: true,
}, // },
{ {
path: '/entgra/geo', path: '/entgra/geo',
component: Geo, component: Geo,
@ -70,58 +63,68 @@ const routes = [
component: Reports, component: Reports,
exact: true, 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,
// exact: true,
// },
// {
// path: '/entgra/certificates',
// component: Certificates,
// exact: true,
// },
{ {
path: '/entgra/groups', path: '/entgra/reports/list',
component: Groups, component: ReportDurationItemList,
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, exact: true,
}, },
{ {
path: '/entgra/certificates', path: '/entgra/reports/enrollments',
component: Certificates, component: EnrollmentsVsUnenrollmentsReport,
exact: true, exact: true,
}, },
{ {
path: '/entgra/reportList', path: '/entgra/reports/enrollment-type',
component: ReportDurationItemList, component: EnrollmentTypeReport,
exact: true, exact: true,
}, },
{ {
path: '/entgra/enrollmentsvsunenrollments', path: '/entgra/reports/policy',
component: EnrollmentsVsUnenrollmentsReport, component: PolicyReportHome,
exact: true, exact: true,
}, },
{ {
path: '/entgra/enrollmenttype', path: '/entgra/reports/policy/compliance',
component: EnrollmentTypeReport, component: PolicyReport,
exact: true, exact: true,
}, },
{ {
path: '/entgra/devicestatus', path: '/entgra/reports/device-status',
component: DeviceStatusReport, component: DeviceStatusReport,
exact: true, 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> <Layout>
<Header style={{ background: '#fff', padding: 0 }}> <Header style={{ background: '#fff', padding: 0 }}>
<div className="logo-image"> <div className="logo-image">
<Link to="/entgra/devices"> <Link to="/entgra/reports">
<img alt="logo" src={this.logo} /> <img alt="logo" src={this.logo} />
</Link> </Link>
</div> </div>
@ -72,26 +72,26 @@ class Dashboard extends React.Component {
marginRight: 110, marginRight: 110,
}} }}
> >
<SubMenu {/* <SubMenu*/}
key="devices" {/* key="devices"*/}
title={ {/* title={*/}
<span> {/* <span>*/}
<Icon type="appstore" /> {/* <Icon type="appstore" />*/}
<span>Devices</span> {/* <span>Devices</span>*/}
</span> {/* </span>*/}
} {/* }*/}
> {/* >*/}
<Menu.Item key="devices"> {/* <Menu.Item key="devices">*/}
<Link to="/entgra/devices"> {/* <Link to="/entgra/devices">*/}
<span>View</span> {/* <span>View</span>*/}
</Link> {/* </Link>*/}
</Menu.Item> {/* </Menu.Item>*/}
<Menu.Item key="deviceEnroll"> {/* <Menu.Item key="deviceEnroll">*/}
<Link to="/entgra/devices/enroll"> {/* <Link to="/entgra/devices/enroll">*/}
<span>Enroll</span> {/* <span>Enroll</span>*/}
</Link> {/* </Link>*/}
</Menu.Item> {/* </Menu.Item>*/}
</SubMenu> {/* </SubMenu>*/}
<SubMenu <SubMenu
key="geo" key="geo"
title={ title={
@ -118,65 +118,65 @@ class Dashboard extends React.Component {
<span>Reports</span> <span>Reports</span>
</Link> </Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="groups"> {/* <Menu.Item key="groups">*/}
<Link to="/entgra/groups"> {/* <Link to="/entgra/groups">*/}
<Icon type="deployment-unit" /> {/* <Icon type="deployment-unit" />*/}
<span>Groups</span> {/* <span>Groups</span>*/}
</Link> {/* </Link>*/}
</Menu.Item> {/* </Menu.Item>*/}
<Menu.Item key="users"> {/* <Menu.Item key="users">*/}
<Link to="/entgra/users"> {/* <Link to="/entgra/users">*/}
<Icon type="user" /> {/* <Icon type="user" />*/}
<span>Users</span> {/* <span>Users</span>*/}
</Link> {/* </Link>*/}
</Menu.Item> {/* </Menu.Item>*/}
<SubMenu {/* <SubMenu*/}
key="policies" {/* key="policies"*/}
title={ {/* title={*/}
<span> {/* <span>*/}
<Icon type="audit" /> {/* <Icon type="audit" />*/}
<span>Policies</span> {/* <span>Policies</span>*/}
</span> {/* </span>*/}
} {/* }*/}
> {/* >*/}
<Menu.Item key="policiesList"> {/* <Menu.Item key="policiesList">*/}
<Link to="/entgra/policies"> {/* <Link to="/entgra/policies">*/}
<span>View</span> {/* <span>View</span>*/}
</Link> {/* </Link>*/}
</Menu.Item> {/* </Menu.Item>*/}
<Menu.Item key="addPolicy"> {/* <Menu.Item key="addPolicy">*/}
<Link to="/entgra/policy/add"> {/* <Link to="/entgra/policy/add">*/}
<span>Add New Policy</span> {/* <span>Add New Policy</span>*/}
</Link> {/* </Link>*/}
</Menu.Item> {/* </Menu.Item>*/}
</SubMenu> {/* </SubMenu>*/}
<Menu.Item key="roles"> {/* <Menu.Item key="roles">*/}
<Link to="/entgra/roles"> {/* <Link to="/entgra/roles">*/}
<Icon type="book" /> {/* <Icon type="book" />*/}
<span>Roles</span> {/* <span>Roles</span>*/}
</Link> {/* </Link>*/}
</Menu.Item> {/* </Menu.Item>*/}
<Menu.Item key="devicetypes"> {/* <Menu.Item key="devicetypes">*/}
<Link to="/entgra/devicetypes"> {/* <Link to="/entgra/devicetypes">*/}
<Icon type="desktop" /> {/* <Icon type="desktop" />*/}
<span>Device Types</span> {/* <span>Device Types</span>*/}
</Link> {/* </Link>*/}
</Menu.Item> {/* </Menu.Item>*/}
<SubMenu {/* <SubMenu*/}
key="configurations" {/* key="configurations"*/}
title={ {/* title={*/}
<span> {/* <span>*/}
<Icon type="setting" /> {/* <Icon type="setting" />*/}
<span>Configurations</span> {/* <span>Configurations</span>*/}
</span> {/* </span>*/}
} {/* }*/}
> {/* >*/}
<Menu.Item key="certificates"> {/* <Menu.Item key="certificates">*/}
<Link to="/entgra/certificates"> {/* <Link to="/entgra/certificates">*/}
<span>Certificates</span> {/* <span>Certificates</span>*/}
</Link> {/* </Link>*/}
</Menu.Item> {/* </Menu.Item>*/}
</SubMenu> {/* </SubMenu>*/}
<Menu.Item key="trigger"></Menu.Item> <Menu.Item key="trigger"></Menu.Item>
<SubMenu <SubMenu
className="profile" className="profile"
@ -192,9 +192,9 @@ class Dashboard extends React.Component {
</Menu> </Menu>
</Header> </Header>
<Content style={{ marginTop: 10 }}> <Content>
<Switch> <Switch>
<Redirect exact from="/entgra" to="/entgra/devices" /> <Redirect exact from="/entgra/devices" to="/entgra/reports" />
{this.state.routes.map(route => ( {this.state.routes.map(route => (
<RouteWithSubRoutes key={route.path} {...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 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 { Link } from 'react-router-dom';
import moment from 'moment'; import moment from 'moment';
@ -32,10 +32,11 @@ class ReportDurationItemList extends React.Component {
}; };
} }
// Array for pre defined date durations
durationItemArray = [ durationItemArray = [
{ {
name: 'Daily Report', name: 'Daily Report',
description: 'Enrollments of today', description: 'Report of today',
duration: [ duration: [
moment().format('YYYY-MM-DD'), moment().format('YYYY-MM-DD'),
moment() moment()
@ -45,7 +46,7 @@ class ReportDurationItemList extends React.Component {
}, },
{ {
name: 'Weekly Report', name: 'Weekly Report',
description: 'Enrollments of last 7 days', description: 'Report of last 7 days',
duration: [ duration: [
moment() moment()
.subtract(6, 'days') .subtract(6, 'days')
@ -57,7 +58,7 @@ class ReportDurationItemList extends React.Component {
}, },
{ {
name: 'Monthly Report', name: 'Monthly Report',
description: 'Enrollments of last month', description: 'Report of last month',
duration: [ duration: [
moment() moment()
.subtract(29, 'days') .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() { render() {
const { data } = this.props.location;
let itemStatus = this.durationItemArray.map(data => ( let itemStatus = this.durationItemArray.map(data => (
<Col key={data.name} span={6}> <Col key={data.name} span={6}>
<Link <Link
@ -175,18 +273,53 @@ class ReportDurationItemList extends React.Component {
</Link> </Link>
</Col> </Col>
)); ));
return (
<div>
<div style={{ borderRadius: 5 }}>
<Row gutter={16}>{itemStatus}</Row>
</div>
<div style={{ borderRadius: 5 }}> let cardItem = this.itemAllPolicyCompliance;
<Row gutter={16}>{itemEnrollmentsVsUnenrollments}</Row>
</div>
<div style={{ borderRadius: 5 }}> switch (data.name) {
<Row gutter={16}>{itemEnrollmentType}</Row> 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}>{cardItem}</Row>
</div>
</div>
</PageHeader>
<div
style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}
></div>
</div> </div>
</div> </div>
); );

@ -19,7 +19,7 @@
import React from 'react'; import React from 'react';
import { PageHeader, Breadcrumb, Icon } from 'antd'; import { PageHeader, Breadcrumb, Icon } from 'antd';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import ReportDurationItemList from './ReportDurationItemList'; import PolicyReportHome from './PolicyReportHome';
class Reports extends React.Component { class Reports extends React.Component {
routes; routes;
@ -70,7 +70,7 @@ class Reports extends React.Component {
</Breadcrumb> </Breadcrumb>
<div className="wrap"> <div className="wrap">
<h3>Reports</h3> <h3>Reports</h3>
<ReportDurationItemList /> <PolicyReportHome />
</div> </div>
</PageHeader> </PageHeader>
<div <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, @PathParam("status") String status,
@ApiParam(name = "deviceList", value = "The payload containing the new name of the device.", required = true) @ApiParam(name = "deviceList", value = "The payload containing the new name of the device.", required = true)
@Valid List<String> deviceList); @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, produces = MediaType.APPLICATION_JSON,
httpMethod = "GET", httpMethod = "GET",
value = "Getting Details of Registered Devices", 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", tags = "Device Management",
extensions = { extensions = {
@Extension(properties = { @Extension(properties = {
@ -161,12 +161,12 @@ public interface ReportManagementService {
@GET @GET
@Path("/devices/count") @Path("/count")
@ApiOperation( @ApiOperation(
produces = MediaType.APPLICATION_JSON, produces = MediaType.APPLICATION_JSON,
httpMethod = "GET", httpMethod = "GET",
value = "Getting Details of Registered Devices", 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", tags = "Device Management",
extensions = { extensions = {
@Extension(properties = { @Extension(properties = {
@ -230,4 +230,83 @@ public interface ReportManagementService {
value = "end date of the duration", value = "end date of the duration",
required = true) required = true)
@QueryParam("to") String toDate) throws ReportManagementException; @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.Operation;
import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManagementException; 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.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.NonComplianceData;
import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.PolicyComplianceException; import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.PolicyComplianceException;
import org.wso2.carbon.device.mgt.common.search.PropertyMap; 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.ErrorResponse;
import org.wso2.carbon.device.mgt.jaxrs.beans.OperationList; 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.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.api.DeviceManagementService;
import org.wso2.carbon.device.mgt.jaxrs.service.impl.util.InputValidationException; import org.wso2.carbon.device.mgt.jaxrs.service.impl.util.InputValidationException;
import org.wso2.carbon.device.mgt.jaxrs.service.impl.util.RequestValidationUtil; 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.PathParam;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam; import javax.ws.rs.QueryParam;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@ -1056,4 +1058,66 @@ public class DeviceManagementServiceImpl implements DeviceManagementService {
} }
return Response.status(Response.Status.OK).build(); 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 { try {
PolicyAdministratorPoint policyAdministratorPoint = policyManagementService.getPAP(); PolicyAdministratorPoint policyAdministratorPoint = policyManagementService.getPAP();
policies = policyAdministratorPoint.getPolicies(); policies = policyAdministratorPoint.getPolicies();
targetPolicies.setCount(policies.size()); if(offset == 0 && limit == 0){
filteredPolicies = FilteringUtil.getFilteredList(policies, offset, limit); return Response.status(Response.Status.OK).entity(policies).build();
targetPolicies.setList(filteredPolicies); }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) { } catch (PolicyManagementException e) {
String msg = "Error occurred while retrieving all available policies"; String msg = "Error occurred while retrieving all available policies";
log.error(msg, e); log.error(msg, e);
@ -179,7 +184,7 @@ public class PolicyManagementServiceImpl implements PolicyManagementService {
new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build(); new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build();
} }
return Response.status(Response.Status.OK).entity(targetPolicies).build();
} }
@GET @GET

@ -15,15 +15,16 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
package org.wso2.carbon.device.mgt.jaxrs.service.impl; package org.wso2.carbon.device.mgt.jaxrs.service.impl;
import com.google.gson.JsonObject;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.device.mgt.common.Device; import org.wso2.carbon.device.mgt.common.Device;
import org.wso2.carbon.device.mgt.common.PaginationRequest; import org.wso2.carbon.device.mgt.common.PaginationRequest;
import org.wso2.carbon.device.mgt.common.PaginationResult; 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.common.exceptions.ReportManagementException;
import org.wso2.carbon.device.mgt.jaxrs.beans.DeviceList; 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.ErrorResponse;
@ -103,7 +104,8 @@ public class ReportManagementServiceImpl implements ReportManagementService {
@QueryParam("to") String toDate) { @QueryParam("to") String toDate) {
int deviceCount; int deviceCount;
try { 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(); return Response.status(Response.Status.OK).entity(deviceCount).build();
} catch (ReportManagementException e) { } catch (ReportManagementException e) {
String errorMessage = "Error while retrieving device count."; String errorMessage = "Error while retrieving device count.";
@ -112,4 +114,39 @@ public class ReportManagementServiceImpl implements ReportManagementService {
new ErrorResponse.ErrorResponseBuilder().setMessage(errorMessage).build()).build(); 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; 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.PaginationRequest;
import org.wso2.carbon.device.mgt.common.PaginationResult; import org.wso2.carbon.device.mgt.common.PaginationResult;
import org.wso2.carbon.device.mgt.common.exceptions.ReportManagementException; 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) int getDevicesByDurationCount(List<String> statusList, String ownership, String fromDate, String toDate)
throws ReportManagementException; 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; package org.wso2.carbon.device.mgt.core.dao;
import org.wso2.carbon.device.mgt.common.Device; import org.wso2.carbon.device.mgt.common.*;
import org.wso2.carbon.device.mgt.common.DeviceIdentifier;
import org.wso2.carbon.device.mgt.common.EnrolmentInfo;
import org.wso2.carbon.device.mgt.common.EnrolmentInfo.Status; 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.device.details.DeviceLocationHistory;
import org.wso2.carbon.device.mgt.common.configuration.mgt.DevicePropertyInfo; import org.wso2.carbon.device.mgt.common.configuration.mgt.DevicePropertyInfo;
import org.wso2.carbon.device.mgt.common.device.details.DeviceData; 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) List<String> statusList, String ownership, String fromDate, String toDate, int tenantId)
throws DeviceManagementDAOException; 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 * Retrieve device location information
* @param deviceIdentifier Device Identifier object * @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.lang.StringUtils;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; 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.Device;
import org.wso2.carbon.device.mgt.common.PaginationRequest; import org.wso2.carbon.device.mgt.common.PaginationRequest;
import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOException; import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOException;
@ -529,7 +530,7 @@ public class GenericDeviceDAOImpl extends AbstractDeviceDAOImpl {
@Override @Override
public int getDevicesByDurationCount(List<String> statusList, String ownership, String fromDate, String toDate, int tenantId) throws DeviceManagementDAOException { public int getDevicesByDurationCount(List<String> statusList, String ownership, String fromDate, String toDate, int tenantId) throws DeviceManagementDAOException {
int deviceCount = 0; int deviceCount = 0;
boolean isStatusProvided = false; boolean isStatusProvided;
String sql = "SELECT " + String sql = "SELECT " +
"COUNT(d.ID) AS DEVICE_COUNT " + "COUNT(d.ID) AS DEVICE_COUNT " +
@ -576,6 +577,70 @@ public class GenericDeviceDAOImpl extends AbstractDeviceDAOImpl {
return deviceCount; 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) { protected boolean buildStatusQuery(List<String> statusList, StringBuilder sqlBuilder) {
if (statusList != null && !statusList.isEmpty() && !statusList.get(0).isEmpty()) { if (statusList != null && !statusList.isEmpty() && !statusList.get(0).isEmpty()) {
sqlBuilder.append(" AND e.STATUS IN("); 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.lang.StringUtils;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; 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.Device;
import org.wso2.carbon.device.mgt.common.PaginationRequest; import org.wso2.carbon.device.mgt.common.PaginationRequest;
import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOException; import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOException;
@ -527,6 +528,85 @@ public class OracleDeviceDAOImpl extends AbstractDeviceDAOImpl {
return 0; 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. * 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.lang.StringUtils;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; 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.Device;
import org.wso2.carbon.device.mgt.common.PaginationRequest; import org.wso2.carbon.device.mgt.common.PaginationRequest;
import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOException; import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOException;
@ -506,6 +507,86 @@ public class PostgreSQLDeviceDAOImpl extends AbstractDeviceDAOImpl {
return 0; 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. * 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.lang.StringUtils;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; 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.Device;
import org.wso2.carbon.device.mgt.common.PaginationRequest; import org.wso2.carbon.device.mgt.common.PaginationRequest;
import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOException; import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOException;
@ -671,6 +672,85 @@ public class SQLServerDeviceDAOImpl extends AbstractDeviceDAOImpl {
return 0; 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 @Override
public int getSubscribedDeviceCount(List<Integer> deviceIds, int tenantId, String status) public int getSubscribedDeviceCount(List<Integer> deviceIds, int tenantId, String status)
throws DeviceManagementDAOException { throws DeviceManagementDAOException {

@ -17,8 +17,10 @@
*/ */
package org.wso2.carbon.device.mgt.core.report.mgt; 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.Log;
import org.apache.commons.logging.LogFactory; 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.Device;
import org.wso2.carbon.device.mgt.common.exceptions.DeviceManagementException; import org.wso2.carbon.device.mgt.common.exceptions.DeviceManagementException;
import org.wso2.carbon.device.mgt.common.PaginationRequest; 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 org.wso2.carbon.device.mgt.core.util.DeviceManagerUtil;
import java.sql.SQLException; 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.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 * 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 { throws ReportManagementException {
try { try {
DeviceManagementDAOFactory.openConnection(); DeviceManagementDAOFactory.openConnection();
return deviceDAO.getDevicesByDurationCount(statusList, ownership, fromDate, toDate, DeviceManagementDAOUtil.getTenantId()); return deviceDAO.getDevicesByDurationCount(
statusList, ownership, fromDate, toDate, DeviceManagementDAOUtil.getTenantId());
} catch (DeviceManagementDAOException e) { } catch (DeviceManagementDAOException e) {
String msg = "Error occurred in while retrieving device count by status for " + statusList + "devices."; String msg = "Error occurred in while retrieving device count by status for " + statusList + "devices.";
log.error(msg, e); log.error(msg, e);
@ -105,4 +114,98 @@ public class ReportManagementServiceImpl implements ReportManagementService {
DeviceManagementDAOFactory.closeConnection(); 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.DeviceIdentifier;
import org.wso2.carbon.device.mgt.common.Feature; 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.Policy;
import org.wso2.carbon.device.mgt.common.policy.mgt.Profile; import org.wso2.carbon.device.mgt.common.policy.mgt.Profile;
import org.wso2.carbon.device.mgt.common.policy.mgt.ProfileFeature; import org.wso2.carbon.device.mgt.common.policy.mgt.ProfileFeature;
@ -81,4 +83,10 @@ public interface PolicyManagerService {
NonComplianceData getDeviceCompliance(DeviceIdentifier deviceIdentifier) throws PolicyComplianceException; NonComplianceData getDeviceCompliance(DeviceIdentifier deviceIdentifier) throws PolicyComplianceException;
boolean isCompliant(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.apache.commons.logging.LogFactory;
import org.wso2.carbon.device.mgt.common.DeviceIdentifier; import org.wso2.carbon.device.mgt.common.DeviceIdentifier;
import org.wso2.carbon.device.mgt.common.Feature; 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.exceptions.InvalidDeviceException;
import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManagementException; 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.Policy;
@ -248,4 +250,16 @@ public class PolicyManagerServiceImpl implements PolicyManagerService {
public boolean isCompliant(DeviceIdentifier deviceIdentifier) throws PolicyComplianceException { public boolean isCompliant(DeviceIdentifier deviceIdentifier) throws PolicyComplianceException {
return monitoringManager.isCompliant(deviceIdentifier); 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; 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.NonComplianceData;
import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.ComplianceFeature; import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.ComplianceFeature;
import org.wso2.carbon.policy.mgt.common.monitor.PolicyDeviceWrapper; import org.wso2.carbon.policy.mgt.common.monitor.PolicyDeviceWrapper;
@ -55,6 +57,10 @@ public interface MonitoringDAO {
List<NonComplianceData> getCompliance() throws MonitoringDAOException; 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; List<ComplianceFeature> getNoneComplianceFeatures(int policyComplianceStatusId) throws MonitoringDAOException;
void deleteNoneComplianceData(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.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.context.PrivilegedCarbonContext; 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.NonComplianceData;
import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.ComplianceFeature; import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.ComplianceFeature;
import org.wso2.carbon.policy.mgt.common.monitor.PolicyDeviceWrapper; 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 @Override
public List<ComplianceFeature> getNoneComplianceFeatures(int policyComplianceStatusId) throws public List<ComplianceFeature> getNoneComplianceFeatures(int policyComplianceStatusId) throws
MonitoringDAOException { 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.Device;
import org.wso2.carbon.device.mgt.common.DeviceIdentifier; 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.NonComplianceData;
import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.ComplianceFeature; import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.ComplianceFeature;
import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.PolicyComplianceException; import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.PolicyComplianceException;
@ -41,4 +43,11 @@ public interface MonitoringManager {
List<String> getDeviceTypes() throws PolicyComplianceException; 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.apache.commons.logging.LogFactory;
import org.wso2.carbon.device.mgt.common.Device; import org.wso2.carbon.device.mgt.common.Device;
import org.wso2.carbon.device.mgt.common.DeviceIdentifier; 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.DeviceManagementException;
import org.wso2.carbon.device.mgt.common.exceptions.InvalidDeviceException; 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.Operation;
import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManagementException; 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.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.ComplianceFeature;
import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.NonComplianceData; 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.policy.mgt.monitor.PolicyComplianceException;
@ -371,6 +374,52 @@ public class MonitoringManagerImpl implements MonitoringManager {
return deviceTypes; 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) private void addMonitoringOperationsToDatabase(List<Device> devices)
throws PolicyComplianceException, OperationManagementException, InvalidDeviceException { throws PolicyComplianceException, OperationManagementException, InvalidDeviceException {

Loading…
Cancel
Save