{this.controllerBar()}
- {locationData.length > 0 ? (
-
+ {locationHistorySnapshots.length > 0 ? (
+
) : (
)}
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Geo/index.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Geo/index.js
index e82b13133b..c313b91252 100644
--- a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Geo/index.js
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Geo/index.js
@@ -32,6 +32,7 @@ class Geo extends React.Component {
}
render() {
+ const { deviceIdentifier, deviceType } = this.props.match.params;
return (
@@ -41,11 +42,14 @@ class Geo extends React.Component {
Home
- Geo
+
+ Devices
+
+ {`Location History - ${deviceType} / ${deviceIdentifier}`}
-
Geo
-
Geo Location Service
+
Location History
+
{`${deviceType} / ${deviceIdentifier}`}
-
+
);
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Notifications/Components/NotificationsTable/index.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Notifications/Components/NotificationsTable/index.js
new file mode 100644
index 0000000000..2b5b2bfc66
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Notifications/Components/NotificationsTable/index.js
@@ -0,0 +1,197 @@
+/*
+ * 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, Table } 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';
+import { handleApiError } from '../../../../../../services/utils/errorHandler';
+
+let config = null;
+
+const columns = [
+ {
+ 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: 'Description',
+ dataIndex: 'description',
+ key: 'description',
+ },
+];
+
+class NotificationsTable 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.notificationType !== this.props.notificationType) {
+ 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;
+
+ extraParams = {
+ 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.notificationType === 'unread') {
+ apiUrl =
+ window.location.origin +
+ config.serverConfig.invoker.uri +
+ config.serverConfig.invoker.deviceMgt +
+ '/notifications?status=NEW&' +
+ encodedExtraParams;
+ } else {
+ apiUrl =
+ window.location.origin +
+ config.serverConfig.invoker.uri +
+ config.serverConfig.invoker.deviceMgt +
+ '/notifications?' +
+ 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;
+
+ return (
+
+
record.id}
+ dataSource={data.notifications}
+ pagination={{
+ ...pagination,
+ size: 'small',
+ // position: "top",
+ total: data.count,
+ showTotal: (total, range) =>
+ `showing ${range[0]}-${range[1]} of ${total} notifications`,
+ }}
+ loading={loading}
+ onChange={this.handleTableChange}
+ rowSelection={this.rowSelection}
+ />
+
+ );
+ }
+}
+
+export default withConfigContext(NotificationsTable);
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Notifications/index.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Notifications/index.js
new file mode 100644
index 0000000000..456c3a9dab
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Notifications/index.js
@@ -0,0 +1,132 @@
+/*
+ * 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,
+ Button,
+ PageHeader,
+ Breadcrumb,
+ Icon,
+ Radio,
+} from 'antd';
+import { withConfigContext } from '../../../../components/ConfigContext';
+import axios from 'axios';
+import { Link } from 'react-router-dom';
+
+import NotificationsTable from './Components/NotificationsTable';
+
+class Notifications extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ visible: false,
+ data: [],
+ notificationType: 'all',
+ };
+ }
+
+ handleModeChange = e => {
+ const notificationType = e.target.value;
+ this.setState({ notificationType });
+ };
+
+ clearNotifications = () => {
+ const config = this.props.context;
+ axios
+ .put(
+ window.location.origin +
+ config.serverConfig.invoker.uri +
+ config.serverConfig.invoker.deviceMgt +
+ '/notifications/clear-all',
+ { 'Content-Type': 'application/json; charset=utf-8' },
+ )
+ .then(res => {
+ if (res.status === 200) {
+ notification.success({
+ message: 'Done',
+ duration: 0,
+ description: 'All notifications are cleared.',
+ });
+ }
+ })
+ .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 clear notifications.',
+ });
+ }
+ });
+ };
+
+ render() {
+ const { notificationType } = this.state;
+ return (
+
+
+
+
+
+ Home
+
+
+ Notifications
+
+
+
DEVICE NOTIFICATIONS
+
+ All Notifications
+ Unread Notifications
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default withConfigContext(Notifications);
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Policies/components/AddPolicy/components/AssignGroups/index.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Policies/components/AddPolicy/components/AssignGroups/index.js
new file mode 100644
index 0000000000..e90d0fa9dd
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Policies/components/AddPolicy/components/AssignGroups/index.js
@@ -0,0 +1,260 @@
+/*
+ * 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 { withConfigContext } from '../../../../../../../../components/ConfigContext';
+import { Button, Col, Form, message, notification, Radio, Select } from 'antd';
+import axios from 'axios';
+const { Option } = Select;
+
+class AssignGroups extends React.Component {
+ constructor(props) {
+ super(props);
+ this.config = this.props.context;
+ this.userSelector = React.createRef();
+ this.roleSelector = React.createRef();
+ this.state = {
+ roles: [],
+ users: [],
+ groups: [],
+ };
+ }
+ componentDidMount() {
+ this.getRolesList();
+ this.getGroupsList();
+ }
+
+ handleSetUserRoleFormItem = event => {
+ if (event.target.value === 'roleSelector') {
+ this.roleSelector.current.style.cssText = 'display: block;';
+ this.userSelector.current.style.cssText = 'display: none;';
+ } else {
+ this.roleSelector.current.style.cssText = 'display: none;';
+ this.userSelector.current.style.cssText = 'display: block;';
+ }
+ };
+
+ // generate payload by adding Assign Groups
+ onHandleContinue = (e, formName) => {
+ this.props.form.validateFields((err, values) => {
+ if (!err) {
+ if (typeof values.roles === 'string') {
+ values.roles = [values.roles];
+ }
+ if (!values.users) {
+ delete values.users;
+ }
+
+ if (values.deviceGroups === 'NONE') {
+ delete values.deviceGroups;
+ }
+
+ this.props.getPolicyPayloadData(formName, values);
+ this.props.getNextStep();
+ }
+ });
+ };
+
+ getRolesList = () => {
+ let apiURL =
+ window.location.origin +
+ this.config.serverConfig.invoker.uri +
+ this.config.serverConfig.invoker.deviceMgt +
+ '/roles?user-store=PRIMARY&limit=100';
+
+ axios
+ .get(apiURL)
+ .then(res => {
+ if (res.status === 200) {
+ this.setState({
+ roles: res.data.data.roles,
+ });
+ }
+ })
+ .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 roles.',
+ });
+ }
+ });
+ };
+
+ getUsersList = value => {
+ let apiURL =
+ window.location.origin +
+ this.config.serverConfig.invoker.uri +
+ this.config.serverConfig.invoker.deviceMgt +
+ '/users/search/usernames?filter=' +
+ value +
+ '&domain=Primary';
+ axios
+ .get(apiURL)
+ .then(res => {
+ if (res.status === 200) {
+ let users = JSON.parse(res.data.data);
+ this.setState({
+ users,
+ });
+ }
+ })
+ .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 users.',
+ });
+ }
+ });
+ };
+
+ // fetch data from api
+ getGroupsList = () => {
+ let apiUrl =
+ window.location.origin +
+ this.config.serverConfig.invoker.uri +
+ this.config.serverConfig.invoker.deviceMgt +
+ '/admin/groups';
+
+ // send request to the invokerss
+ axios
+ .get(apiUrl)
+ .then(res => {
+ if (res.status === 200) {
+ this.setState({
+ groups: res.data.data.deviceGroups,
+ });
+ }
+ })
+ .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 device groups.',
+ });
+ }
+ });
+ };
+
+ render() {
+ const { getFieldDecorator } = this.props.form;
+ return (
+
+
+
+ Set User role(s)
+ Set User(s)
+
+
+
+ {getFieldDecorator('roles', {
+ initialValue: 'ANY',
+ })(
+ ,
+ )}
+
+
+
+
+ {getFieldDecorator('users', {})(
+ ,
+ )}
+
+
+
+
+ {getFieldDecorator('deviceGroups', {
+ initialValue: 'NONE',
+ })(
+ ,
+ )}
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default withConfigContext(Form.create()(AssignGroups));
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Policies/components/AddPolicy/components/ConfigureProfile/index.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Policies/components/AddPolicy/components/ConfigureProfile/index.js
index c28046d100..9e6fedb7e9 100644
--- a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Policies/components/AddPolicy/components/ConfigureProfile/index.js
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Policies/components/AddPolicy/components/ConfigureProfile/index.js
@@ -45,6 +45,8 @@ const { TabPane } = Tabs;
const { Option } = Select;
const { TextArea } = Input;
+const subPanelpayloadAttributes = {};
+
class ConfigureProfile extends React.Component {
constructor(props) {
super(props);
@@ -54,6 +56,8 @@ class ConfigureProfile extends React.Component {
isDisplayMain: 'none',
activePanelKeys: [],
activeSubPanelKeys: [],
+ subFormList: [],
+ subPanelpayloadAttributes: {},
count: 0,
dataArray: [],
customInputDataArray: [],
@@ -62,8 +66,6 @@ class ConfigureProfile extends React.Component {
};
}
- componentDidMount() {}
-
// convert time from 24h format to 12h format
timeConverter = time => {
time = time
@@ -297,9 +299,66 @@ class ConfigureProfile extends React.Component {
return columns;
};
+ // generate payload by adding policy configurations
+ onHandleContinue = (e, formname) => {
+ const allFields = this.props.form.getFieldsValue();
+ let activeFields = [];
+ // get currently active field list
+ for (let i = 0; i < this.state.activePanelKeys.length; i++) {
+ Object.keys(allFields).map(key => {
+ if (key.includes(`${this.state.activePanelKeys[i]}-`)) {
+ if (
+ subPanelpayloadAttributes.hasOwnProperty(
+ `${this.state.activePanelKeys[i]}`,
+ )
+ ) {
+ Object.keys(
+ subPanelpayloadAttributes[this.state.activePanelKeys[i]],
+ ).map(subPanel => {
+ if (`${this.state.activePanelKeys[i]}-${subPanel}` === true) {
+ if (key.includes(`-${subPanel}-`)) {
+ activeFields.push(key);
+ }
+ } else if (!key.includes(`-${subPanel}-`)) {
+ activeFields.push(key);
+ }
+ });
+ } else {
+ activeFields.push(key);
+ }
+ }
+ });
+ }
+ // validate fields and get profile features list
+ this.props.form.validateFields(activeFields, (err, values) => {
+ if (!err) {
+ let profileFeaturesList = [];
+ for (let i = 0; i < this.state.activePanelKeys.length; i++) {
+ let content = {};
+ Object.entries(values).map(([key, value]) => {
+ if (key.includes(`${this.state.activePanelKeys[i]}-`)) {
+ content[
+ key.replace(`${this.state.activePanelKeys[i]}-`, '')
+ ] = value;
+ }
+ });
+ let feature = {
+ featureCode: this.state.activePanelKeys[i],
+ deviceType: 'android',
+ content: content,
+ };
+ profileFeaturesList.push(feature);
+ }
+ this.props.getPolicyPayloadData(formname, profileFeaturesList);
+ this.props.getNextStep();
+ }
+ });
+ };
+
// generate form items
- getPanelItems = panel => {
+ getPanelItems = (panel, panelId) => {
const { getFieldDecorator } = this.props.form;
+ const subPanelList = {};
return panel.map((item, k) => {
switch (item.type) {
case 'select':
@@ -399,7 +458,6 @@ class ConfigureProfile extends React.Component {
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
- // valuePropName: 'option',
initialValue: item.optional.initialDataIndex,
})(