From 39e3e7fa1d7f00a81e3a9c27c7204beccb922a47 Mon Sep 17 00:00:00 2001 From: Shamalka Navod Date: Mon, 27 Jan 2020 16:56:47 +0000 Subject: [PATCH] Implement reports for device enrollment and policy compliance --- .../react-app/src/App.js | 2 +- .../src/components/Reports/DateRangePicker.js | 43 ++- .../Reports/Templates/DeviceStatusReport.js | 244 +++++++++--- .../Reports/Templates/EnrollmentTypeReport.js | 204 ++++++++-- .../EnrollmentsVsUnenrollmentsReport.js | 266 +++++++++---- .../Reports/Templates/PolicyReport.js | 189 ++++++++++ .../components/Reports/Widgets/CountWidget.js | 2 - .../Reports/Widgets/FeatureListModal.js | 128 +++++++ .../components/Reports/Widgets/PieChart.js | 19 - .../Reports/Widgets/PolicyDevicesTable.js | 352 ++++++++++++++++++ .../Reports/Widgets/SelectPolicyDropDown.js | 111 ++++++ .../react-app/src/index.js | 115 +++--- .../react-app/src/js/Utils.js | 45 +++ .../src/pages/Dashboard/Dashboard.js | 164 ++++---- .../Dashboard/Reports/PolicyReportHome.js | 156 ++++++++ .../Reports/ReportDurationItemList.js | 161 +++++++- .../src/pages/Dashboard/Reports/Reports.js | 4 +- .../mgt/jaxrs/beans/ComplianceDeviceList.java | 50 +++ .../service/api/DeviceManagementService.java | 106 ++++++ .../service/api/ReportManagementService.java | 87 ++++- .../impl/DeviceManagementServiceImpl.java | 68 +++- .../impl/PolicyManagementServiceImpl.java | 13 +- .../impl/ReportManagementServiceImpl.java | 41 +- .../wso2/carbon/device/mgt/common/Count.java | 45 +++ .../policy/mgt/monitor/ComplianceData.java | 186 +++++++++ .../report/mgt/ReportManagementService.java | 4 + .../carbon/device/mgt/core/dao/DeviceDAO.java | 22 +- .../dao/impl/device/GenericDeviceDAOImpl.java | 67 +++- .../dao/impl/device/OracleDeviceDAOImpl.java | 82 +++- .../impl/device/PostgreSQLDeviceDAOImpl.java | 85 ++++- .../impl/device/SQLServerDeviceDAOImpl.java | 80 ++++ .../mgt/ReportManagementServiceImpl.java | 105 +++++- .../policy/mgt/core/PolicyManagerService.java | 8 + .../mgt/core/PolicyManagerServiceImpl.java | 14 + .../policy/mgt/core/dao/MonitoringDAO.java | 6 + .../mgt/core/dao/impl/MonitoringDAOImpl.java | 93 +++++ .../mgt/core/mgt/MonitoringManager.java | 9 + .../core/mgt/impl/MonitoringManagerImpl.java | 49 +++ 38 files changed, 3042 insertions(+), 383 deletions(-) create mode 100644 components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Templates/PolicyReport.js create mode 100644 components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Widgets/FeatureListModal.js create mode 100644 components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Widgets/PolicyDevicesTable.js create mode 100644 components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Widgets/SelectPolicyDropDown.js create mode 100644 components/device-mgt/io.entgra.device.mgt.ui/react-app/src/js/Utils.js create mode 100644 components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/Dashboard/Reports/PolicyReportHome.js create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/ComplianceDeviceList.java create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/Count.java create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/policy/mgt/monitor/ComplianceData.java diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/App.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/App.js index 5c6976f378..fb01bb034c 100644 --- a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/App.js +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/App.js @@ -148,7 +148,7 @@ class App extends React.Component {
- + {this.props.routes.map(route => ( ))} diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/DateRangePicker.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/DateRangePicker.js index b805ecf46e..763b659727 100644 --- a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/DateRangePicker.js +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/DateRangePicker.js @@ -33,28 +33,27 @@ class DateRangePicker extends React.Component { render() { const { RangePicker } = DatePicker; return ( - +
+ +
); } } diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Templates/DeviceStatusReport.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Templates/DeviceStatusReport.js index 4640d9c8f2..71ce345f85 100644 --- a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Templates/DeviceStatusReport.js +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Templates/DeviceStatusReport.js @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved. + * Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved. * * Entgra (pvt) Ltd. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -17,14 +17,16 @@ */ import React from 'react'; -import { PageHeader, Breadcrumb, Icon, Select, Button, Card } from 'antd'; +import { PageHeader, Breadcrumb, Icon, Radio, Popover, Button } from 'antd'; import { Link } from 'react-router-dom'; -import ReportDeviceTable from '../../../components/Devices/ReportDevicesTable'; -import PieChart from '../../../components/Reports/Widgets/PieChart'; import { withConfigContext } from '../../../context/ConfigContext'; - -const { Option } = Select; +import axios from 'axios'; +import DateRangePicker from '../DateRangePicker'; +import moment from 'moment'; +import { Chart, Geom, Axis, Tooltip, Legend } from 'bizcharts'; +import DataSet from '@antv/data-set'; +import { handleApiError } from '../../../js/Utils'; class DeviceStatusReport extends React.Component { routes; @@ -32,37 +34,145 @@ class DeviceStatusReport extends React.Component { constructor(props) { super(props); this.routes = props.routes; - const { reportData } = this.props.location; this.state = { - selectedTags: ['Enrolled'], paramsObject: { - from: reportData.duration[0], - to: reportData.duration[1], + from: moment() + .subtract(7, 'days') + .format('YYYY-MM-DD'), + to: moment().format('YYYY-MM-DD'), }, - statsObject: {}, - statArray: [ - { item: 'ACTIVE', count: 0 }, - { item: 'INACTIVE', count: 0 }, - { item: 'REMOVED', count: 0 }, - ], + data: [], + fields: [], + durationMode: 'weekly', + visible: false, }; } - onClickPieChart = value => { - console.log(value.data.point.item); - const chartValue = value.data.point.item; - let tempParamObj = this.state.paramsObject; + handleDurationModeChange = e => { + const durationMode = e.target.value; + switch (durationMode) { + case 'daily': + this.updateDurationValue( + moment().format('YYYY-MM-DD'), + moment() + .add(1, 'days') + .format('YYYY-MM-DD'), + ); + break; + case 'weekly': + this.updateDurationValue( + moment() + .subtract(7, 'days') + .format('YYYY-MM-DD'), + moment().format('YYYY-MM-DD'), + ); + break; + case 'monthly': + this.updateDurationValue( + moment() + .subtract(30, 'days') + .format('YYYY-MM-DD'), + moment().format('YYYY-MM-DD'), + ); + break; + } + this.setState({ durationMode }); + }; - 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 }); - console.log(this.state.paramsObject); + this.fetchData(); + }; + + // Call count APIs and get count for given parameters, then create data object to build pie chart + fetchData = () => { + this.setState({ loading: true }); + + const { paramsObject } = this.state; + const config = this.props.context; + + const encodedExtraParams = Object.keys(paramsObject) + .map(key => key + '=' + paramsObject[key]) + .join('&'); + + axios + .all([ + axios.get( + window.location.origin + + config.serverConfig.invoker.uri + + config.serverConfig.invoker.deviceMgt + + '/reports/count?status=ACTIVE&' + + encodedExtraParams, + 'Active', + ), + axios.get( + window.location.origin + + config.serverConfig.invoker.uri + + config.serverConfig.invoker.deviceMgt + + '/reports/count?status=INACTIVE&' + + encodedExtraParams, + 'Inactive', + ), + axios.get( + window.location.origin + + config.serverConfig.invoker.uri + + config.serverConfig.invoker.deviceMgt + + '/reports/count?status=REMOVED&' + + encodedExtraParams, + 'Removed', + ), + ]) + .then(res => { + let keys = Object.keys(res[0].data.data); + let active = res[0].data.data; + let inactive = res[1].data.data; + let removed = res[2].data.data; + + if (Object.keys(active).length != 0) { + active.name = 'Active'; + inactive.name = 'Inactive'; + removed.name = 'Removed'; + } + + const finalData = [active, inactive, removed]; + + this.setState({ + data: finalData, + fields: keys, + }); + }) + .catch(error => { + handleApiError( + error, + 'Error occurred while trying to get device count.', + ); + }); }; render() { - const { reportData } = this.props.location; + const { durationMode } = this.state; + + const ds = new DataSet(); + const dv = ds.createView().source(this.state.data); + dv.transform({ + type: 'fold', + fields: this.state.fields, + key: 'Time', + value: 'Number of Devices', + }); - const params = { ...this.state.paramsObject }; return (
@@ -75,45 +185,63 @@ class DeviceStatusReport extends React.Component { Report
-

Summary of enrollments

-
- - -
-
+

Device Status Report

+ + + Today + Last Week + Last Month + + + + +
+ } + visible={this.state.visible} + onVisibleChange={this.handlePopoverVisibleChange} + > + + -
- - - -
- -
- + + + + + + + +
{ - const chartValue = value.data.point.item; + handleDurationModeChange = e => { + const durationMode = e.target.value; + switch (durationMode) { + case 'daily': + this.updateDurationValue( + moment().format('YYYY-MM-DD'), + moment() + .add(1, 'days') + .format('YYYY-MM-DD'), + ); + break; + case 'weekly': + this.updateDurationValue( + moment() + .subtract(7, 'days') + .format('YYYY-MM-DD'), + moment().format('YYYY-MM-DD'), + ); + break; + case 'monthly': + this.updateDurationValue( + moment() + .subtract(30, 'days') + .format('YYYY-MM-DD'), + moment().format('YYYY-MM-DD'), + ); + break; + } + this.setState({ durationMode }); + }; + + handlePopoverVisibleChange = visible => { + this.setState({ visible }); + }; + + // Get modified value from datepicker and set it to paramsObject + updateDurationValue = (modifiedFromDate, modifiedToDate) => { let tempParamObj = this.state.paramsObject; + tempParamObj.from = modifiedFromDate; + tempParamObj.to = modifiedToDate; + this.setState({ paramsObject: tempParamObj }); + this.fetchData(); + }; - console.log(chartValue); + // Call count APIs and get count for given parameters, then create data object to build pie chart + fetchData = () => { + this.setState({ loading: true }); - tempParamObj.ownership = chartValue; + const { paramsObject } = this.state; + const config = this.props.context; - this.setState({ paramsObject: tempParamObj }); + const encodedExtraParams = Object.keys(paramsObject) + .map(key => key + '=' + paramsObject[key]) + .join('&'); + + axios + .all([ + axios.get( + window.location.origin + + config.serverConfig.invoker.uri + + config.serverConfig.invoker.deviceMgt + + '/reports/count?ownership=BYOD&' + + encodedExtraParams, + 'BYOD', + ), + axios.get( + window.location.origin + + config.serverConfig.invoker.uri + + config.serverConfig.invoker.deviceMgt + + '/reports/count?ownership=COPE&' + + encodedExtraParams, + 'COPE', + ), + ]) + .then(res => { + let keys = Object.keys(res[0].data.data); + let byod = res[0].data.data; + let cope = res[1].data.data; + if (Object.keys(byod).length != 0) { + byod.name = 'BYOD'; + cope.name = 'COPE'; + } + + const finalData = [byod, cope]; + + this.setState({ + data: finalData, + fields: keys, + }); + }) + .catch(error => { + handleApiError( + error, + 'Error occurred while trying to get device count.', + ); + }); }; render() { - const { reportData } = this.props.location; + const { durationMode } = this.state; + + const ds = new DataSet(); + const dv = ds.createView().source(this.state.data); + dv.transform({ + type: 'fold', + fields: this.state.fields, + key: 'Time', + value: 'Number of Devices', + }); - const params = { ...this.state.paramsObject }; return (
@@ -68,28 +174,62 @@ class EnrollmentTypeReport extends React.Component { Report
-

Summary of enrollments

-
+

Device Enrollment Type Report

+ + Today + Last Week + Last Month + -
- + +
+ } + visible={this.state.visible} + onVisibleChange={this.handlePopoverVisibleChange} + > + + + +
- - -
- -
- + + + + + + + +
{ - return ; + handleDurationModeChange = e => { + const durationMode = e.target.value; + switch (durationMode) { + case 'daily': + this.updateDurationValue( + moment().format('YYYY-MM-DD'), + moment() + .add(1, 'days') + .format('YYYY-MM-DD'), + ); + break; + case 'weekly': + this.updateDurationValue( + moment() + .subtract(7, 'days') + .format('YYYY-MM-DD'), + moment().format('YYYY-MM-DD'), + ); + break; + case 'monthly': + this.updateDurationValue( + moment() + .subtract(30, 'days') + .format('YYYY-MM-DD'), + moment().format('YYYY-MM-DD'), + ); + break; + } + this.setState({ durationMode }); }; - onClickPieChart = value => { - const chartValue = value.data.point.item; - let tempParamObj = this.state.paramsObject; - - console.log(chartValue); - - if (chartValue === 'Enrollments') { - tempParamObj.status = 'ACTIVE&status=INACTIVE'; - } else { - tempParamObj.status = 'REMOVED'; - } + handlePopoverVisibleChange = visible => { + this.setState({ visible }); + }; + // Get modified value from datepicker and set it to paramsObject + updateDurationValue = (modifiedFromDate, modifiedToDate) => { + let tempParamObj = this.state.paramsObject; + tempParamObj.from = modifiedFromDate; + tempParamObj.to = modifiedToDate; this.setState({ paramsObject: tempParamObj }); + this.fetchData(); + }; + + // 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() { - const { reportData } = this.props.location; + const { durationMode } = this.state; - console.log('======'); - console.log(reportData); - console.log('======'); + 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', + }); - let reportDataClone = { - params: ['ACTIVE'], - duration: ['2020-01-01', '2020-01-01'], - }; - - const params = { ...this.state.paramsObject }; return (
-
- {!reportData ? ( - - ) : ( - - - - - Home - - - Report - -
-

Summary of enrollments

-
- -
- - + + + + Home + + + Report + +
+

Enrollments vs Unenrollments Report

+ + + Today + Last Week + Last Month + + + + - -
- -
- -
- - )} -
+
+ } + visible={this.state.visible} + onVisibleChange={this.handlePopoverVisibleChange} + > + + + +
+ + + + + + + +
+
+ +
); } diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Templates/PolicyReport.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Templates/PolicyReport.js new file mode 100644 index 0000000000..5368318d7c --- /dev/null +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Templates/PolicyReport.js @@ -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 ( +
+ + + + + Home + + + Report + +
+

Policy Report

+ + + Policy Compliant Devices + + Policy Non-Compliant Devices + + + + + Today + Last Week + Last Month + + + +
+ } + visible={this.state.visible} + onVisibleChange={this.handlePopoverVisibleChange} + > + + + + + +
+ +
+
+ +
+
+ ); + } +} + +export default withConfigContext(PolicyReport); diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Widgets/CountWidget.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Widgets/CountWidget.js index d27a74f995..36f5597a77 100644 --- a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Widgets/CountWidget.js +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Widgets/CountWidget.js @@ -33,8 +33,6 @@ class CountWidget extends React.Component { {data.item}

{data.count}

- {/*

{data.duration}

*/} - {/* */} diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Widgets/FeatureListModal.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Widgets/FeatureListModal.js new file mode 100644 index 0000000000..5909d0b21a --- /dev/null +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Widgets/FeatureListModal.js @@ -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 => ( + + + {data.featureCode} + + + )); + + return ( +
+
+ +
+
+ + OK + , + ]} + > + + {featureList} + + +
+
+ ); + } +} + +export default withConfigContext(FeatureListModal); diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Widgets/PieChart.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Widgets/PieChart.js index 263af782c6..25c6fead00 100644 --- a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Widgets/PieChart.js +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Widgets/PieChart.js @@ -39,8 +39,6 @@ class PieChart extends React.Component { duration: reportData.duration, }; - console.log(urlSet); - if (reportData.params[0] === 'Enrollments') { this.getEnrollmentsVsUnenrollmentsCount(params, urlSet); } else if (reportData.params[0] === 'BYOD') { @@ -50,10 +48,6 @@ class PieChart extends React.Component { } } - clicked = () => { - console.log('Clicked...!!'); - }; - onChartChange = data => { this.props.onClickPieChart(data); }; @@ -66,8 +60,6 @@ class PieChart extends React.Component { let { statArray } = this.state; - console.log(urlSet); - const urlArray = []; urlSet.paramsList.map(data => { @@ -76,7 +68,6 @@ class PieChart extends React.Component { from: urlSet.duration[0], to: urlSet.duration[1], }; - // console.log(paramsObj) const encodedExtraParams = Object.keys(paramsObj) .map(key => key + '=' + paramsObj[key]) .join('&'); @@ -90,8 +81,6 @@ class PieChart extends React.Component { urlArray.push(axios.get(apiUrl, data)); }); - console.log(urlArray); - axios .all(urlArray) .then(res => { @@ -128,8 +117,6 @@ class PieChart extends React.Component { let { statArray } = this.state; - console.log(urlSet); - const urlArray = []; urlSet.paramsList.map(data => { @@ -161,8 +148,6 @@ class PieChart extends React.Component { urlArray.push(axios.get(apiUrl, data)); }); - console.log(urlArray); - axios .all(urlArray) .then(res => { @@ -199,8 +184,6 @@ class PieChart extends React.Component { let { statArray } = this.state; - console.log(urlSet); - const urlArray = []; urlSet.paramsList.map(data => { @@ -222,8 +205,6 @@ class PieChart extends React.Component { urlArray.push(axios.get(apiUrl, data)); }); - console.log(urlArray); - axios .all(urlArray) .then(res => { diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Widgets/PolicyDevicesTable.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Widgets/PolicyDevicesTable.js new file mode 100644 index 0000000000..f0a284cc76 --- /dev/null +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Widgets/PolicyDevicesTable.js @@ -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 ( + + + + ); + }, + }, + { + 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 ( + + {moment(data).fromNow()} + + ); + } + return 'Not available'; + }, + }, + { + title: 'Last Success Time', + dataIndex: 'lastSucceededTime', + key: 'lastSucceededTime', + render: data => { + if (data) { + return ( + + {moment(data).fromNow()} + + ); + } + 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 => , + }, + { + title: 'Device Details', + dataIndex: 'id', + key: 'device_details', + // eslint-disable-next-line react/display-name + render: id => ( + + ), + }, +]; + +// 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 ( + + + + ); + }, + }, + { + 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 ( + + {moment(data).fromNow()} + + ); + } + 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 => ( + + ), + }, +]; + +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 ( +
+ 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} + /> + + ); + } +} + +export default withConfigContext(PolicyDevicesTable); diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Widgets/SelectPolicyDropDown.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Widgets/SelectPolicyDropDown.js new file mode 100644 index 0000000000..2ad089269c --- /dev/null +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Widgets/SelectPolicyDropDown.js @@ -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 => ( + + {data.profile.profileName} + + )); + } + + return ( + + ); + } +} + +export default withConfigContext(SelectPolicyDropDown); diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/index.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/index.js index 066f770975..c081b3776c 100644 --- a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/index.js +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/index.js @@ -23,21 +23,14 @@ import App from './App'; import Login from './pages/Login'; import Dashboard from './pages/Dashboard/Dashboard'; import './index.css'; -import Devices from './pages/Dashboard/Devices/Devices'; import Reports from './pages/Dashboard/Reports/Reports'; import Geo from './pages/Dashboard/Geo/Geo'; -import Groups from './pages/Dashboard/Groups/Groups'; -import Users from './pages/Dashboard/Users/Users'; -import Policies from './pages/Dashboard/Policies/Policies'; -import Roles from './pages/Dashboard/Roles/Roles'; -import DeviceTypes from './pages/Dashboard/DeviceTypes/DeviceTypes'; -import DeviceEnroll from './pages/Dashboard/Devices/DeviceEnroll'; -import AddNewPolicy from './pages/Dashboard/Policies/AddNewPolicy'; -import Certificates from './pages/Dashboard/Configurations/Certificates/Certificates'; -import ReportDurationItemList from './pages/Dashboard/Reports/ReportDurationItemList'; import EnrollmentsVsUnenrollmentsReport from './components/Reports/Templates/EnrollmentsVsUnenrollmentsReport'; import EnrollmentTypeReport from './components/Reports/Templates/EnrollmentTypeReport'; +import PolicyReport from './components/Reports/Templates/PolicyReport'; import DeviceStatusReport from './components/Reports/Templates/DeviceStatusReport'; +import PolicyReportHome from './pages/Dashboard/Reports/PolicyReportHome'; +import ReportDurationItemList from './pages/Dashboard/Reports/ReportDurationItemList'; const routes = [ { @@ -50,16 +43,16 @@ const routes = [ exact: false, component: Dashboard, routes: [ - { - path: '/entgra/devices', - component: Devices, - exact: true, - }, - { - path: '/entgra/devices/enroll', - component: DeviceEnroll, - exact: true, - }, + // { + // path: '/entgra/devices', + // component: Devices, + // exact: true, + // }, + // { + // path: '/entgra/devices/enroll', + // component: DeviceEnroll, + // exact: true, + // }, { path: '/entgra/geo', component: Geo, @@ -70,58 +63,68 @@ const routes = [ component: Reports, exact: true, }, + // { + // path: '/entgra/groups', + // component: Groups, + // exact: true, + // }, + // { + // path: '/entgra/users', + // component: Users, + // exact: true, + // }, + // { + // path: '/entgra/policies', + // component: Policies, + // exact: true, + // }, + // { + // path: '/entgra/policy/add', + // component: AddNewPolicy, + // exact: true, + // }, + // { + // path: '/entgra/roles', + // component: Roles, + // exact: true, + // }, + // { + // path: '/entgra/devicetypes', + // component: DeviceTypes, + // exact: true, + // }, + // { + // path: '/entgra/certificates', + // component: Certificates, + // exact: true, + // }, { - path: '/entgra/groups', - component: Groups, - exact: true, - }, - { - path: '/entgra/users', - component: Users, - exact: true, - }, - { - path: '/entgra/policies', - component: Policies, - exact: true, - }, - { - path: '/entgra/policy/add', - component: AddNewPolicy, - exact: true, - }, - { - path: '/entgra/roles', - component: Roles, - exact: true, - }, - { - path: '/entgra/devicetypes', - component: DeviceTypes, + path: '/entgra/reports/list', + component: ReportDurationItemList, exact: true, }, { - path: '/entgra/certificates', - component: Certificates, + path: '/entgra/reports/enrollments', + component: EnrollmentsVsUnenrollmentsReport, exact: true, }, { - path: '/entgra/reportList', - component: ReportDurationItemList, + path: '/entgra/reports/enrollment-type', + component: EnrollmentTypeReport, exact: true, }, { - path: '/entgra/enrollmentsvsunenrollments', - component: EnrollmentsVsUnenrollmentsReport, + path: '/entgra/reports/policy', + component: PolicyReportHome, exact: true, }, { - path: '/entgra/enrollmenttype', - component: EnrollmentTypeReport, + path: '/entgra/reports/policy/compliance', + component: PolicyReport, exact: true, }, { - path: '/entgra/devicestatus', + path: '/entgra/reports/device-status', component: DeviceStatusReport, exact: true, }, diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/js/Utils.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/js/Utils.js new file mode 100644 index 0000000000..9d5b09e3f5 --- /dev/null +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/js/Utils.js @@ -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, + }); + } +}; diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/Dashboard/Dashboard.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/Dashboard/Dashboard.js index ec413b1c2a..da03b3126a 100644 --- a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/Dashboard/Dashboard.js +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/Dashboard/Dashboard.js @@ -59,7 +59,7 @@ class Dashboard extends React.Component {
- + logo
@@ -72,26 +72,26 @@ class Dashboard extends React.Component { marginRight: 110, }} > - - - Devices - - } - > - - - View - - - - - Enroll - - - + {/* */} + {/* */} + {/* Devices*/} + {/* */} + {/* }*/} + {/* >*/} + {/* */} + {/* */} + {/* View*/} + {/* */} + {/* */} + {/* */} + {/* */} + {/* Enroll*/} + {/* */} + {/* */} + {/* */} Reports - - - - Groups - - - - - - Users - - - - - Policies - - } - > - - - View - - - - - Add New Policy - - - - - - - Roles - - - - - - Device Types - - - - - Configurations - - } - > - - - Certificates - - - + {/* */} + {/* */} + {/* */} + {/* Groups*/} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* Users*/} + {/* */} + {/* */} + {/* */} + {/* */} + {/* Policies*/} + {/* */} + {/* }*/} + {/* >*/} + {/* */} + {/* */} + {/* View*/} + {/* */} + {/* */} + {/* */} + {/* */} + {/* Add New Policy*/} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* Roles*/} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* Device Types*/} + {/* */} + {/* */} + {/* */} + {/* */} + {/* Configurations*/} + {/* */} + {/* }*/} + {/* >*/} + {/* */} + {/* */} + {/* Certificates*/} + {/* */} + {/* */} + {/* */}
- + - + {this.state.routes.map(route => ( ))} diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/Dashboard/Reports/PolicyReportHome.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/Dashboard/Reports/PolicyReportHome.js new file mode 100644 index 0000000000..9115112dac --- /dev/null +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/Dashboard/Reports/PolicyReportHome.js @@ -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 ( +
+
+ +
+ + +
+ +

+ Policy Compliance Report +

+

Policy compliance details of all enrolled devices

+
+
+ + + + + +
+ +

+ Enrollments vs Unenrollments +

+

Details on device enrollments vs unenrollments

+
+
+ + + + + + +
+ +

+ Device Status Report +

+

Report based on device status

+
+
+ + + + + + +
+ +

+ Device Type Report +

+

Report for all device types

+
+
+ + + + + + ); + } +} + +export default PolicyReportHome; diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/Dashboard/Reports/ReportDurationItemList.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/Dashboard/Reports/ReportDurationItemList.js index 36ef714646..8fc23edef7 100644 --- a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/Dashboard/Reports/ReportDurationItemList.js +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/Dashboard/Reports/ReportDurationItemList.js @@ -17,7 +17,7 @@ */ import React from 'react'; -import { Icon, Col, Row, Card } from 'antd'; +import { Icon, Col, Row, Card, PageHeader, Breadcrumb } from 'antd'; import { Link } from 'react-router-dom'; import moment from 'moment'; @@ -32,10 +32,11 @@ class ReportDurationItemList extends React.Component { }; } + // Array for pre defined date durations durationItemArray = [ { name: 'Daily Report', - description: 'Enrollments of today', + description: 'Report of today', duration: [ moment().format('YYYY-MM-DD'), moment() @@ -45,7 +46,7 @@ class ReportDurationItemList extends React.Component { }, { name: 'Weekly Report', - description: 'Enrollments of last 7 days', + description: 'Report of last 7 days', duration: [ moment() .subtract(6, 'days') @@ -57,7 +58,7 @@ class ReportDurationItemList extends React.Component { }, { name: 'Monthly Report', - description: 'Enrollments of last month', + description: 'Report of last month', duration: [ moment() .subtract(29, 'days') @@ -69,7 +70,104 @@ class ReportDurationItemList extends React.Component { }, ]; + // Map durationItemArray and additional parameters to antd cards + mapDurationCards = data => { + return this.durationItemArray.map(item => ( + + + +
+ +

+ {item.name} +

+

{item.description}

+
+
+ + + )); + }; + + itemAllPolicyCompliance = this.durationItemArray.map(data => ( + + + +
+ +

+ {data.name} +

+

{data.description}

+
+
+ + + )); + + itemPerPolicyCompliance = this.durationItemArray.map(data => ( + + + +
+ +

+ {data.name} +

+

{data.description}

+
+
+ + + )); + render() { + const { data } = this.props.location; + let itemStatus = this.durationItemArray.map(data => ( )); - return ( -
-
- {itemStatus} -
-
- {itemEnrollmentsVsUnenrollments} -
+ let cardItem = this.itemAllPolicyCompliance; -
- {itemEnrollmentType} + switch (data.name) { + case 'all_policy_compliance_report': + cardItem = this.itemAllPolicyCompliance; + // cardItem = this.mapDurationCards({}); + break; + case 'per_policy_compliance_report': + cardItem = this.itemPerPolicyCompliance; + // cardItem = this.mapDurationCards({ + // policyId: 6, + // }); + break; + case 'enrollments_vs_unenrollments_report': + cardItem = itemEnrollmentsVsUnenrollments; + break; + case 'enrollment_status_report': + cardItem = itemStatus; + break; + case 'enrollemt_type_report': + cardItem = itemEnrollmentType; + break; + } + + return ( +
+
+ + + + + Home + + + Reports + +
+

Reports

+
+ {cardItem} +
+
+
+
); diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/Dashboard/Reports/Reports.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/Dashboard/Reports/Reports.js index 8f2eac48b6..4694c3a52f 100644 --- a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/Dashboard/Reports/Reports.js +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/Dashboard/Reports/Reports.js @@ -19,7 +19,7 @@ import React from 'react'; import { PageHeader, Breadcrumb, Icon } from 'antd'; import { Link } from 'react-router-dom'; -import ReportDurationItemList from './ReportDurationItemList'; +import PolicyReportHome from './PolicyReportHome'; class Reports extends React.Component { routes; @@ -70,7 +70,7 @@ class Reports extends React.Component {

Reports

- +
complianceData = new ArrayList<>(); + + @ApiModelProperty(value = "List of devices returned") + @JsonProperty("devices") + public List getList() { + return complianceData; + } + + public void setList(List 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(); + } +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/DeviceManagementService.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/DeviceManagementService.java index f80d3326fe..d916e54fa9 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/DeviceManagementService.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/DeviceManagementService.java @@ -1906,4 +1906,110 @@ public interface DeviceManagementService { @PathParam("status") String status, @ApiParam(name = "deviceList", value = "The payload containing the new name of the device.", required = true) @Valid List 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); } diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/ReportManagementService.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/ReportManagementService.java index 37325c58b4..60479e619f 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/ReportManagementService.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/ReportManagementService.java @@ -82,7 +82,7 @@ public interface ReportManagementService { produces = MediaType.APPLICATION_JSON, httpMethod = "GET", value = "Getting Details of Registered Devices", - notes = "Provides details of all the devices enrolled with WSO2 IoT Server.", + notes = "Provides details of all the devices enrolled with Entgra IoT Server.", tags = "Device Management", extensions = { @Extension(properties = { @@ -161,12 +161,12 @@ public interface ReportManagementService { @GET - @Path("/devices/count") + @Path("/count") @ApiOperation( produces = MediaType.APPLICATION_JSON, httpMethod = "GET", value = "Getting Details of Registered Devices", - notes = "Provides details of all the devices enrolled with WSO2 IoT Server.", + notes = "Provides details of all the devices enrolled with Entgra IoT Server.", tags = "Device Management", extensions = { @Extension(properties = { @@ -230,4 +230,83 @@ public interface ReportManagementService { value = "end date of the duration", required = true) @QueryParam("to") String toDate) throws ReportManagementException; -} \ No newline at end of file + + + @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 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; +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceManagementServiceImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceManagementServiceImpl.java index 70f61283ec..10318be4ec 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceManagementServiceImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceManagementServiceImpl.java @@ -66,6 +66,8 @@ import org.wso2.carbon.device.mgt.common.operation.mgt.Activity; import org.wso2.carbon.device.mgt.common.operation.mgt.Operation; import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManagementException; import org.wso2.carbon.device.mgt.common.policy.mgt.Policy; +import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.ComplianceData; +import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.ComplianceFeature; import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.NonComplianceData; import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.PolicyComplianceException; import org.wso2.carbon.device.mgt.common.search.PropertyMap; @@ -84,6 +86,7 @@ import org.wso2.carbon.device.mgt.jaxrs.beans.DeviceList; import org.wso2.carbon.device.mgt.jaxrs.beans.ErrorResponse; import org.wso2.carbon.device.mgt.jaxrs.beans.OperationList; import org.wso2.carbon.device.mgt.jaxrs.beans.OperationRequest; +import org.wso2.carbon.device.mgt.jaxrs.beans.ComplianceDeviceList; import org.wso2.carbon.device.mgt.jaxrs.service.api.DeviceManagementService; import org.wso2.carbon.device.mgt.jaxrs.service.impl.util.InputValidationException; import org.wso2.carbon.device.mgt.jaxrs.service.impl.util.RequestValidationUtil; @@ -106,13 +109,12 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; +import javax.ws.rs.DefaultValue; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.Date; import java.util.List; @@ -1056,4 +1058,66 @@ public class DeviceManagementServiceImpl implements DeviceManagementService { } return Response.status(Response.Status.OK).build(); } + + @GET + @Override + @Path("/compliance/{compliance-status}") + public Response getPolicyCompliance( + @PathParam("compliance-status") boolean complianceStatus, + @QueryParam("policy") String policyId, + @DefaultValue("false") + @QueryParam("pending") boolean isPending, + @QueryParam("from") String fromDate, + @QueryParam("to") String toDate, + @DefaultValue("0") + @QueryParam("offset") int offset, + @DefaultValue("10") + @QueryParam("limit") int limit) { + + PaginationRequest request = new PaginationRequest(offset, limit); + ComplianceDeviceList complianceDeviceList = new ComplianceDeviceList(); + PaginationResult paginationResult; + try { + + PolicyManagerService policyManagerService = DeviceMgtAPIUtils.getPolicyManagementService(); + paginationResult = policyManagerService.getPolicyCompliance(request, policyId, complianceStatus, isPending, fromDate, toDate); + + if (paginationResult.getData().isEmpty()) { + return Response.status(Response.Status.OK) + .entity("No policy compliance or non compliance devices are available").build(); + } else { + complianceDeviceList.setList((List) 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 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(); + } + } } diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/PolicyManagementServiceImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/PolicyManagementServiceImpl.java index 5f9f9c9063..d971a766db 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/PolicyManagementServiceImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/PolicyManagementServiceImpl.java @@ -169,9 +169,14 @@ public class PolicyManagementServiceImpl implements PolicyManagementService { try { PolicyAdministratorPoint policyAdministratorPoint = policyManagementService.getPAP(); policies = policyAdministratorPoint.getPolicies(); - targetPolicies.setCount(policies.size()); - filteredPolicies = FilteringUtil.getFilteredList(policies, offset, limit); - targetPolicies.setList(filteredPolicies); + if(offset == 0 && limit == 0){ + return Response.status(Response.Status.OK).entity(policies).build(); + }else{ + targetPolicies.setCount(policies.size()); + filteredPolicies = FilteringUtil.getFilteredList(policies, offset, limit); + targetPolicies.setList(filteredPolicies); + return Response.status(Response.Status.OK).entity(targetPolicies).build(); + } } catch (PolicyManagementException e) { String msg = "Error occurred while retrieving all available policies"; log.error(msg, e); @@ -179,7 +184,7 @@ public class PolicyManagementServiceImpl implements PolicyManagementService { new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build(); } - return Response.status(Response.Status.OK).entity(targetPolicies).build(); + } @GET diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/ReportManagementServiceImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/ReportManagementServiceImpl.java index cebd3c1cbe..d536ff83fb 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/ReportManagementServiceImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/ReportManagementServiceImpl.java @@ -15,15 +15,16 @@ * specific language governing permissions and limitations * under the License. */ + package org.wso2.carbon.device.mgt.jaxrs.service.impl; +import com.google.gson.JsonObject; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.wso2.carbon.device.mgt.common.Device; import org.wso2.carbon.device.mgt.common.PaginationRequest; import org.wso2.carbon.device.mgt.common.PaginationResult; -import org.wso2.carbon.device.mgt.common.exceptions.DeviceManagementException; import org.wso2.carbon.device.mgt.common.exceptions.ReportManagementException; import org.wso2.carbon.device.mgt.jaxrs.beans.DeviceList; import org.wso2.carbon.device.mgt.jaxrs.beans.ErrorResponse; @@ -103,7 +104,8 @@ public class ReportManagementServiceImpl implements ReportManagementService { @QueryParam("to") String toDate) { int deviceCount; try { - deviceCount = DeviceMgtAPIUtils.getReportManagementService().getDevicesByDurationCount(status, ownership, fromDate, toDate); + deviceCount = DeviceMgtAPIUtils.getReportManagementService() + .getDevicesByDurationCount(status, ownership, fromDate, toDate); return Response.status(Response.Status.OK).entity(deviceCount).build(); } catch (ReportManagementException e) { String errorMessage = "Error while retrieving device count."; @@ -112,4 +114,39 @@ public class ReportManagementServiceImpl implements ReportManagementService { new ErrorResponse.ErrorResponseBuilder().setMessage(errorMessage).build()).build(); } } + + @GET + @Path("/count") + @Override + public Response getCountOfDevicesByDuration( + @QueryParam("status") List 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(); + } + } } diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/Count.java b/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/Count.java new file mode 100644 index 0000000000..8662fe68a4 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/Count.java @@ -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; + } +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/policy/mgt/monitor/ComplianceData.java b/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/policy/mgt/monitor/ComplianceData.java new file mode 100644 index 0000000000..50bf8084b9 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/policy/mgt/monitor/ComplianceData.java @@ -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 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 getComplianceFeatures() { + return complianceFeatures; + } + + public void setComplianceFeatures(List 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; + } +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/report/mgt/ReportManagementService.java b/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/report/mgt/ReportManagementService.java index f788508f9e..8546add79b 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/report/mgt/ReportManagementService.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/report/mgt/ReportManagementService.java @@ -17,6 +17,7 @@ */ package org.wso2.carbon.device.mgt.common.report.mgt; +import com.google.gson.JsonObject; import org.wso2.carbon.device.mgt.common.PaginationRequest; import org.wso2.carbon.device.mgt.common.PaginationResult; import org.wso2.carbon.device.mgt.common.exceptions.ReportManagementException; @@ -43,4 +44,7 @@ public interface ReportManagementService { int getDevicesByDurationCount(List statusList, String ownership, String fromDate, String toDate) throws ReportManagementException; + + JsonObject getCountOfDevicesByDuration(PaginationRequest request, List statusList, String fromDate, String toDate) + throws ReportManagementException; } diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/DeviceDAO.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/DeviceDAO.java index 6ff205517f..b755a4c68e 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/DeviceDAO.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/DeviceDAO.java @@ -35,11 +35,8 @@ package org.wso2.carbon.device.mgt.core.dao; -import org.wso2.carbon.device.mgt.common.Device; -import org.wso2.carbon.device.mgt.common.DeviceIdentifier; -import org.wso2.carbon.device.mgt.common.EnrolmentInfo; +import org.wso2.carbon.device.mgt.common.*; import org.wso2.carbon.device.mgt.common.EnrolmentInfo.Status; -import org.wso2.carbon.device.mgt.common.PaginationRequest; import org.wso2.carbon.device.mgt.common.device.details.DeviceLocationHistory; import org.wso2.carbon.device.mgt.common.configuration.mgt.DevicePropertyInfo; import org.wso2.carbon.device.mgt.common.device.details.DeviceData; @@ -561,6 +558,23 @@ public interface DeviceDAO { List statusList, String ownership, String fromDate, String toDate, int tenantId) throws DeviceManagementDAOException; + /** + * This method is used to get the device count to generate the report graph within a specific time periode + * + * @param request Pagination request to get paginated result + * @param statusList Status list to filter data + * @param tenantId ID of the current tenant + * @param fromDate Start date to filter devices(YYYY-MM-DD) + * @param toDate End date to filter devices(YYYY-MM-DD) + * @return returns a list of Count objects + * @throws DeviceManagementDAOException + */ + List getCountOfDevicesByDuration(PaginationRequest request, + List statusList, + int tenantId, + String fromDate, + String toDate) throws DeviceManagementDAOException; + /** * Retrieve device location information * @param deviceIdentifier Device Identifier object diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/GenericDeviceDAOImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/GenericDeviceDAOImpl.java index e8ce9b0cca..c984d6a77e 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/GenericDeviceDAOImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/GenericDeviceDAOImpl.java @@ -21,6 +21,7 @@ package org.wso2.carbon.device.mgt.core.dao.impl.device; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.Count; import org.wso2.carbon.device.mgt.common.Device; import org.wso2.carbon.device.mgt.common.PaginationRequest; import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOException; @@ -529,7 +530,7 @@ public class GenericDeviceDAOImpl extends AbstractDeviceDAOImpl { @Override public int getDevicesByDurationCount(List statusList, String ownership, String fromDate, String toDate, int tenantId) throws DeviceManagementDAOException { int deviceCount = 0; - boolean isStatusProvided = false; + boolean isStatusProvided; String sql = "SELECT " + "COUNT(d.ID) AS DEVICE_COUNT " + @@ -576,6 +577,70 @@ public class GenericDeviceDAOImpl extends AbstractDeviceDAOImpl { return deviceCount; } + @Override + public List getCountOfDevicesByDuration(PaginationRequest request, List statusList, int tenantId, + String fromDate, String toDate) + throws DeviceManagementDAOException { + List 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 statusList, StringBuilder sqlBuilder) { if (statusList != null && !statusList.isEmpty() && !statusList.get(0).isEmpty()) { sqlBuilder.append(" AND e.STATUS IN("); diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/OracleDeviceDAOImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/OracleDeviceDAOImpl.java index 25b37095f0..d706af806d 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/OracleDeviceDAOImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/OracleDeviceDAOImpl.java @@ -21,6 +21,7 @@ package org.wso2.carbon.device.mgt.core.dao.impl.device; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.Count; import org.wso2.carbon.device.mgt.common.Device; import org.wso2.carbon.device.mgt.common.PaginationRequest; import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOException; @@ -527,6 +528,85 @@ public class OracleDeviceDAOImpl extends AbstractDeviceDAOImpl { return 0; } + @Override + public List getCountOfDevicesByDuration(PaginationRequest request, List statusList, int tenantId, + String fromDate, String toDate) + throws DeviceManagementDAOException { + List 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 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. * @@ -715,4 +795,4 @@ public class OracleDeviceDAOImpl extends AbstractDeviceDAOImpl { private Connection getConnection() throws SQLException { return DeviceManagementDAOFactory.getConnection(); } -} \ No newline at end of file +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/PostgreSQLDeviceDAOImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/PostgreSQLDeviceDAOImpl.java index f0c53ea9c0..41d5150743 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/PostgreSQLDeviceDAOImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/PostgreSQLDeviceDAOImpl.java @@ -21,6 +21,7 @@ package org.wso2.carbon.device.mgt.core.dao.impl.device; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.Count; import org.wso2.carbon.device.mgt.common.Device; import org.wso2.carbon.device.mgt.common.PaginationRequest; import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOException; @@ -506,6 +507,86 @@ public class PostgreSQLDeviceDAOImpl extends AbstractDeviceDAOImpl { return 0; } + @Override + public List getCountOfDevicesByDuration(PaginationRequest request, List statusList, int tenantId, + String fromDate, String toDate) + throws DeviceManagementDAOException { + List 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 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. * @@ -572,7 +653,7 @@ public class PostgreSQLDeviceDAOImpl extends AbstractDeviceDAOImpl { } return devices; } - + @Override public List getSubscribedDevices(int offsetValue, int limitValue, List deviceIds, int tenantId, String status) @@ -695,4 +776,4 @@ public class PostgreSQLDeviceDAOImpl extends AbstractDeviceDAOImpl { private Connection getConnection() throws SQLException { return DeviceManagementDAOFactory.getConnection(); } -} \ No newline at end of file +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/SQLServerDeviceDAOImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/SQLServerDeviceDAOImpl.java index 296d6b5086..0135c2d025 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/SQLServerDeviceDAOImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/SQLServerDeviceDAOImpl.java @@ -21,6 +21,7 @@ package org.wso2.carbon.device.mgt.core.dao.impl.device; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.Count; import org.wso2.carbon.device.mgt.common.Device; import org.wso2.carbon.device.mgt.common.PaginationRequest; import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOException; @@ -671,6 +672,85 @@ public class SQLServerDeviceDAOImpl extends AbstractDeviceDAOImpl { return 0; } + @Override + public List getCountOfDevicesByDuration(PaginationRequest request, List statusList, int tenantId, + String fromDate, String toDate) + throws DeviceManagementDAOException { + List 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 statusList, StringBuilder sqlBuilder) { + if (statusList != null && !statusList.isEmpty() && !statusList.get(0).isEmpty()) { + sqlBuilder.append(" AND e.STATUS IN("); + for (int i = 0; i < statusList.size(); i++) { + sqlBuilder.append("?"); + if (i != statusList.size() - 1) { + sqlBuilder.append(","); + } + } + sqlBuilder.append(")"); + return true; + }else { + return false; + } + } + @Override public int getSubscribedDeviceCount(List deviceIds, int tenantId, String status) throws DeviceManagementDAOException { diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/report/mgt/ReportManagementServiceImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/report/mgt/ReportManagementServiceImpl.java index a1d39e29cb..f3bbb1e666 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/report/mgt/ReportManagementServiceImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/report/mgt/ReportManagementServiceImpl.java @@ -17,8 +17,10 @@ */ package org.wso2.carbon.device.mgt.core.report.mgt; +import com.google.gson.JsonObject; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.Count; import org.wso2.carbon.device.mgt.common.Device; import org.wso2.carbon.device.mgt.common.exceptions.DeviceManagementException; import org.wso2.carbon.device.mgt.common.PaginationRequest; @@ -32,7 +34,13 @@ import org.wso2.carbon.device.mgt.core.dao.util.DeviceManagementDAOUtil; import org.wso2.carbon.device.mgt.core.util.DeviceManagerUtil; import java.sql.SQLException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; import java.util.List; +import java.util.Calendar; +import java.util.concurrent.TimeUnit; /** * This is the service class for reports which calls dao classes and its method which are used for @@ -92,7 +100,8 @@ public class ReportManagementServiceImpl implements ReportManagementService { throws ReportManagementException { try { DeviceManagementDAOFactory.openConnection(); - return deviceDAO.getDevicesByDurationCount(statusList, ownership, fromDate, toDate, DeviceManagementDAOUtil.getTenantId()); + return deviceDAO.getDevicesByDurationCount( + statusList, ownership, fromDate, toDate, DeviceManagementDAOUtil.getTenantId()); } catch (DeviceManagementDAOException e) { String msg = "Error occurred in while retrieving device count by status for " + statusList + "devices."; log.error(msg, e); @@ -105,4 +114,98 @@ public class ReportManagementServiceImpl implements ReportManagementService { DeviceManagementDAOFactory.closeConnection(); } } + + @Override + public JsonObject getCountOfDevicesByDuration(PaginationRequest request, List 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 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 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; + } } diff --git a/components/policy-mgt/org.wso2.carbon.policy.mgt.core/src/main/java/org/wso2/carbon/policy/mgt/core/PolicyManagerService.java b/components/policy-mgt/org.wso2.carbon.policy.mgt.core/src/main/java/org/wso2/carbon/policy/mgt/core/PolicyManagerService.java index 08fb6b4e5b..3e562c1e80 100644 --- a/components/policy-mgt/org.wso2.carbon.policy.mgt.core/src/main/java/org/wso2/carbon/policy/mgt/core/PolicyManagerService.java +++ b/components/policy-mgt/org.wso2.carbon.policy.mgt.core/src/main/java/org/wso2/carbon/policy/mgt/core/PolicyManagerService.java @@ -21,6 +21,8 @@ package org.wso2.carbon.policy.mgt.core; import org.wso2.carbon.device.mgt.common.DeviceIdentifier; import org.wso2.carbon.device.mgt.common.Feature; +import org.wso2.carbon.device.mgt.common.PaginationRequest; +import org.wso2.carbon.device.mgt.common.PaginationResult; import org.wso2.carbon.device.mgt.common.policy.mgt.Policy; import org.wso2.carbon.device.mgt.common.policy.mgt.Profile; import org.wso2.carbon.device.mgt.common.policy.mgt.ProfileFeature; @@ -81,4 +83,10 @@ public interface PolicyManagerService { NonComplianceData getDeviceCompliance(DeviceIdentifier deviceIdentifier) throws PolicyComplianceException; boolean isCompliant(DeviceIdentifier deviceIdentifier) throws PolicyComplianceException; + + PaginationResult getPolicyCompliance( + PaginationRequest paginationRequest, String policyId, boolean complianceStatus, boolean isPending, String fromDate, String toDate) + throws PolicyComplianceException; + + List getNoneComplianceFeatures(int complianceStatusId) throws PolicyComplianceException; } diff --git a/components/policy-mgt/org.wso2.carbon.policy.mgt.core/src/main/java/org/wso2/carbon/policy/mgt/core/PolicyManagerServiceImpl.java b/components/policy-mgt/org.wso2.carbon.policy.mgt.core/src/main/java/org/wso2/carbon/policy/mgt/core/PolicyManagerServiceImpl.java index fd3b9f019d..7e25bb213d 100644 --- a/components/policy-mgt/org.wso2.carbon.policy.mgt.core/src/main/java/org/wso2/carbon/policy/mgt/core/PolicyManagerServiceImpl.java +++ b/components/policy-mgt/org.wso2.carbon.policy.mgt.core/src/main/java/org/wso2/carbon/policy/mgt/core/PolicyManagerServiceImpl.java @@ -39,6 +39,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.wso2.carbon.device.mgt.common.DeviceIdentifier; import org.wso2.carbon.device.mgt.common.Feature; +import org.wso2.carbon.device.mgt.common.PaginationRequest; +import org.wso2.carbon.device.mgt.common.PaginationResult; import org.wso2.carbon.device.mgt.common.exceptions.InvalidDeviceException; import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManagementException; import org.wso2.carbon.device.mgt.common.policy.mgt.Policy; @@ -248,4 +250,16 @@ public class PolicyManagerServiceImpl implements PolicyManagerService { public boolean isCompliant(DeviceIdentifier deviceIdentifier) throws PolicyComplianceException { return monitoringManager.isCompliant(deviceIdentifier); } + + @Override + public PaginationResult getPolicyCompliance( + PaginationRequest paginationRequest, String policyId, boolean complianceStatus, boolean isPending, String fromDate, String toDate) + throws PolicyComplianceException { + return monitoringManager.getPolicyCompliance(paginationRequest, policyId, complianceStatus, isPending, fromDate, toDate); + } + + @Override + public List getNoneComplianceFeatures(int complianceStatusId) throws PolicyComplianceException { + return monitoringManager.getNoneComplianceFeatures(complianceStatusId); + } } \ No newline at end of file diff --git a/components/policy-mgt/org.wso2.carbon.policy.mgt.core/src/main/java/org/wso2/carbon/policy/mgt/core/dao/MonitoringDAO.java b/components/policy-mgt/org.wso2.carbon.policy.mgt.core/src/main/java/org/wso2/carbon/policy/mgt/core/dao/MonitoringDAO.java index b1a6584378..d7af966f28 100644 --- a/components/policy-mgt/org.wso2.carbon.policy.mgt.core/src/main/java/org/wso2/carbon/policy/mgt/core/dao/MonitoringDAO.java +++ b/components/policy-mgt/org.wso2.carbon.policy.mgt.core/src/main/java/org/wso2/carbon/policy/mgt/core/dao/MonitoringDAO.java @@ -19,6 +19,8 @@ package org.wso2.carbon.policy.mgt.core.dao; +import org.wso2.carbon.device.mgt.common.PaginationRequest; +import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.ComplianceData; import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.NonComplianceData; import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.ComplianceFeature; import org.wso2.carbon.policy.mgt.common.monitor.PolicyDeviceWrapper; @@ -55,6 +57,10 @@ public interface MonitoringDAO { List getCompliance() throws MonitoringDAOException; + List getAllComplianceDevices( + PaginationRequest paginationRequest, String policyId, boolean complianceStatus, boolean isPending, String fromDate, String toDate) + throws MonitoringDAOException; + List getNoneComplianceFeatures(int policyComplianceStatusId) throws MonitoringDAOException; void deleteNoneComplianceData(int policyComplianceStatusId) throws MonitoringDAOException; diff --git a/components/policy-mgt/org.wso2.carbon.policy.mgt.core/src/main/java/org/wso2/carbon/policy/mgt/core/dao/impl/MonitoringDAOImpl.java b/components/policy-mgt/org.wso2.carbon.policy.mgt.core/src/main/java/org/wso2/carbon/policy/mgt/core/dao/impl/MonitoringDAOImpl.java index 779232bfee..662d3ac349 100644 --- a/components/policy-mgt/org.wso2.carbon.policy.mgt.core/src/main/java/org/wso2/carbon/policy/mgt/core/dao/impl/MonitoringDAOImpl.java +++ b/components/policy-mgt/org.wso2.carbon.policy.mgt.core/src/main/java/org/wso2/carbon/policy/mgt/core/dao/impl/MonitoringDAOImpl.java @@ -22,6 +22,8 @@ package org.wso2.carbon.policy.mgt.core.dao.impl; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.common.PaginationRequest; +import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.ComplianceData; import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.NonComplianceData; import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.ComplianceFeature; import org.wso2.carbon.policy.mgt.common.monitor.PolicyDeviceWrapper; @@ -347,6 +349,97 @@ public class MonitoringDAOImpl implements MonitoringDAO { } } + @Override + public List getAllComplianceDevices( + PaginationRequest paginationRequest, + String policyId, + boolean complianceStatus, + boolean isPending, + String fromDate, + String toDate) + throws MonitoringDAOException { + List complianceDataList = new ArrayList<>(); + int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(); + + String query = + "SELECT " + + "DEVICE.NAME, " + + "DM_DEVICE_TYPE.NAME AS DEVICE_TYPE, " + + "ENROLLMENT.OWNER, " + + "DM_POLICY.NAME AS POLICY_NAME, " + + "POLICY.* " + + "FROM DM_POLICY_COMPLIANCE_STATUS AS POLICY, DM_DEVICE AS DEVICE, " + + "DM_ENROLMENT AS ENROLLMENT, DM_POLICY, DM_DEVICE_TYPE " + + "WHERE DEVICE.ID=POLICY.DEVICE_ID " + + "AND DEVICE.ID=ENROLLMENT.DEVICE_ID " + + "AND POLICY.POLICY_ID=DM_POLICY.ID " + + "AND DEVICE.DEVICE_TYPE_ID=DM_DEVICE_TYPE.ID " + + "AND POLICY.TENANT_ID = ? AND POLICY.STATUS = ?"; + + if (isPending) { + query = query + " AND POLICY.LAST_SUCCESS_TIME IS NULL " + + "AND POLICY.LAST_FAILED_TIME IS NULL"; + } else { + query = query + " AND (POLICY.LAST_SUCCESS_TIME IS NOT NULL " + + "OR POLICY.LAST_FAILED_TIME IS NOT NULL)"; + } + + if (policyId != null) { + query = query + " AND POLICY.POLICY_ID = ?"; + } + + if (fromDate != null && toDate != null) { + if (!complianceStatus) { + query = query + " AND POLICY.LAST_FAILED_TIME BETWEEN ? AND ?"; + } else { + query = query + " AND POLICY.LAST_SUCCESS_TIME BETWEEN ? AND ?"; + } + } + + query = query + " LIMIT ?,?"; + + try (Connection conn = this.getConnection(); + PreparedStatement stmt = conn.prepareStatement(query);) { + int paramIdx = 1; + stmt.setInt(paramIdx++, tenantId); + stmt.setBoolean(paramIdx++, complianceStatus); + if (policyId != null) { + stmt.setInt(paramIdx++, Integer.parseInt(policyId)); + } + if (fromDate != null && toDate != null) { + stmt.setString(paramIdx++, fromDate); + stmt.setString(paramIdx++, toDate); + } + stmt.setInt(paramIdx++, paginationRequest.getStartIndex()); + stmt.setInt(paramIdx, paginationRequest.getRowCount()); + + try (ResultSet resultSet = stmt.executeQuery()) { + while (resultSet.next()) { + ComplianceData complianceData = new ComplianceData(); + complianceData.setId(resultSet.getInt("ID")); + complianceData.setDeviceId(resultSet.getInt("DEVICE_ID")); + complianceData.setDeviceName(resultSet.getString("NAME")); + complianceData.setDeviceType(resultSet.getString("DEVICE_TYPE")); + complianceData.setOwner(resultSet.getString("OWNER")); + complianceData.setEnrolmentId(resultSet.getInt("ENROLMENT_ID")); + complianceData.setPolicyId(resultSet.getInt("POLICY_ID")); + complianceData.setPolicyName(resultSet.getString("POLICY_NAME")); + complianceData.setStatus(resultSet.getBoolean("STATUS")); + complianceData.setAttempts(resultSet.getInt("ATTEMPTS")); + complianceData.setLastRequestedTime(resultSet.getTimestamp("LAST_REQUESTED_TIME")); + complianceData.setLastFailedTime(resultSet.getTimestamp("LAST_FAILED_TIME")); + complianceData.setLastSucceededTime(resultSet.getTimestamp("LAST_SUCCESS_TIME")); + complianceDataList.add(complianceData); + } + } + } catch (SQLException e) { + String msg = "Unable to retrieve compliance data from database."; + log.error(msg, e); + throw new MonitoringDAOException(msg, e); + } + return complianceDataList; + } + @Override public List getNoneComplianceFeatures(int policyComplianceStatusId) throws MonitoringDAOException { diff --git a/components/policy-mgt/org.wso2.carbon.policy.mgt.core/src/main/java/org/wso2/carbon/policy/mgt/core/mgt/MonitoringManager.java b/components/policy-mgt/org.wso2.carbon.policy.mgt.core/src/main/java/org/wso2/carbon/policy/mgt/core/mgt/MonitoringManager.java index 4843689c22..b46e2612ca 100644 --- a/components/policy-mgt/org.wso2.carbon.policy.mgt.core/src/main/java/org/wso2/carbon/policy/mgt/core/mgt/MonitoringManager.java +++ b/components/policy-mgt/org.wso2.carbon.policy.mgt.core/src/main/java/org/wso2/carbon/policy/mgt/core/mgt/MonitoringManager.java @@ -21,6 +21,8 @@ package org.wso2.carbon.policy.mgt.core.mgt; import org.wso2.carbon.device.mgt.common.Device; import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.PaginationRequest; +import org.wso2.carbon.device.mgt.common.PaginationResult; import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.NonComplianceData; import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.ComplianceFeature; import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.PolicyComplianceException; @@ -41,4 +43,11 @@ public interface MonitoringManager { List getDeviceTypes() throws PolicyComplianceException; + PaginationResult getPolicyCompliance( + PaginationRequest paginationRequest, String policyId, boolean complianceStatus, boolean isPending, String fromDate, String toDate) + throws PolicyComplianceException; + + List getNoneComplianceFeatures(int complianceStatusId) + throws PolicyComplianceException; + } diff --git a/components/policy-mgt/org.wso2.carbon.policy.mgt.core/src/main/java/org/wso2/carbon/policy/mgt/core/mgt/impl/MonitoringManagerImpl.java b/components/policy-mgt/org.wso2.carbon.policy.mgt.core/src/main/java/org/wso2/carbon/policy/mgt/core/mgt/impl/MonitoringManagerImpl.java index ec9e7ef806..756fd68e07 100644 --- a/components/policy-mgt/org.wso2.carbon.policy.mgt.core/src/main/java/org/wso2/carbon/policy/mgt/core/mgt/impl/MonitoringManagerImpl.java +++ b/components/policy-mgt/org.wso2.carbon.policy.mgt.core/src/main/java/org/wso2/carbon/policy/mgt/core/mgt/impl/MonitoringManagerImpl.java @@ -22,11 +22,14 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.wso2.carbon.device.mgt.common.Device; import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.PaginationRequest; +import org.wso2.carbon.device.mgt.common.PaginationResult; import org.wso2.carbon.device.mgt.common.exceptions.DeviceManagementException; import org.wso2.carbon.device.mgt.common.exceptions.InvalidDeviceException; import org.wso2.carbon.device.mgt.common.operation.mgt.Operation; import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManagementException; import org.wso2.carbon.device.mgt.common.policy.mgt.PolicyMonitoringManager; +import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.ComplianceData; import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.ComplianceFeature; import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.NonComplianceData; import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.PolicyComplianceException; @@ -371,6 +374,52 @@ public class MonitoringManagerImpl implements MonitoringManager { return deviceTypes; } + @Override + public PaginationResult getPolicyCompliance( + PaginationRequest paginationRequest, String policyId, + boolean complianceStatus, boolean isPending, String fromDate, String toDate) + throws PolicyComplianceException { + PaginationResult paginationResult = new PaginationResult(); + try { + PolicyManagementDAOFactory.openConnection(); + List 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 getNoneComplianceFeatures(int complianceStatusId) throws PolicyComplianceException { + List 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 devices) throws PolicyComplianceException, OperationManagementException, InvalidDeviceException {