Merge branch 'master' of https://gitlab.com/entgra/carbon-device-mgt into entgra-master

feature/appm-store/pbac
Charitha Goonetilleke 5 years ago
commit f4bda90e19

@ -23,15 +23,7 @@ import App from './App';
import Login from './scenes/Login'; import Login from './scenes/Login';
import Home from './scenes/Home'; import Home from './scenes/Home';
import './index.css'; import './index.css';
import Reports from './scenes/Home/scenes/Reports';
import EnrollmentsVsUnenrollmentsReport from './scenes/Home/scenes/Reports/scenes/EnrolmentVsUnenrollments';
import EnrollmentTypeReport from './scenes/Home/scenes/Reports/scenes/EnrollmentType';
import PolicyReport from './scenes/Home/scenes/Reports/scenes/PolicyCompliance';
import DeviceStatusReport from './scenes/Home/scenes/Reports/scenes/DeviceStatus';
import AppNotInstalledDevicesReport from './scenes/Home/scenes/Reports/scenes/AppNotInstalledDevices';
import Geo from './scenes/Home/scenes/Geo'; import Geo from './scenes/Home/scenes/Geo';
import EncryptionStatus from './scenes/Home/scenes/Reports/scenes/EncryptionStatus';
import OutdatedOSversionReport from './scenes/Home/scenes/Reports/scenes/OutdatedOSVersion';
import Notifications from './scenes/Home/scenes/Notifications'; import Notifications from './scenes/Home/scenes/Notifications';
import DeviceEnroll from './scenes/Home/scenes/Devices/scenes/Enroll'; import DeviceEnroll from './scenes/Home/scenes/Devices/scenes/Enroll';
import Groups from './scenes/Home/scenes/Groups'; import Groups from './scenes/Home/scenes/Groups';
@ -71,11 +63,6 @@ const routes = [
component: Geo, component: Geo,
exact: true, exact: true,
}, },
{
path: '/entgra/reports',
component: Reports,
exact: true,
},
{ {
path: '/entgra/groups', path: '/entgra/groups',
component: Groups, component: Groups,
@ -126,41 +113,6 @@ const routes = [
component: Notifications, component: Notifications,
exact: true, exact: true,
}, },
{
path: '/entgra/reports/enrollments',
component: EnrollmentsVsUnenrollmentsReport,
exact: true,
},
{
path: '/entgra/reports/expired-devices',
component: OutdatedOSversionReport,
exact: true,
},
{
path: '/entgra/reports/enrollment-type',
component: EnrollmentTypeReport,
exact: true,
},
{
path: '/entgra/reports/policy/compliance',
component: PolicyReport,
exact: true,
},
{
path: '/entgra/reports/device-status',
component: DeviceStatusReport,
exact: true,
},
{
path: '/entgra/reports/app-not-installed',
component: AppNotInstalledDevicesReport,
exact: true,
},
{
path: '/entgra/reports/encryption-status',
component: EncryptionStatus,
exact: true,
},
], ],
}, },
]; ];

@ -22,7 +22,7 @@ import { Icon, message, notification, Table, Tag, Tooltip } from 'antd';
import TimeAgo from 'javascript-time-ago'; import TimeAgo from 'javascript-time-ago';
// Load locale-specific relative date/time formatting rules. // Load locale-specific relative date/time formatting rules.
import en from 'javascript-time-ago/locale/en'; import en from 'javascript-time-ago/locale/en';
import { withConfigContext } from '../../../../../../components/ConfigContext'; import { withConfigContext } from '../../../../components/ConfigContext';
let config = null; let config = null;

@ -58,7 +58,7 @@ class Home extends React.Component {
<Layout> <Layout>
<Header style={{ background: '#fff', padding: 0 }}> <Header style={{ background: '#fff', padding: 0 }}>
<div className="logo-image"> <div className="logo-image">
<Link to="/entgra/reports"> <Link to="/entgra">
<img alt="logo" src={this.logo} /> <img alt="logo" src={this.logo} />
</Link> </Link>
</div> </div>
@ -91,12 +91,6 @@ class Home extends React.Component {
</Link> </Link>
</Menu.Item> </Menu.Item>
</SubMenu> </SubMenu>
<Menu.Item key="reports">
<Link to="/entgra/reports">
<Icon type="bar-chart" />
<span>Reports</span>
</Link>
</Menu.Item>
<Menu.Item key="groups"> <Menu.Item key="groups">
<Link to="/entgra/groups"> <Link to="/entgra/groups">
<Icon type="deployment-unit" /> <Icon type="deployment-unit" />

@ -23,7 +23,7 @@ import TimeAgo from 'javascript-time-ago';
// Load locale-specific relative date/time formatting rules. // Load locale-specific relative date/time formatting rules.
import en from 'javascript-time-ago/locale/en'; import en from 'javascript-time-ago/locale/en';
import { withConfigContext } from '../../../../../../../../components/ConfigContext'; import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import DevicesTable from '../../../../../Reports/components/DevicesTable'; import DevicesTable from '../../../../../../components/DevicesTable';
let apiUrl; let apiUrl;

@ -46,6 +46,7 @@ const { Option } = Select;
const { TextArea } = Input; const { TextArea } = Input;
const subPanelpayloadAttributes = {}; const subPanelpayloadAttributes = {};
let formContainers = {};
class ConfigureProfile extends React.Component { class ConfigureProfile extends React.Component {
constructor(props) { constructor(props) {
@ -303,7 +304,7 @@ class ConfigureProfile extends React.Component {
onHandleContinue = (e, formname) => { onHandleContinue = (e, formname) => {
const allFields = this.props.form.getFieldsValue(); const allFields = this.props.form.getFieldsValue();
let activeFields = []; let activeFields = [];
// get currently active field list let subContentsList = {};
for (let i = 0; i < this.state.activePanelKeys.length; i++) { for (let i = 0; i < this.state.activePanelKeys.length; i++) {
Object.keys(allFields).map(key => { Object.keys(allFields).map(key => {
if (key.includes(`${this.state.activePanelKeys[i]}-`)) { if (key.includes(`${this.state.activePanelKeys[i]}-`)) {
@ -312,14 +313,16 @@ class ConfigureProfile extends React.Component {
`${this.state.activePanelKeys[i]}`, `${this.state.activePanelKeys[i]}`,
) )
) { ) {
Object.keys( Object.entries(
subPanelpayloadAttributes[this.state.activePanelKeys[i]], subPanelpayloadAttributes[this.state.activePanelKeys[i]],
).map(subPanel => { ).map(([subPanel, subContent]) => {
if (`${this.state.activePanelKeys[i]}-${subPanel}` === true) { subContentsList[subContent] = {};
if (key.includes(`-${subPanel}-`)) { if (
activeFields.push(key); allFields[`${this.state.activePanelKeys[i]}-${subPanel}`] ===
} true
} else if (!key.includes(`-${subPanel}-`)) { ) {
activeFields.push(key);
} else if (!key.includes(`-${subPanel}`)) {
activeFields.push(key); activeFields.push(key);
} }
}); });
@ -337,17 +340,67 @@ class ConfigureProfile extends React.Component {
let content = {}; let content = {};
Object.entries(values).map(([key, value]) => { Object.entries(values).map(([key, value]) => {
if (key.includes(`${this.state.activePanelKeys[i]}-`)) { if (key.includes(`${this.state.activePanelKeys[i]}-`)) {
content[ if (
key.replace(`${this.state.activePanelKeys[i]}-`, '') subPanelpayloadAttributes.hasOwnProperty(
] = value; `${this.state.activePanelKeys[i]}`,
)
) {
Object.entries(
subPanelpayloadAttributes[this.state.activePanelKeys[i]],
).map(([subPanel, contentKey]) => {
if (key.includes(`-${subPanel}-`)) {
subContentsList[contentKey][
key.replace(
`${this.state.activePanelKeys[i]}-${subPanel}-`,
'',
)
] = value;
content = { ...content, ...subContentsList };
} else {
content[
key.replace(`${this.state.activePanelKeys[i]}-`, '')
] = value;
}
});
} else if (this.state.activePanelKeys[i] in formContainers) {
formContainers[this.state.activePanelKeys[i]].forEach(
subFeature => {
if (
key.includes(
`${this.state.activePanelKeys[i]}-${subFeature}-`,
)
) {
let subFormContent = {};
subFormContent[
key.replace(
`${this.state.activePanelKeys[i]}-${subFeature}-`,
'',
)
] = value;
let feature = {
featureCode: subFeature,
deviceType: 'android',
content: subFormContent,
};
profileFeaturesList.push(feature);
}
},
);
} else {
content[
key.replace(`${this.state.activePanelKeys[i]}-`, '')
] = value;
}
} }
}); });
let feature = { if (!(this.state.activePanelKeys[i] in formContainers)) {
featureCode: this.state.activePanelKeys[i], let feature = {
deviceType: 'android', featureCode: this.state.activePanelKeys[i],
content: content, deviceType: 'android',
}; content: content,
profileFeaturesList.push(feature); };
profileFeaturesList.push(feature);
}
} }
this.props.getPolicyPayloadData(formname, profileFeaturesList); this.props.getPolicyPayloadData(formname, profileFeaturesList);
this.props.getNextStep(); this.props.getNextStep();
@ -751,6 +804,7 @@ class ConfigureProfile extends React.Component {
<TabPane tab={<span>{element.name}</span>} key={i}> <TabPane tab={<span>{element.name}</span>} key={i}>
{Object.values(element.panels).map((panel, j) => { {Object.values(element.panels).map((panel, j) => {
panel = panel.panel; panel = panel.panel;
let subForms = [];
return ( return (
<div key={j}> <div key={j}>
<Collapse <Collapse
@ -803,6 +857,8 @@ class ConfigureProfile extends React.Component {
<div> <div>
{Object.values(panel.subFormLists).map( {Object.values(panel.subFormLists).map(
(form, i) => { (form, i) => {
subForms.push(form.id);
formContainers[`${panel.panelId}`] = subForms;
return ( return (
<Form name={form.id} key={i}> <Form name={form.id} key={i}>
<Form.Item style={{ display: 'none' }}> <Form.Item style={{ display: 'none' }}>

@ -46,6 +46,7 @@ const { Option } = Select;
const { TextArea } = Input; const { TextArea } = Input;
const subPanelpayloadAttributes = {}; const subPanelpayloadAttributes = {};
let subFormContainer = {};
class ConfigureProfile extends React.Component { class ConfigureProfile extends React.Component {
constructor(props) { constructor(props) {
@ -65,22 +66,57 @@ class ConfigureProfile extends React.Component {
setProfileInfo = e => { setProfileInfo = e => {
let activePolicies = []; let activePolicies = [];
let activePolicyFields = {}; let activePolicyFields = {};
let activeSubPanels = [];
const allFields = this.props.form.getFieldsValue(); const allFields = this.props.form.getFieldsValue();
this.props.policyFeatureList.map(element => { this.props.policyFeatureList.map(element => {
activePolicies.push(element.featureCode); activePolicies.push(element.featureCode);
let featureData = JSON.parse(element.content); let featureData = JSON.parse(element.content);
Object.keys(featureData).map(key => { Object.keys(featureData).map(key => {
let regex = new RegExp(`${element.featureCode}.+${key}`, 'g'); if (element.featureCode in subPanelpayloadAttributes) {
Object.keys(allFields).map(fieldName => { Object.entries(subPanelpayloadAttributes[element.featureCode]).map(
if (fieldName.match(regex) != null) { ([panelKey, payloadAttr]) => {
activePolicyFields[fieldName] = featureData[key]; if (key === payloadAttr) {
} activeSubPanels.push(`${element.featureCode}-${panelKey}`);
}); }
},
);
let regex = new RegExp(`${element.featureCode}.+${key}`, 'g');
Object.keys(allFields).map(fieldName => {
if (fieldName.match(regex) != null) {
activePolicyFields[fieldName] = featureData[key];
}
});
} else if (element.featureCode in subFormContainer) {
let regex = new RegExp(`.+${element.featureCode}-${key}`, 'g');
Object.keys(allFields).map(fieldName => {
if (fieldName.match(regex) != null) {
activePolicyFields[fieldName] = featureData[key];
if (
!activePolicies.includes(
fieldName.replace(`-${element.featureCode}-${key}`, ''),
)
) {
activePolicies.push(
fieldName.replace(`-${element.featureCode}-${key}`, ''),
);
}
}
});
} else {
let regex = new RegExp(`${element.featureCode}.+${key}`, 'g');
Object.keys(allFields).map(fieldName => {
if (fieldName.match(regex) != null) {
activePolicyFields[fieldName] = featureData[key];
}
});
}
}); });
}); });
this.props.form.setFieldsValue(activePolicyFields); this.props.form.setFieldsValue(activePolicyFields);
this.setState({ this.setState({
activePanelKeys: activePolicies, activePanelKeys: activePolicies,
activeSubPanelKeys: activeSubPanels,
}); });
}; };
@ -795,6 +831,7 @@ class ConfigureProfile extends React.Component {
</Col> </Col>
<Col offset={8} span={1}> <Col offset={8} span={1}>
<Switch <Switch
id={`${panel.panelId}_SWITCH`}
checkedChildren="ON" checkedChildren="ON"
unCheckedChildren="OFF" unCheckedChildren="OFF"
onChange={e => onChange={e =>
@ -829,6 +866,8 @@ class ConfigureProfile extends React.Component {
<div> <div>
{Object.values(panel.subFormLists).map( {Object.values(panel.subFormLists).map(
(form, i) => { (form, i) => {
subFormContainer[`${form.id}`] =
panel.panelId;
return ( return (
<Form name={form.id} key={i}> <Form name={form.id} key={i}>
<Form.Item style={{ display: 'none' }}> <Form.Item style={{ display: 'none' }}>

@ -65,7 +65,7 @@ class BulkActionBar extends React.Component {
return ( return (
<div> <div>
<div style={{ padding: '8px' }}> <div style={{ padding: '5px' }}>
<Tooltip placement="bottom" title={'Apply Changes to Device'}> <Tooltip placement="bottom" title={'Apply Changes to Device'}>
<Popconfirm <Popconfirm
placement="topLeft" placement="topLeft"
@ -80,7 +80,9 @@ class BulkActionBar extends React.Component {
icon="check-circle" icon="check-circle"
size={'default'} size={'default'}
style={{ margin: '2px' }} style={{ margin: '2px' }}
/> >
APPLY CHANGES TO DEVICES
</Button>
</Popconfirm> </Popconfirm>
</Tooltip> </Tooltip>
</div> </div>
@ -110,7 +112,9 @@ class BulkActionBar extends React.Component {
size={'default'} size={'default'}
onClick={this.onCheckPolicyStatus} onClick={this.onCheckPolicyStatus}
style={{ margin: '2px' }} style={{ margin: '2px' }}
/> >
Remove
</Button>
</Popconfirm> </Popconfirm>
</Tooltip> </Tooltip>
<Divider type="vertical" /> <Divider type="vertical" />
@ -135,7 +139,9 @@ class BulkActionBar extends React.Component {
style={{ style={{
margin: '2px', margin: '2px',
}} }}
/> >
Publish
</Button>
</Popconfirm> </Popconfirm>
</Tooltip> </Tooltip>
<Divider type="vertical" /> <Divider type="vertical" />
@ -158,7 +164,9 @@ class BulkActionBar extends React.Component {
onClick={this.onCheckPolicyStatus} onClick={this.onCheckPolicyStatus}
size={'default'} size={'default'}
style={{ margin: '2px' }} style={{ margin: '2px' }}
/> >
Unpublish
</Button>
</Popconfirm> </Popconfirm>
</Tooltip> </Tooltip>
</div> </div>

@ -46,6 +46,7 @@ const { TextArea } = Input;
const subPanelpayloadAttributes = {}; const subPanelpayloadAttributes = {};
const fieldKeys = []; const fieldKeys = [];
let subFormContainer = {};
class PolicyInfo extends React.Component { class PolicyInfo extends React.Component {
constructor(props) { constructor(props) {
@ -55,6 +56,7 @@ class PolicyInfo extends React.Component {
data: {}, data: {},
policyFeatureList: [], policyFeatureList: [],
activePanelKeys: [], activePanelKeys: [],
activeSubPanelKeys: [],
profilePreviewKey: '', profilePreviewKey: '',
customInputDataArray: [], customInputDataArray: [],
inputTableDataSources: {}, inputTableDataSources: {},
@ -65,22 +67,57 @@ class PolicyInfo extends React.Component {
setProfileInfo = e => { setProfileInfo = e => {
let activePolicies = []; let activePolicies = [];
let activePolicyFields = {}; let activePolicyFields = {};
let activeSubPanels = [];
const allFields = this.props.form.getFieldsValue(); const allFields = this.props.form.getFieldsValue();
this.props.policyFeatureList.map(element => { this.props.policyFeatureList.map(element => {
activePolicies.push(element.featureCode); activePolicies.push(element.featureCode);
let featureData = JSON.parse(element.content); let featureData = JSON.parse(element.content);
Object.keys(featureData).map(key => { Object.keys(featureData).map(key => {
let regex = new RegExp(`${element.featureCode}.+${key}`, 'g'); if (element.featureCode in subPanelpayloadAttributes) {
Object.keys(allFields).map(fieldName => { Object.entries(subPanelpayloadAttributes[element.featureCode]).map(
if (fieldName.match(regex) != null) { ([panelKey, payloadAttr]) => {
activePolicyFields[fieldName] = featureData[key]; if (key === payloadAttr) {
} activeSubPanels.push(`${element.featureCode}-${panelKey}`);
}); }
},
);
let regex = new RegExp(`${element.featureCode}.+${key}`, 'g');
Object.keys(allFields).map(fieldName => {
if (fieldName.match(regex) != null) {
activePolicyFields[fieldName] = featureData[key];
}
});
} else if (element.featureCode in subFormContainer) {
let regex = new RegExp(`.+${element.featureCode}-${key}`, 'g');
Object.keys(allFields).map(fieldName => {
if (fieldName.match(regex) != null) {
activePolicyFields[fieldName] = featureData[key];
if (
!activePolicies.includes(
fieldName.replace(`-${element.featureCode}-${key}`, ''),
)
) {
activePolicies.push(
fieldName.replace(`-${element.featureCode}-${key}`, ''),
);
}
}
});
} else {
let regex = new RegExp(`${element.featureCode}.+${key}`, 'g');
Object.keys(allFields).map(fieldName => {
if (fieldName.match(regex) != null) {
activePolicyFields[fieldName] = featureData[key];
}
});
}
}); });
}); });
this.props.form.setFieldsValue(activePolicyFields); this.props.form.setFieldsValue(activePolicyFields);
this.setState({ this.setState({
activePanelKeys: activePolicies, activePanelKeys: activePolicies,
activeSubPanelKeys: activeSubPanels,
}); });
}; };
@ -235,7 +272,7 @@ class PolicyInfo extends React.Component {
{getFieldDecorator(`${columnData.key}${i}`, { {getFieldDecorator(`${columnData.key}${i}`, {
initialValue: columnData.others.initialDataIndex, initialValue: columnData.others.initialDataIndex,
})( })(
<Select> <Select disabled>
{columnData.others.option.map((option, i) => { {columnData.others.option.map((option, i) => {
return ( return (
<Option key={i} value={option.key}> <Option key={i} value={option.key}>
@ -285,6 +322,7 @@ class PolicyInfo extends React.Component {
onChange={e => onChange={e =>
this.handleSelectedPanel(e, item.optional.subPanel) this.handleSelectedPanel(e, item.optional.subPanel)
} }
disabled
> >
{item.optional.option.map((option, i) => { {item.optional.option.map((option, i) => {
return ( return (
@ -332,7 +370,7 @@ class PolicyInfo extends React.Component {
{getFieldDecorator(`${item.id}`, { {getFieldDecorator(`${item.id}`, {
initialValue: `${item.optional.option[0].name}`, initialValue: `${item.optional.option[0].name}`,
})( })(
<Select> <Select disabled>
{item.optional.option.map((option, i) => { {item.optional.option.map((option, i) => {
return ( return (
<Option key={i} value={option.value}> <Option key={i} value={option.value}>
@ -413,7 +451,7 @@ class PolicyInfo extends React.Component {
valuePropName: 'checked', valuePropName: 'checked',
initialValue: item.optional.ischecked, initialValue: item.optional.ischecked,
})( })(
<Checkbox> <Checkbox disabled>
<span> <span>
{item.label}&nbsp; {item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right"> <Tooltip title={item.tooltip} placement="right">
@ -459,7 +497,7 @@ class PolicyInfo extends React.Component {
valuePropName: 'checked', valuePropName: 'checked',
initialValue: item.optional.ischecked, initialValue: item.optional.ischecked,
})( })(
<Checkbox> <Checkbox disabled>
<span> <span>
{item.label}&nbsp; {item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right"> <Tooltip title={item.tooltip} placement="right">
@ -629,12 +667,20 @@ class PolicyInfo extends React.Component {
}; };
onPreview = e => { onPreview = e => {
this.setProfileInfo();
this.setState({ this.setState({
profilePreviewKey: 'profileInfo', profilePreviewKey: 'profileInfo',
isInfoPreview: true, isInfoPreview: true,
}); });
}; };
onCancelPreview = e => {
this.setState({
profilePreviewKey: 'profileInfo',
isInfoPreview: false,
});
};
render() { render() {
const { policyUIConfigurationsList } = this.props; const { policyUIConfigurationsList } = this.props;
const { getFieldDecorator } = this.props.form; const { getFieldDecorator } = this.props.form;
@ -645,16 +691,26 @@ class PolicyInfo extends React.Component {
<Title level={4}>Profile Information</Title> <Title level={4}>Profile Information</Title>
</Col> </Col>
<Col span={16}> <Col span={16}>
<Button type="link" icon="eye" onClick={this.onPreview}> <Button
type="link"
icon="eye"
onClick={this.onPreview}
style={{ display: this.state.isInfoPreview ? 'none' : 'inline' }}
>
<Text <Text
style={{ style={{
fontSize: 'small', fontSize: 'small',
display: this.state.isInfoPreview ? 'none' : 'inline',
}} }}
> >
(Click to view policy information) (Click to view policy information)
</Text> </Text>
</Button> </Button>
<Button
type="link"
icon="eye-invisible"
onClick={this.onCancelPreview}
style={{ display: this.state.isInfoPreview ? 'inline' : 'none' }}
/>
</Col> </Col>
</Row> </Row>
<Collapse <Collapse
@ -670,11 +726,7 @@ class PolicyInfo extends React.Component {
}} }}
> >
<div className="tab-container"> <div className="tab-container">
<Tabs <Tabs tabPosition={'left'} size={'large'}>
tabPosition={'left'}
size={'large'}
onChange={this.setProfileInfo}
>
{policyUIConfigurationsList.map((element, i) => { {policyUIConfigurationsList.map((element, i) => {
return ( return (
<TabPane tab={<span>{element.name}</span>} key={i}> <TabPane tab={<span>{element.name}</span>} key={i}>
@ -720,6 +772,8 @@ class PolicyInfo extends React.Component {
<div> <div>
{Object.values(panel.subFormLists).map( {Object.values(panel.subFormLists).map(
(form, i) => { (form, i) => {
subFormContainer[`${form.id}`] =
panel.panelId;
return ( return (
<Form name={form.id} key={i}> <Form name={form.id} key={i}>
<Form.Item <Form.Item

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

@ -1,258 +0,0 @@
/*
* 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, Row, Col, Card } from 'antd';
import { Link } from 'react-router-dom';
class Reports extends React.Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
this.state = {
paramsObject: {},
};
}
// Get modified value from datepicker and set it to paramsObject
updateDurationValue = (modifiedFromDate, modifiedToDate) => {
let tempParamObj = this.state.paramsObject;
tempParamObj.from = modifiedFromDate;
tempParamObj.to = modifiedToDate;
this.setState({ paramsObject: tempParamObj });
};
// Get modified value from filters and set it to paramsObject
updateFiltersValue = (modifiedValue, filterType) => {
let tempParamObj = this.state.paramsObject;
if (filterType == 'Device Status') {
tempParamObj.status = modifiedValue;
if (modifiedValue == 'ALL' && tempParamObj.status) {
delete tempParamObj.status;
}
} else {
tempParamObj.ownership = modifiedValue;
if (modifiedValue == 'ALL' && tempParamObj.ownership) {
delete tempParamObj.ownership;
}
}
this.setState({ paramsObject: tempParamObj });
};
render() {
return (
<div>
<PageHeader style={{ paddingTop: 0 }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/entgra">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Reports</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap">
<h3>Reports</h3>
<div style={{ borderRadius: 5 }}>
<Row gutter={16}>
<Col span={8}>
<Link
to={{
// Path to respective report page
pathname: '/entgra/reports/policy/compliance',
data: {
name: 'all_policy_compliance_report',
},
}}
>
<Card
bordered={true}
hoverable={true}
style={{ borderRadius: 10, marginBottom: 16 }}
>
<div align="center">
<Icon
type="desktop"
style={{ fontSize: '25px', color: '#08c' }}
/>
<h2>
<b>Policy Compliance Report</b>
</h2>
<p>Policy compliance details of all enrolled devices</p>
</div>
</Card>
</Link>
</Col>
<Col span={8}>
<Link
to={{
// Path to respective report page
pathname: '/entgra/reports/enrollments',
data: {
name: 'enrollments_vs_unenrollments_report',
},
}}
>
<Card
bordered={true}
hoverable={true}
style={{ borderRadius: 10, marginBottom: 16 }}
>
<div align="center">
<Icon
type="desktop"
style={{ fontSize: '25px', color: '#08c' }}
/>
<h2>
<b>Enrollments vs Unenrollments</b>
</h2>
<p>Details on device enrollments vs unenrollments</p>
</div>
</Card>
</Link>
</Col>
<Col span={8}>
<Link
to={{
// Path to respective report page
pathname: '/entgra/reports/device-status',
data: {
name: 'enrollment_status_report',
},
}}
>
<Card
bordered={true}
hoverable={true}
style={{ borderRadius: 10, marginBottom: 16 }}
>
<div align="center">
<Icon
type="desktop"
style={{ fontSize: '25px', color: '#08c' }}
/>
<h2>
<b>Device Status Report</b>
</h2>
<p>Report based on device status</p>
</div>
</Card>
</Link>
</Col>
<Col span={8}>
<Link
to={{
// Path to respective report page
pathname: '/entgra/reports/expired-devices',
data: {
name: 'expired_devices_report',
},
}}
>
<Card
bordered={true}
hoverable={true}
style={{ borderRadius: 10, marginBottom: 16 }}
>
<div align="center">
<Icon
type="desktop"
style={{ fontSize: '25px', color: '#08c' }}
/>
<h2>
<b>Outdated OS Version Report</b>
</h2>
<p>Report based on device OS version</p>
</div>
</Card>
</Link>
</Col>
<Col span={8}>
<Link
to={{
// Path to respective report page
pathname: '/entgra/reports/enrollment-type',
data: {
name: 'enrollemt_type_report',
},
}}
>
<Card
bordered={true}
hoverable={true}
style={{ borderRadius: 10, marginBottom: 16 }}
>
<div align="center">
<Icon
type="desktop"
style={{ fontSize: '25px', color: '#08c' }}
/>
<h2>
<b>Device Type Report</b>
</h2>
<p>Report for all device types</p>
</div>
</Card>
</Link>
</Col>
<Col span={8}>
<Link
to={{
// Path to respective report page
pathname: '/entgra/reports/app-not-installed',
data: {
name: 'app_not_installed_devices_report',
},
}}
>
<Card
bordered={true}
hoverable={true}
style={{ borderRadius: 10, marginBottom: 16 }}
>
<div align="center">
<Icon
type="desktop"
style={{ fontSize: '25px', color: '#08c' }}
/>
<h2>
<b>App NOT Installed Devices Report</b>
</h2>
<p>Report for all device types</p>
</div>
</Card>
</Link>
</Col>
</Row>
</div>
</div>
</PageHeader>
<div
style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}
></div>
</div>
);
}
}
export default Reports;

@ -1,100 +0,0 @@
/*
* 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 { message, notification, Select } from 'antd';
import axios from 'axios';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
const { Option } = Select;
class AppListDropDown extends React.Component {
constructor(props) {
super(props);
this.state = {
selectItem: [],
};
}
componentDidMount() {
this.fetchFullAppList();
}
fetchFullAppList = () => {
const config = this.props.context;
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/devices/android/applications?offset=0&limit=-1',
)
.then(res => {
if (res.status === 200) {
let selectItem;
selectItem = res.data.data.applicationList.map(data => (
<Option value={data.id} key={data.applicationIdentifier}>
{data.name.replace('%', ' ')}
</Option>
));
this.setState({ selectItem });
}
})
.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 application list.',
});
}
});
};
onChange = (value, data) => {
this.props.getAppList(data.key);
};
render() {
const { selectItem } = this.state;
return (
<div>
<Select
showSearch
style={{ width: 200 }}
placeholder="Select an app"
optionFilterProp="children"
onChange={this.onChange}
filterOption={(input, option) =>
option.props.children.toLowerCase().indexOf(input.toLowerCase()) >=
0
}
>
{selectItem}
</Select>
</div>
);
}
}
export default withConfigContext(AppListDropDown);

@ -1,118 +0,0 @@
/*
* 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 { message, notification, Select } from 'antd';
import axios from 'axios';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
const { Option } = Select;
class AppVersionDropDown extends React.Component {
constructor(props) {
super(props);
this.state = {
selectItem: [],
loading: false,
};
}
componentDidMount() {
if (this.props.packageName) {
this.fetchVersionList();
}
}
// Rerender component when parameters change
componentDidUpdate(prevProps, prevState, snapshot) {
if (prevProps.packageName !== this.props.packageName) {
this.fetchVersionList();
}
}
fetchVersionList = () => {
const config = this.props.context;
this.setState({ loading: true });
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/devices/application/' +
this.props.packageName +
'/versions',
)
.then(res => {
if (res.status === 200) {
let selectItem;
selectItem = JSON.parse(res.data.data).map(data => (
<Option value={data} key={data}>
{data}
</Option>
));
this.setState({ selectItem, loading: false });
}
})
.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 application list.',
});
}
});
};
onChange = value => {
this.props.getVersion(value);
};
render() {
const { selectItem, loading } = this.state;
return (
<div>
<Select
defaultValue={'all'}
showSearch
style={{ width: 200 }}
placeholder="Select app version"
optionFilterProp="children"
onChange={this.onChange}
loading={loading}
filterOption={(input, option) =>
option.props.children.toLowerCase().indexOf(input.toLowerCase()) >=
0
}
>
<Option value={'all'} key={'all'}>
All
</Option>
{selectItem}
</Select>
</div>
);
}
}
export default withConfigContext(AppVersionDropDown);

@ -1,136 +0,0 @@
/*
* 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, Button } from 'antd';
import { Link } from 'react-router-dom';
import { withConfigContext } from '../../../../../../components/ConfigContext';
import AppListDropDown from './components/AppListDropDown';
import DevicesTable from './../../components/DevicesTable';
import AppVersionDropDown from './components/AppVersionDropDown';
// eslint-disable-next-line no-unused-vars
let config = null;
let url;
let packageName;
let version;
class AppNotInstalledDevicesReport extends React.Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
config = this.props.context;
this.state = {
apiUrl: null,
visible: false,
packageName: null,
version: 'all',
};
}
getAppList = appPackageName => {
packageName = appPackageName;
this.setState({ packageName });
};
getVersion = appVersion => {
version = appVersion;
this.setState({ version });
};
onClickGenerateButton = () => {
const { packageName, version } = this.state;
if (version === 'all') {
url =
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/reports/devices/android/' +
packageName +
'/not-installed?';
} else {
url =
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/reports/devices/android/' +
packageName +
'/not-installed?app-version=' +
version +
'&';
}
this.setState({ apiUrl: url });
};
render() {
const { apiUrl, packageName } = this.state;
return (
<div>
<PageHeader style={{ paddingTop: 0 }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/entgra">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>
<Link to="/entgra/reports">Reports</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>App NOT Installed Devices Report</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap" style={{ marginBottom: '10px' }}>
<h3>Policy Report</h3>
<div style={{ display: 'flex', marginBottom: '10px' }}>
<div>
<AppListDropDown getAppList={this.getAppList} />
</div>
<div style={{ marginLeft: '10px' }}>
<AppVersionDropDown
getVersion={this.getVersion}
packageName={packageName}
/>
</div>
<Button
type="primary"
onClick={this.onClickGenerateButton}
style={{ marginLeft: '10px' }}
>
Generate Report
</Button>
</div>
<div style={{ backgroundColor: '#ffffff', borderRadius: 5 }}>
<DevicesTable apiUrl={apiUrl} />
</div>
</div>
</PageHeader>
<div
style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}
></div>
</div>
);
}
}
export default withConfigContext(AppNotInstalledDevicesReport);

@ -1,264 +0,0 @@
/*
* 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 '../../../../../../components/ConfigContext';
import axios from 'axios';
import DateRangePicker from '../../components/DateRangePicker';
import moment from 'moment';
import { Chart, Geom, Axis, Tooltip, Legend } from 'bizcharts';
import DataSet from '@antv/data-set';
import { handleApiError } from '../../../../../../services/utils/errorHandler';
class DeviceStatusReport extends React.Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
this.state = {
paramsObject: {
from: moment()
.subtract(6, 'days')
.format('YYYY-MM-DD'),
to: moment()
.add(1, 'days')
.format('YYYY-MM-DD'),
},
data: [],
fields: [],
durationMode: 'weekly',
visible: false,
};
}
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(6, 'days')
.format('YYYY-MM-DD'),
moment()
.add(1, 'days')
.format('YYYY-MM-DD'),
);
break;
case 'monthly':
this.updateDurationValue(
moment()
.subtract(29, 'days')
.format('YYYY-MM-DD'),
moment()
.add(1, 'days')
.format('YYYY-MM-DD'),
);
break;
}
this.setState({ durationMode });
};
handlePopoverVisibleChange = visible => {
this.setState({ visible });
};
componentDidMount() {
this.fetchData();
}
// Get modified value from datepicker and set it to paramsObject
updateDurationValue = (modifiedFromDate, modifiedToDate) => {
let tempParamObj = this.state.paramsObject;
tempParamObj.from = modifiedFromDate;
tempParamObj.to = modifiedToDate;
this.setState({ paramsObject: tempParamObj });
this.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 { 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',
});
return (
<div>
<PageHeader style={{ paddingTop: 0 }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/entgra">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>
<Link to="/entgra/reports">Reports</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Device Status Report</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap" style={{ marginBottom: '10px' }}>
<h3>Device Status Report</h3>
<Radio.Group
onChange={this.handleDurationModeChange}
defaultValue={'weekly'}
value={durationMode}
style={{ marginBottom: 8, marginRight: 5 }}
>
<Radio.Button value={'daily'}>Today</Radio.Button>
<Radio.Button value={'weekly'}>Last Week</Radio.Button>
<Radio.Button value={'monthly'}>Last Month</Radio.Button>
</Radio.Group>
<Popover
trigger="hover"
content={
<div>
<DateRangePicker
updateDurationValue={this.updateDurationValue}
/>
</div>
}
visible={this.state.visible}
onVisibleChange={this.handlePopoverVisibleChange}
>
<Button style={{ marginRight: 10 }}>Custom Date</Button>
</Popover>
<div
style={{
backgroundColor: '#ffffff',
borderRadius: 5,
marginTop: 10,
}}
>
<Chart height={400} data={dv} forceFit>
<Axis name="Time" />
<Axis name="Number of Devices" />
<Legend />
<Tooltip
crosshairs={{
type: 'y',
}}
/>
<Geom
type="interval"
position="Time*Number of Devices"
color={'name'}
adjust={[
{
type: 'dodge',
marginRatio: 1 / 32,
},
]}
/>
</Chart>
</div>
</div>
</PageHeader>
<div
style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}
></div>
</div>
);
}
}
export default withConfigContext(DeviceStatusReport);

@ -1,258 +0,0 @@
/*
* 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 { Icon, message, notification, Radio, Table, Tag, Tooltip } from 'antd';
import TimeAgo from 'javascript-time-ago';
// Load locale-specific relative date/time formatting rules.
import en from 'javascript-time-ago/locale/en';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
let config = null;
const columns = [
{
title: 'Device',
dataIndex: 'name',
},
{
title: 'Type',
dataIndex: 'type',
key: 'type',
// eslint-disable-next-line react/display-name
render: type => {
const defaultPlatformIcons = config.defaultPlatformIcons;
let icon = defaultPlatformIcons.default.icon;
let color = defaultPlatformIcons.default.color;
let theme = defaultPlatformIcons.default.theme;
if (defaultPlatformIcons.hasOwnProperty(type)) {
icon = defaultPlatformIcons[type].icon;
color = defaultPlatformIcons[type].color;
theme = defaultPlatformIcons[type].theme;
}
return (
<span style={{ fontSize: 20, color: color, textAlign: 'center' }}>
<Icon type={icon} theme={theme} />
</span>
);
},
// todo add filtering options
},
{
title: 'Owner',
dataIndex: 'enrolmentInfo',
key: 'owner',
render: enrolmentInfo => enrolmentInfo.owner,
// todo add filtering options
},
{
title: 'Ownership',
dataIndex: 'enrolmentInfo',
key: 'ownership',
render: enrolmentInfo => enrolmentInfo.ownership,
// todo add filtering options
},
{
title: 'Status',
dataIndex: 'enrolmentInfo',
key: 'status',
// eslint-disable-next-line react/display-name
render: enrolmentInfo => {
const status = enrolmentInfo.status.toLowerCase();
let color = '#f9ca24';
switch (status) {
case 'active':
color = '#badc58';
break;
case 'created':
color = '#6ab04c';
break;
case 'removed':
color = '#ff7979';
break;
case 'inactive':
color = '#f9ca24';
break;
case 'blocked':
color = '#636e72';
break;
}
return <Tag color={color}>{status}</Tag>;
},
// todo add filtering options
},
{
title: 'Last Updated',
dataIndex: 'enrolmentInfo',
key: 'dateOfLastUpdate',
// eslint-disable-next-line react/display-name
render: data => {
const { dateOfLastUpdate } = data;
const timeAgoString = getTimeAgo(dateOfLastUpdate);
return (
<Tooltip title={new Date(dateOfLastUpdate).toString()}>
{timeAgoString}
</Tooltip>
);
},
// todo add filtering options
},
];
const getTimeAgo = time => {
const timeAgo = new TimeAgo('en-US');
return timeAgo.format(time);
};
class EncryptedDeviceTable extends React.Component {
constructor(props) {
super(props);
config = this.props.context;
TimeAgo.addLocale(en);
this.state = {
data: [],
pagination: {},
loading: false,
isEncrypted: true,
};
}
componentDidMount() {
this.fetch();
}
// fetch data from api
fetch = (params = {}) => {
const config = this.props.context;
this.setState({ loading: true });
// get current page
const currentPage = params.hasOwnProperty('page') ? params.page : 1;
const extraParams = {
offset: 10 * (currentPage - 1), // calculate the offset
limit: 10,
requireDeviceInfo: true,
isEncrypted: this.state.isEncrypted,
};
const encodedExtraParams = Object.keys(extraParams)
.map(key => key + '=' + extraParams[key])
.join('&');
// send request to the invoker
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/reports/encryption-status?' +
encodedExtraParams,
)
.then(res => {
if (res.status === 200) {
const pagination = { ...this.state.pagination };
this.setState({
loading: false,
data: res.data.data.devices,
pagination,
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load devices.',
});
}
this.setState({ loading: false });
});
};
handleTableChange = (pagination, filters, sorter) => {
const pager = { ...this.state.pagination };
pager.current = pagination.current;
this.setState({
pagination: pager,
});
this.fetch({
results: pagination.pageSize,
page: pagination.current,
sortField: sorter.field,
sortOrder: sorter.order,
...filters,
});
};
handleModeChange = value => {
this.setState(
{
isEncrypted: value.target.value,
},
this.fetch,
);
};
render() {
const { data, pagination, loading } = this.state;
return (
<div>
<Radio.Group
onChange={this.handleModeChange}
defaultValue={'true'}
style={{ marginBottom: 8, marginRight: 5 }}
>
<Radio.Button value={'true'}>Enabled Devices</Radio.Button>
<Radio.Button value={'false'}>Disabled Devices</Radio.Button>
</Radio.Group>
<div style={{ backgroundColor: '#ffffff', borderRadius: 5 }}>
<Table
columns={columns}
rowKey={record =>
record.deviceIdentifier +
record.enrolmentInfo.owner +
record.enrolmentInfo.ownership
}
dataSource={data}
pagination={{
...pagination,
size: 'small',
// position: "top",
showTotal: (total, range) =>
`showing ${range[0]}-${range[1]} of ${total} devices`,
// showQuickJumper: true
}}
loading={loading}
onChange={this.handleTableChange}
/>
</div>
</div>
);
}
}
export default withConfigContext(EncryptedDeviceTable);

@ -1,59 +0,0 @@
/*
* 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 } from 'antd';
import { Link } from 'react-router-dom';
import EncryptedDeviceTable from './components/EncryptedDeviceTable/EncryptedDevicesTable';
class EncryptionStatus extends React.Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
}
render() {
return (
<div>
<PageHeader style={{ paddingTop: 0 }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/entgra/devices">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>
<Link to="/entgra/reports">Reports</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Encryption Status</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap">
<h3>Encryption Status Report</h3>
</div>
<EncryptedDeviceTable />
</PageHeader>
<div
style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}
></div>
</div>
);
}
}
export default EncryptionStatus;

@ -1,252 +0,0 @@
/*
* 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 '../../../../../../components/ConfigContext';
import axios from 'axios';
import DateRangePicker from '../../components/DateRangePicker';
import moment from 'moment';
import { Chart, Geom, Axis, Tooltip, Legend } from 'bizcharts';
import DataSet from '@antv/data-set';
import { handleApiError } from '../../../../../../services/utils/errorHandler';
class EnrollmentTypeReport extends React.Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
this.state = {
paramsObject: {
from: moment()
.subtract(6, 'days')
.format('YYYY-MM-DD'),
to: moment()
.add(1, 'days')
.format('YYYY-MM-DD'),
},
data: [],
fields: [],
durationMode: 'weekly',
visible: false,
};
}
componentDidMount() {
this.fetchData();
}
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(6, 'days')
.format('YYYY-MM-DD'),
moment()
.add(1, 'days')
.format('YYYY-MM-DD'),
);
break;
case 'monthly':
this.updateDurationValue(
moment()
.subtract(29, 'days')
.format('YYYY-MM-DD'),
moment()
.add(1, 'days')
.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();
};
// 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?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 { 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',
});
return (
<div>
<PageHeader style={{ paddingTop: 0 }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/entgra">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>
<Link to="/entgra/reports">Reports</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Device Enrollment Type Report</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap" style={{ marginBottom: '10px' }}>
<h3>Device Enrollment Type Report</h3>
<Radio.Group
onChange={this.handleDurationModeChange}
defaultValue={'weekly'}
value={durationMode}
style={{ marginBottom: 8, marginRight: 5 }}
>
<Radio.Button value={'daily'}>Today</Radio.Button>
<Radio.Button value={'weekly'}>Last Week</Radio.Button>
<Radio.Button value={'monthly'}>Last Month</Radio.Button>
</Radio.Group>
<Popover
trigger="hover"
content={
<div>
<DateRangePicker
updateDurationValue={this.updateDurationValue}
/>
</div>
}
visible={this.state.visible}
onVisibleChange={this.handlePopoverVisibleChange}
>
<Button style={{ marginRight: 10 }}>Custom Date</Button>
</Popover>
<div
style={{
backgroundColor: '#ffffff',
borderRadius: 5,
marginTop: 10,
}}
>
<Chart height={400} data={dv} forceFit>
<Axis name="Time" />
<Axis name="Number of Devices" />
<Legend />
<Tooltip
crosshairs={{
type: 'y',
}}
/>
<Geom
type="interval"
position="Time*Number of Devices"
color={'name'}
adjust={[
{
type: 'dodge',
marginRatio: 1 / 32,
},
]}
/>
</Chart>
</div>
</div>
</PageHeader>
<div
style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}
></div>
</div>
);
}
}
export default withConfigContext(EnrollmentTypeReport);

@ -1,255 +0,0 @@
/*
* 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 '../../../../../../components/ConfigContext';
import axios from 'axios';
import DateRangePicker from '../../components/DateRangePicker';
import moment from 'moment';
import { Chart, Geom, Axis, Tooltip, Legend } from 'bizcharts';
import DataSet from '@antv/data-set';
import { handleApiError } from '../../../../../../services/utils/errorHandler';
class EnrollmentsVsUnenrollmentsReport extends React.Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
this.state = {
paramsObject: {
from: moment()
.subtract(6, 'days')
.format('YYYY-MM-DD'),
to: moment()
.add(1, 'days')
.format('YYYY-MM-DD'),
},
data: [],
fields: [],
durationMode: 'weekly',
visible: false,
};
}
componentDidMount() {
this.fetchData();
}
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(6, 'days')
.format('YYYY-MM-DD'),
moment()
.add(1, 'days')
.format('YYYY-MM-DD'),
);
break;
case 'monthly':
this.updateDurationValue(
moment()
.subtract(29, 'days')
.format('YYYY-MM-DD'),
moment()
.add(1, 'days')
.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();
};
// 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 { 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',
});
return (
<div>
<PageHeader style={{ paddingTop: 0 }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/entgra">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>
<Link to="/entgra/reports">Reports</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>
Enrollments vs Unenrollments Report
</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap" style={{ marginBottom: '10px' }}>
<h3>Enrollments vs Unenrollments Report</h3>
<Radio.Group
onChange={this.handleDurationModeChange}
defaultValue={'weekly'}
value={durationMode}
style={{ marginBottom: 8, marginRight: 5 }}
>
<Radio.Button value={'daily'}>Today</Radio.Button>
<Radio.Button value={'weekly'}>Last Week</Radio.Button>
<Radio.Button value={'monthly'}>Last Month</Radio.Button>
</Radio.Group>
<Popover
trigger="hover"
content={
<div>
<DateRangePicker
updateDurationValue={this.updateDurationValue}
/>
</div>
}
visible={this.state.visible}
onVisibleChange={this.handlePopoverVisibleChange}
>
<Button style={{ marginRight: 10 }}>Custom Date</Button>
</Popover>
<div
style={{
backgroundColor: '#ffffff',
borderRadius: 5,
marginTop: 10,
}}
>
<Chart height={400} data={dv} forceFit>
<Axis name="Time" />
<Axis name="Number of Devices" />
<Legend />
<Tooltip
crosshairs={{
type: 'y',
}}
/>
<Geom
type="interval"
position="Time*Number of Devices"
color={'name'}
adjust={[
{
type: 'dodge',
marginRatio: 1 / 32,
},
]}
/>
</Chart>
</div>
</div>
</PageHeader>
<div
style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}
></div>
</div>
);
}
}
export default withConfigContext(EnrollmentsVsUnenrollmentsReport);

@ -1,183 +0,0 @@
/*
* 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 {
PageHeader,
Breadcrumb,
Icon,
Button,
Select,
message,
notification,
} from 'antd';
import { Link } from 'react-router-dom';
import { withConfigContext } from '../../../../../../components/ConfigContext';
import DeviceTable from '../../components/DevicesTable';
let config = null;
const { Option } = Select;
let { typeSelected, versionSelected } = false;
class OutdatedOSversionReport extends React.Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
config = this.props.context;
this.state = {
deviceType: null,
osVersion: null,
apiURL: null,
visible: false,
supportedOsVersions: [],
};
}
onDeviceTypeChange = value => {
typeSelected = true;
this.setState({ deviceType: value });
this.getSupportedOsVersions(value);
};
onOSVersionChange = value => {
versionSelected = true;
this.setState({ osVersion: value });
};
onClickGenerateButton = () => {
const { osVersion, deviceType } = this.state;
if (osVersion && deviceType != null) {
let apiURL =
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
`/reports/expired-devices/${deviceType}?osVersion=${osVersion}&`;
this.setState({ apiURL });
}
};
getSupportedOsVersions = deviceType => {
const config = this.props.context;
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
`/admin/device-types/${deviceType}/versions`,
)
.then(res => {
if (res.status === 200) {
let supportedOsVersions = JSON.parse(res.data.data);
this.setState({
supportedOsVersions,
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
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 OS Versions.',
});
}
});
};
render() {
const { apiURL, supportedOsVersions } = this.state;
return (
<div>
<PageHeader style={{ paddingTop: 0 }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/entgra">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>
<Link to="/entgra/reports">Reports</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Outdated OS Version</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap" style={{ marginBottom: '10px' }}>
<h3>Outdated OS Version Report</h3>
<p>
This report displays devices with an OS Version below the
specified version.
</p>
<div className="wrap" style={{ marginBottom: '10px' }}>
<Select
showSearch
style={{ width: 200, marginLeft: '10px' }}
placeholder="Select a Device type"
onChange={this.onDeviceTypeChange}
>
{config.supportedOStypes.map(data => (
<Option value={data.name} key={data.id}>
{data.name}
</Option>
))}
</Select>
<Select
style={{ width: 200, marginLeft: '10px' }}
placeholder="
Select an OS Version"
onChange={this.onOSVersionChange}
>
{supportedOsVersions.map(data => {
return (
<Option key={data.versionName}>{data.versionName}</Option>
);
})}
</Select>
{typeSelected && versionSelected ? (
<Button
type="primary"
onClick={this.onClickGenerateButton}
style={{ marginLeft: '10px' }}
>
Generate Report
</Button>
) : null}
</div>
<div
id="table"
style={{ backgroundColor: '#ffffff', borderRadius: 5 }}
>
<DeviceTable apiUrl={apiURL} />
</div>
</div>
</PageHeader>
<div
style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}
></div>
</div>
);
}
}
export default withConfigContext(OutdatedOSversionReport);

@ -1,128 +0,0 @@
/*
* 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 '../../../../../../../../../../components/ConfigContext';
class FeatureListModal extends React.Component {
constructor(props) {
super(props);
this.state = {
modalVisible: false,
name: '',
description: '',
features: [],
};
}
fetchViolatedFeatures = id => {
const config = this.props.context;
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/devices/' +
id +
'/features',
)
.then(res => {
if (res.status === 200) {
this.setState({
features: JSON.parse(res.data.data),
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description:
'Error occurred while trying to load non compliance feature list.',
});
}
});
};
openModal = () => {
this.fetchViolatedFeatures(this.props.id);
this.setState({
modalVisible: true,
});
};
handleOk = e => {
this.setState({
modalVisible: false,
});
};
render() {
const { features, modalVisible } = this.state;
let featureList = features.map(data => (
<List.Item key={data.featureCodes}>
<Typography.Text key={data.featureCodes} mark>
{data.featureCode}
</Typography.Text>
</List.Item>
));
return (
<div>
<div>
<Button
type="primary"
size={'small'}
icon="book"
onClick={this.openModal}
>
Violated Features
</Button>
</div>
<div>
<Modal
title="VIOLATED FEATURES"
width="40%"
visible={modalVisible}
onOk={this.handleOk}
footer={[
<Button key="submit" type="primary" onClick={this.handleOk}>
OK
</Button>,
]}
>
<List size="large" bordered>
{featureList}
</List>
</Modal>
</div>
</div>
);
}
}
export default withConfigContext(FeatureListModal);

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

@ -1,111 +0,0 @@
/*
* 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 '../../../../../../../../components/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: res.data.data.policies,
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load policies.',
});
}
this.setState({ loading: false });
});
};
handleChange = value => {
this.props.getPolicyId(value);
};
render() {
let item;
if (this.state.data) {
item = this.state.data.map(data => (
<Select.Option value={data.id} key={data.id}>
{data.profile.profileName}
</Select.Option>
));
}
return (
<Select
defaultValue="all"
style={{ width: 200 }}
onChange={this.handleChange}
>
<Option value="all">All</Option>
{item}
</Select>
);
}
}
export default withConfigContext(SelectPolicyDropDown);

@ -1,198 +0,0 @@
/*
* 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 '../../../../../../components/ConfigContext';
import PolicyDevicesTable from './components/PolicyDevicesTable';
import moment from 'moment';
import DateRangePicker from '../../components/DateRangePicker';
import SelectPolicyDropDown from './components/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(6, 'days')
.format('YYYY-MM-DD'),
to: moment()
.add(1, 'days')
.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().format('YYYY-MM-DD'),
moment()
.add(1, 'days')
.format('YYYY-MM-DD'),
);
break;
case 'weekly':
this.updateDurationValue(
moment()
.subtract(6, 'days')
.format('YYYY-MM-DD'),
moment()
.add(1, 'days')
.format('YYYY-MM-DD'),
);
break;
case 'monthly':
this.updateDurationValue(
moment()
.subtract(29, 'days')
.format('YYYY-MM-DD'),
moment()
.add(1, 'days')
.format('YYYY-MM-DD'),
);
break;
}
this.setState({ durationMode });
};
hidePopover = () => {
this.setState({
visible: false,
});
};
handlePopoverVisibleChange = visible => {
this.setState({ visible });
};
getPolicyId = policyId => {
let tempParamObj = this.state.policyReportData;
if (policyId === 'all') {
delete tempParamObj.policy;
} else {
tempParamObj.policy = policyId;
}
this.setState({ policyReportData: tempParamObj });
};
// Get modified value from datepicker and set it to paramsObject
updateDurationValue = (modifiedFromDate, modifiedToDate) => {
let tempParamObj = this.state.policyReportData;
tempParamObj.from = modifiedFromDate;
tempParamObj.to = modifiedToDate;
this.setState({ policyReportData: tempParamObj });
};
render() {
const { isCompliant, durationMode } = this.state;
const policyData = { ...this.state.policyReportData };
return (
<div>
<PageHeader style={{ paddingTop: 0 }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/entgra">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>
<Link to="/entgra/reports">Reports</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Policy Compliance Report</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap" style={{ marginBottom: '10px' }}>
<h3>Policy Report</h3>
<Radio.Group
onChange={this.handleModeChange}
defaultValue={true}
value={isCompliant}
style={{ marginBottom: 8, marginRight: 10 }}
>
<Radio.Button value={true}>Policy Compliant Devices</Radio.Button>
<Radio.Button value={false}>
Policy Non-Compliant Devices
</Radio.Button>
</Radio.Group>
<Radio.Group
onChange={this.handleDurationModeChange}
defaultValue={'weekly'}
value={durationMode}
style={{ marginBottom: 8, marginRight: 5 }}
>
<Radio.Button value={'daily'}>Today</Radio.Button>
<Radio.Button value={'weekly'}>Last Week</Radio.Button>
<Radio.Button value={'monthly'}>Last Month</Radio.Button>
</Radio.Group>
<Popover
trigger="hover"
content={
<div>
<DateRangePicker
updateDurationValue={this.updateDurationValue}
/>
</div>
}
visible={this.state.visible}
onVisibleChange={this.handlePopoverVisibleChange}
>
<Button style={{ marginRight: 10 }}>Custom Date</Button>
</Popover>
<SelectPolicyDropDown getPolicyId={this.getPolicyId} />
<div style={{ backgroundColor: '#ffffff', borderRadius: 5 }}>
<PolicyDevicesTable
policyReportData={policyData}
isCompliant={isCompliant}
/>
</div>
</div>
</PageHeader>
<div
style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}
></div>
</div>
);
}
}
export default withConfigContext(PolicyReport);

@ -19,7 +19,6 @@ package org.wso2.carbon.device.mgt.extensions.device.type.template;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.device.mgt.extensions.device.type.template.dao.DeviceTypePluginDAOManager; import org.wso2.carbon.device.mgt.extensions.device.type.template.dao.DeviceTypePluginDAOManager;
import org.wso2.carbon.device.mgt.extensions.device.type.template.exception.DeviceTypePluginExtensionException; import org.wso2.carbon.device.mgt.extensions.device.type.template.exception.DeviceTypePluginExtensionException;
import org.wso2.carbon.device.mgt.extensions.spi.DeviceTypePluginExtensionService; import org.wso2.carbon.device.mgt.extensions.spi.DeviceTypePluginExtensionService;
@ -36,34 +35,29 @@ public class DeviceTypePluginExtensionServiceImpl implements DeviceTypePluginExt
@Override @Override
public void addPluginDAOManager(String deviceType, DeviceTypePluginDAOManager pluginDAOManager) public void addPluginDAOManager(String deviceType, DeviceTypePluginDAOManager pluginDAOManager)
throws DeviceTypePluginExtensionException { throws DeviceTypePluginExtensionException {
int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId();
if (pluginDAOManager == null) { if (pluginDAOManager == null) {
String msg = "Cannot save DeviceTypePluginDAOManager against tenant id " + tenantId String msg = "Cannot save DeviceTypePluginDAOManager of device type: " + deviceType
+ " and device type: " + deviceType + " since DeviceTypePluginDAOManager is null"; + " since DeviceTypePluginDAOManager is null";
log.error(msg); log.error(msg);
throw new DeviceTypePluginExtensionException(msg); throw new DeviceTypePluginExtensionException(msg);
} }
if (!pluginDAOManagers.containsKey(tenantId + deviceType)) { if (!pluginDAOManagers.containsKey(deviceType)) {
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("Saving DeviceTypePluginDAOManager against tenant id " + tenantId + log.debug("Saving DeviceTypePluginDAOManager of device type: " + deviceType);
" and device type: " + deviceType);
} }
pluginDAOManagers.put(tenantId + deviceType, pluginDAOManager); pluginDAOManagers.put(deviceType, pluginDAOManager);
} }
} }
@Override @Override
public DeviceTypePluginDAOManager getPluginDAOManager(String deviceType) throws DeviceTypePluginExtensionException { public DeviceTypePluginDAOManager getPluginDAOManager(String deviceType) throws DeviceTypePluginExtensionException {
int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(); if (pluginDAOManagers.containsKey(deviceType)) {
if (pluginDAOManagers.containsKey(tenantId + deviceType)) {
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("Retrieving DeviceTypePluginDAOManager against tenant id " + tenantId + log.debug("Retrieving DeviceTypePluginDAOManager of device type: " + deviceType);
" and device type: " + deviceType);
} }
return pluginDAOManagers.get(tenantId + deviceType); return pluginDAOManagers.get(deviceType);
} else { } else {
String msg = "DeviceTypePluginDAOManager could not be found against tenant id " + tenantId + String msg = "DeviceTypePluginDAOManager of device type: " + deviceType + " cannot be found";
" and device type: " + deviceType;
log.error(msg); log.error(msg);
throw new DeviceTypePluginExtensionException(msg); throw new DeviceTypePluginExtensionException(msg);
} }

@ -1,3 +1,21 @@
/*
* Copyright (c) 2020, Entgra (Pvt) Ltd. (http://www.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.extensions.device.type.template.config; package org.wso2.carbon.device.mgt.extensions.device.type.template.config;
import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElement;

Loading…
Cancel
Save