From 070c8cb3efdac2711a2d11519e140a41f6f20628 Mon Sep 17 00:00:00 2001 From: Shamalka Navod Date: Wed, 18 Mar 2020 06:11:19 +0000 Subject: [PATCH] Add device-mgt react app notification feature --- .../react-app/src/index.js | 115 +++++----- .../components/NotificationsDrawer/index.js | 172 +++++++++++++++ .../react-app/src/scenes/Home/index.js | 163 +++++++------- .../Components/NotificationsTable/index.js | 198 ++++++++++++++++++ .../scenes/Home/scenes/Notifications/index.js | 139 ++++++++++++ 5 files changed, 652 insertions(+), 135 deletions(-) create mode 100644 components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/components/NotificationsDrawer/index.js create mode 100644 components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Notifications/Components/NotificationsTable/index.js create mode 100644 components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Notifications/index.js diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/index.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/index.js index 07b2222a9c..f0433aca5c 100644 --- a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/index.js +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/index.js @@ -32,6 +32,16 @@ import AppNotInstalledDevicesReport from './scenes/Home/scenes/Reports/scenes/Ap 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 DeviceEnroll from './scenes/Home/scenes/Devices/scenes/Enroll'; +import Groups from './scenes/Home/scenes/Groups'; +import Users from './scenes/Home/scenes/Users'; +import Policies from './scenes/Home/scenes/Policies'; +import AddNewPolicy from './scenes/Home/scenes/Policies/scenes/AddNewPolicy'; +import Roles from './scenes/Home/scenes/Roles'; +import DeviceTypes from './scenes/Home/scenes/DeviceTypes'; +import Certificates from './scenes/Home/scenes/Certificates'; +import Devices from './scenes/Home/scenes/Devices'; const routes = [ { @@ -44,26 +54,16 @@ const routes = [ exact: false, component: Home, routes: [ - // { - // path: '/entgra/devices', - // component: Devices, - // exact: true, - // }, - // { - // path: '/entgra/devices/enroll', - // component: DeviceEnroll, - // exact: true, - // }, - // { - // path: '/entgra/devices', - // component: Devices, - // exact: true, - // }, - // { - // path: '/entgra/devices/enroll', - // component: DeviceEnroll, - // exact: true, - // }, + { + path: '/entgra/devices', + component: Devices, + exact: true, + }, + { + path: '/entgra/devices/enroll', + component: DeviceEnroll, + exact: true, + }, { path: '/entgra/geo', component: Geo, @@ -74,41 +74,46 @@ const routes = [ component: Reports, exact: true, }, - // { - // path: '/entgra/groups', - // component: Groups, - // exact: true, - // }, - // { - // path: '/entgra/users', - // component: Users, - // exact: true, - // }, - // { - // path: '/entgra/policies', - // component: Policies, - // exact: true, - // }, - // { - // path: '/entgra/policy/add', - // component: AddNewPolicy, - // exact: true, - // }, - // { - // path: '/entgra/roles', - // component: Roles, - // exact: true, - // }, - // { - // path: '/entgra/devicetypes', - // component: DeviceTypes, - // exact: true, - // }, - // { - // path: '/entgra/certificates', - // component: Certificates, - // exact: true, - // }, + { + path: '/entgra/groups', + component: Groups, + exact: true, + }, + { + path: '/entgra/users', + component: Users, + exact: true, + }, + { + path: '/entgra/policies', + component: Policies, + exact: true, + }, + { + path: '/entgra/policy/add', + component: AddNewPolicy, + exact: true, + }, + { + path: '/entgra/roles', + component: Roles, + exact: true, + }, + { + path: '/entgra/devicetypes', + component: DeviceTypes, + exact: true, + }, + { + path: '/entgra/certificates', + component: Certificates, + exact: true, + }, + { + path: '/entgra/notifications', + component: Notifications, + exact: true, + }, { path: '/entgra/reports/enrollments', component: EnrollmentsVsUnenrollmentsReport, diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/components/NotificationsDrawer/index.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/components/NotificationsDrawer/index.js new file mode 100644 index 0000000000..7421038e4e --- /dev/null +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/components/NotificationsDrawer/index.js @@ -0,0 +1,172 @@ +/* + * 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 { Drawer, message, notification, Card, Button } from 'antd'; +import { withConfigContext } from '../../../../components/ConfigContext'; +import axios from 'axios'; +import { Link } from 'react-router-dom'; + +// eslint-disable-next-line no-unused-vars +let config = null; + +class NotificationsDrawer extends React.Component { + routes; + + constructor(props) { + super(props); + this.routes = props.routes; + config = this.props.context; + this.state = { + visible: false, + data: [], + }; + } + + showDrawer = () => { + this.setState({ + visible: true, + }); + this.fetchNotifications(); + }; + + onClose = () => { + this.setState({ + visible: false, + }); + }; + + fetchNotifications = () => { + const config = this.props.context; + axios + .get( + window.location.origin + + config.serverConfig.invoker.uri + + config.serverConfig.invoker.deviceMgt + + '/notifications', + ) + .then(res => { + if (res.status === 200) { + this.setState({ data: res.data.data.notifications }); + } + }) + .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 notifications list.', + }); + } + }); + }; + + 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.', + }); + } + }); + this.setState({ + visible: false, + }); + }; + + render() { + const { data } = this.state; + const notificationItemList = data.map(data => ( + +

{data.deviceType + ':' + data.deviceName}t

+

{data.description}

+
+ )); + return ( +
+
+
+ Notifications +
+
+ + + + + + {notificationItemList} + +
+ ); + } +} + +export default withConfigContext(NotificationsDrawer); diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/index.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/index.js index 529185c8b3..80cd6d19d5 100644 --- a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/index.js +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/index.js @@ -23,6 +23,7 @@ import RouteWithSubRoutes from '../../components/RouteWithSubRoutes'; import { Redirect } from 'react-router'; import './styles.css'; import { withConfigContext } from '../../components/ConfigContext'; +import NotificationsDrawer from './components/NotificationsDrawer'; import Logout from './components/Logout'; const { Header, Content, Footer } = Layout; @@ -72,26 +73,26 @@ class Home extends React.Component { marginRight: 110, }} > - {/* */} - {/* */} - {/* Devices*/} - {/* */} - {/* }*/} - {/* >*/} - {/* */} - {/* */} - {/* View*/} - {/* */} - {/* */} - {/* */} - {/* */} - {/* Enroll*/} - {/* */} - {/* */} - {/* */} + + + Devices + + } + > + + + View + + + + + Enroll + + + Reports - {/* */} - {/* */} - {/* */} - {/* Groups*/} - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - {/* Users*/} - {/* */} - {/* */} - {/* */} - {/* */} - {/* Policies*/} - {/* */} - {/* }*/} - {/* >*/} - {/* */} - {/* */} - {/* View*/} - {/* */} - {/* */} - {/* */} - {/* */} - {/* Add New Policy*/} - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - {/* Roles*/} - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - {/* Device Types*/} - {/* */} - {/* */} - {/* */} - {/* */} - {/* Configurations*/} - {/* */} - {/* }*/} - {/* >*/} - {/* */} - {/* */} - {/* Certificates*/} - {/* */} - {/* */} - {/* */} - + + + + Groups + + + + + + Users + + + + + Policies + + } + > + + + View + + + + + Add New Policy + + + + + + + Roles + + + + + + Device Types + + + + + Configurations + + } + > + + + Certificates + + + + + + 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} devices`, + // showQuickJumper: true + }} + 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..2c86484680 --- /dev/null +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Notifications/index.js @@ -0,0 +1,139 @@ +/* + * 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'; + +// eslint-disable-next-line no-unused-vars +let config = null; + +class Notifications extends React.Component { + routes; + + constructor(props) { + super(props); + this.routes = props.routes; + config = this.props.context; + 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);