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

feature/appm-store/pbac
inoshperera 5 years ago
commit 199b61ab94

@ -32,9 +32,8 @@
<SchedulerTaskEnabled>true</SchedulerTaskEnabled>
<PushNotificationProviders>
<Provider>org.wso2.carbon.device.mgt.extensions.push.notification.provider.fcm.FCMBasedPushNotificationProvider</Provider>
<!--<Provider>org.wso2.carbon.device.mgt.mobile.impl.ios.apns.APNSBasedPushNotificationProvider</Provider>-->
<Provider>org.wso2.carbon.device.mgt.extensions.push.notification.provider.mqtt.MQTTBasedPushNotificationProvider</Provider>
<Provider>org.wso2.carbon.device.mgt.extensions.push.notification.provider.http.HTTPBasedPushNotificationProvider</Provider>
<Provider>org.wso2.carbon.device.mgt.extensions.push.notification.provider.http.HTTPBasedPushNotificationProvider</Provider>
<Provider>org.wso2.carbon.device.mgt.extensions.push.notification.provider.xmpp.XMPPBasedPushNotificationProvider</Provider>
</PushNotificationProviders>
</PushNotificationConfiguration>

@ -22,7 +22,7 @@ import RouteWithSubRoutes from './components/RouteWithSubRoutes';
import { BrowserRouter as Router, Redirect, Switch } from 'react-router-dom';
import axios from 'axios';
import { Layout, Spin, Result } from 'antd';
import ConfigContext from './context/ConfigContext';
import ConfigContext from './components/context/ConfigContext';
const { Content } = Layout;
const loadingView = (

@ -18,12 +18,12 @@
import React from 'react';
import ReactDOM from 'react-dom';
import * as serviceWorker from './serviceWorker';
import * as serviceWorker from './services/serviceWorkers/serviceWorker';
import App from './App';
import Login from './pages/Login';
import Dashboard from './pages/dashboard/Dashboard';
import Apps from './pages/dashboard/apps/Apps';
import Release from './pages/dashboard/apps/release/Release';
import Login from './scenes/Login';
import Dashboard from './scenes/Home';
import Apps from './scenes/Home/scenes/Apps';
import Release from './scenes/Home/scenes/Apps/scenes/Release';
import './index.css';
const routes = [

@ -19,8 +19,8 @@
import React from 'react';
import { Menu, Icon } from 'antd';
import axios from 'axios';
import { withConfigContext } from '../../../context/ConfigContext';
import { handleApiError } from '../../../js/Utils';
import { withConfigContext } from '../../../../components/context/ConfigContext';
import { handleApiError } from '../../../../services/utils/errorHandler';
/*
This class for call the logout api by sending request

@ -24,10 +24,10 @@ import { Link } from 'react-router-dom';
import RouteWithSubRoutes from '../../components/RouteWithSubRoutes';
import { Switch } from 'react-router';
import axios from 'axios';
import './Dashboard.css';
import { withConfigContext } from '../../context/ConfigContext';
import Logout from './logout/Logout';
import { handleApiError } from '../../js/Utils';
import './styles.css';
import { withConfigContext } from '../../components/context/ConfigContext';
import Logout from './components/Logout';
import { handleApiError } from '../../services/utils/errorHandler';
const { SubMenu } = Menu;

@ -19,7 +19,7 @@
import { Card, Typography, Col, Row } from 'antd';
import React from 'react';
import { Link } from 'react-router-dom';
import './AppCard.css';
import './styles.css';
import StarRatings from 'react-star-ratings';
const { Meta } = Card;

@ -17,11 +17,11 @@
*/
import React from 'react';
import AppCard from './AppCard';
import AppCard from './components/AppCard';
import { Col, Row, Result } from 'antd';
import axios from 'axios';
import { withConfigContext } from '../../context/ConfigContext';
import { handleApiError } from '../../js/Utils';
import { withConfigContext } from '../../../../../../components/context/ConfigContext';
import { handleApiError } from '../../../../../../services/utils/errorHandler';
import InfiniteScroll from 'react-infinite-scroller';
const limit = 30;

@ -17,7 +17,7 @@
*/
import React from 'react';
import AppList from '../../../components/apps/AppList';
import AppList from './components/AppList';
class Apps extends React.Component {
routes;

@ -19,7 +19,7 @@
import React, { Component } from 'react';
import RcViewer from 'rc-viewer';
class ImgViewer extends Component {
class ImageViewer extends Component {
render() {
const options = {
title: false,
@ -59,4 +59,4 @@ class ImgViewer extends Component {
}
}
export default ImgViewer;
export default ImageViewer;

@ -23,9 +23,9 @@ 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 '../../../../context/ConfigContext';
import { handleApiError } from '../../../../js/Utils';
import InstallModalFooter from './installModalFooter/InstallModalFooter';
import { withConfigContext } from '../../../../../../../../../../../../components/context/ConfigContext';
import { handleApiError } from '../../../../../../../../../../../../services/utils/errorHandler';
import InstallModalFooter from '../../../installModalFooter';
const { Text } = Typography;
const columns = [

@ -20,9 +20,9 @@ import React from 'react';
import { Typography, Select, Spin, Alert } from 'antd';
import debounce from 'lodash.debounce';
import axios from 'axios';
import { withConfigContext } from '../../../../context/ConfigContext';
import { handleApiError } from '../../../../js/Utils';
import InstallModalFooter from './installModalFooter/InstallModalFooter';
import { withConfigContext } from '../../../../../../../../../../../../components/context/ConfigContext';
import { handleApiError } from '../../../../../../../../../../../../services/utils/errorHandler';
import InstallModalFooter from '../../../installModalFooter';
const { Text } = Typography;
const { Option } = Select;

@ -20,9 +20,9 @@ import React from 'react';
import { Typography, Select, Spin, Alert } from 'antd';
import debounce from 'lodash.debounce';
import axios from 'axios';
import { withConfigContext } from '../../../../context/ConfigContext';
import { handleApiError } from '../../../../js/Utils';
import InstallModalFooter from './installModalFooter/InstallModalFooter';
import { withConfigContext } from '../../../../../../../../../../../../components/context/ConfigContext';
import { handleApiError } from '../../../../../../../../../../../../services/utils/errorHandler';
import InstallModalFooter from '../../../installModalFooter';
const { Text } = Typography;
const { Option } = Select;

@ -20,9 +20,9 @@ import React from 'react';
import { Typography, Select, Spin, Alert } from 'antd';
import debounce from 'lodash.debounce';
import axios from 'axios';
import { withConfigContext } from '../../../../context/ConfigContext';
import { handleApiError } from '../../../../js/Utils';
import InstallModalFooter from './installModalFooter/InstallModalFooter';
import { withConfigContext } from '../../../../../../../../../../../../components/context/ConfigContext';
import { handleApiError } from '../../../../../../../../../../../../services/utils/errorHandler';
import InstallModalFooter from '../../../installModalFooter';
const { Text } = Typography;
const { Option } = Select;

@ -18,14 +18,14 @@
import React from 'react';
import { Modal, Spin, Tabs } from 'antd';
import UserInstall from './UserInstall';
import GroupInstall from './GroupInstall';
import RoleInstall from './RoleInstall';
import DeviceInstall from './DeviceInstall';
import UserInstall from './components/UserInstall';
import GroupInstall from './components/GroupInstall';
import RoleInstall from './components/RoleInstall';
import DeviceInstall from './components/DeviceInstall';
const { TabPane } = Tabs;
class AppInstallModal extends React.Component {
class Install extends React.Component {
state = {
data: [],
};
@ -65,4 +65,4 @@ class AppInstallModal extends React.Component {
}
}
export default AppInstallModal;
export default Install;

@ -31,8 +31,8 @@ import {
} from 'antd';
import StarRatings from 'react-star-ratings';
import axios from 'axios';
import { withConfigContext } from '../../../../context/ConfigContext';
import { handleApiError } from '../../../../js/Utils';
import { withConfigContext } from '../../../../../../../../../../../../../../components/context/ConfigContext';
import { handleApiError } from '../../../../../../../../../../../../../../services/utils/errorHandler';
const { Title } = Typography;
const { TextArea } = Input;

@ -18,9 +18,9 @@
import React from 'react';
import { List, Typography, Empty, Alert } from 'antd';
import SingleReview from './singleReview/SingleReview';
import AddReview from './AddReview';
import { withConfigContext } from '../../../../context/ConfigContext';
import SingleReview from '../Reviews/components/Review';
import AddReview from './components/AddReview';
import { withConfigContext } from '../../../../../../../../../../../../components/context/ConfigContext';
const { Text } = Typography;

@ -19,12 +19,12 @@
import React from 'react';
import { Row, Typography, Icon } from 'antd';
import StarRatings from 'react-star-ratings';
import './DetailedRating.css';
import { withConfigContext } from '../../../context/ConfigContext';
import './styles.css';
import { withConfigContext } from '../../../../../../../../../../../../components/context/ConfigContext';
const { Text } = Typography;
class DetailedRating extends React.Component {
class Rating extends React.Component {
render() {
const { detailedRating } = this.props;
@ -122,4 +122,4 @@ class DetailedRating extends React.Component {
}
}
export default withConfigContext(DetailedRating);
export default withConfigContext(Rating);

@ -30,14 +30,14 @@ import {
} from 'antd';
import StarRatings from 'react-star-ratings';
import axios from 'axios';
import './EditReview.css';
import { withConfigContext } from '../../../../../../context/ConfigContext';
import { handleApiError } from '../../../../../../js/Utils';
import './styles.css';
import { withConfigContext } from '../../../../../../../../../../../../../../../../components/context/ConfigContext';
import { handleApiError } from '../../../../../../../../../../../../../../../../services/utils/errorHandler';
const { Title } = Typography;
const { TextArea } = Input;
class EditReview extends React.Component {
class Edit extends React.Component {
constructor(props) {
super(props);
this.state = {
@ -195,4 +195,4 @@ class EditReview extends React.Component {
}
}
export default withConfigContext(EditReview);
export default withConfigContext(Edit);

@ -21,11 +21,11 @@ import { Avatar, notification } from 'antd';
import { List, Typography, Popconfirm } from 'antd';
import StarRatings from 'react-star-ratings';
import Twemoji from 'react-twemoji';
import './SingleReview.css';
import EditReview from './editReview/EditReview';
import './styles.css';
import EditReview from './components/Edit';
import axios from 'axios';
import { withConfigContext } from '../../../../../context/ConfigContext';
import { handleApiError } from '../../../../../js/Utils';
import { withConfigContext } from '../../../../../../../../../../../../../../components/context/ConfigContext';
import { handleApiError } from '../../../../../../../../../../../../../../services/utils/errorHandler';
const { Text, Paragraph } = Typography;
const colorList = [
@ -41,7 +41,7 @@ const colorList = [
'#f9ca24',
];
class SingleReview extends React.Component {
class Review extends React.Component {
static defaultProps = {
isPersonalReview: false,
};
@ -167,4 +167,4 @@ class SingleReview extends React.Component {
}
}
export default withConfigContext(SingleReview);
export default withConfigContext(Review);

@ -18,13 +18,13 @@
import React from 'react';
import { List, Spin, Button, Alert } from 'antd';
import './Reviews.css';
import './styles.css';
import InfiniteScroll from 'react-infinite-scroller';
import SingleReview from './singleReview/SingleReview';
import SingleReview from './components/Review';
import axios from 'axios';
import { withConfigContext } from '../../../../context/ConfigContext';
import { handleApiError } from '../../../../js/Utils';
import { withConfigContext } from '../../../../../../../../../../../../components/context/ConfigContext';
import { handleApiError } from '../../../../../../../../../../../../services/utils/errorHandler';
const limit = 5;

@ -17,13 +17,13 @@
*/
import React from 'react';
import CurrentUsersReview from './CurrentUsersReview';
import CurrentUsersReview from './componets/CurrentUsersReview';
import { Col, Divider, Row, Typography } from 'antd';
import DetailedRating from '../DetailedRating';
import Reviews from './Reviews';
import DetailedRating from './componets/Rating';
import Reviews from './componets/Reviews';
import axios from 'axios';
import { handleApiError } from '../../../../js/Utils';
import { withConfigContext } from '../../../../context/ConfigContext';
import { handleApiError } from '../../../../../../../../../../services/utils/errorHandler';
import { withConfigContext } from '../../../../../../../../../../components/context/ConfigContext';
const { Text } = Typography;

@ -23,8 +23,8 @@ 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 '../../../context/ConfigContext';
import { handleApiError } from '../../../js/Utils';
import { withConfigContext } from '../../../../../../../../../../components/context/ConfigContext';
import { handleApiError } from '../../../../../../../../../../services/utils/errorHandler';
const { Text } = Typography;

@ -23,9 +23,9 @@ 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 '../../../../context/ConfigContext';
import { handleApiError } from '../../../../js/Utils';
import InstallModalFooter from './installModalFooter/InstallModalFooter';
import { withConfigContext } from '../../../../../../../../../../../../components/context/ConfigContext';
import { handleApiError } from '../../../../../../../../../../../../services/utils/errorHandler';
import InstallModalFooter from '../../../installModalFooter';
const { Text } = Typography;
const columns = [

@ -20,9 +20,9 @@ import React from 'react';
import { Typography, Select, Spin, Alert } from 'antd';
import debounce from 'lodash.debounce';
import axios from 'axios';
import { withConfigContext } from '../../../../context/ConfigContext';
import { handleApiError } from '../../../../js/Utils';
import InstallModalFooter from './installModalFooter/InstallModalFooter';
import { withConfigContext } from '../../../../../../../../../../../../components/context/ConfigContext';
import { handleApiError } from '../../../../../../../../../../../../services/utils/errorHandler';
import InstallModalFooter from '../../../installModalFooter';
const { Text } = Typography;
const { Option } = Select;

@ -20,9 +20,9 @@ import React from 'react';
import { Typography, Select, Spin, Alert } from 'antd';
import debounce from 'lodash.debounce';
import axios from 'axios';
import { withConfigContext } from '../../../../context/ConfigContext';
import { handleApiError } from '../../../../js/Utils';
import InstallModalFooter from './installModalFooter/InstallModalFooter';
import { withConfigContext } from '../../../../../../../../../../../../components/context/ConfigContext';
import { handleApiError } from '../../../../../../../../../../../../services/utils/errorHandler';
import InstallModalFooter from '../../../installModalFooter';
const { Text } = Typography;
const { Option } = Select;

@ -20,9 +20,9 @@ import React from 'react';
import { Typography, Select, Spin, Alert } from 'antd';
import debounce from 'lodash.debounce';
import axios from 'axios';
import { withConfigContext } from '../../../../context/ConfigContext';
import { handleApiError } from '../../../../js/Utils';
import InstallModalFooter from './installModalFooter/InstallModalFooter';
import { withConfigContext } from '../../../../../../../../../../../../components/context/ConfigContext';
import { handleApiError } from '../../../../../../../../../../../../services/utils/errorHandler';
import InstallModalFooter from '../../../installModalFooter';
const { Text } = Typography;
const { Option } = Select;

@ -17,14 +17,14 @@
*/
import React from 'react';
import { Modal, Spin, Tabs } from 'antd';
import DeviceUninstall from './DeviceUninstall';
import UserUninstall from './UserUninstall';
import RoleUninstall from './RoleUninstall';
import GroupUninstall from './GroupUninstall';
import DeviceUninstall from './components/DeviceUninstall';
import UserUninstall from './components/UserUninstall';
import RoleUninstall from './components/RoleUninstall';
import GroupUninstall from './components/GroupUninstall';
const { TabPane } = Tabs;
class AppUninstallModal extends React.Component {
class Uninstall extends React.Component {
state = {
data: [],
};
@ -74,4 +74,4 @@ class AppUninstallModal extends React.Component {
}
}
export default AppUninstallModal;
export default Uninstall;

@ -30,17 +30,17 @@ import {
Tabs,
Tag,
} from 'antd';
import '../../../App.css';
import ImgViewer from '../../apps/release/images/ImgViewer';
import '../../../../../../../../App.css';
import ImageViewer from './components/ImageViewer';
import StarRatings from 'react-star-ratings';
import axios from 'axios';
import pSBC from 'shade-blend-color';
import AppInstallModal from './install/AppInstallModal';
import AppUninstallModal from './install/AppUninstallModal';
import { withConfigContext } from '../../../context/ConfigContext';
import { handleApiError } from '../../../js/Utils';
import ReviewContainer from './review/ReviewContainer';
import SubscriptionDetails from './SubscriptionDetails';
import AppInstallModal from './components/Install';
import Uninstall from './components/Uninstall';
import { withConfigContext } from '../../../../../../../../components/context/ConfigContext';
import { handleApiError } from '../../../../../../../../services/utils/errorHandler';
import ReviewContainer from './components/ReviewContainer';
import SubscriptionDetails from './components/SubscriptionDetails';
const { Title, Text, Paragraph } = Typography;
const { TabPane } = Tabs;
@ -175,7 +175,7 @@ class ReleaseView extends React.Component {
onClose={this.closeAppOperationModal}
onInstall={this.appOperation}
/>
<AppUninstallModal
<Uninstall
uuid={release.uuid}
loading={this.state.loading}
visible={this.state.appUninstallModalVisible}
@ -220,7 +220,7 @@ class ReleaseView extends React.Component {
<Tabs>
<TabPane tab="App" key="1">
<Row>
<ImgViewer images={release.screenshots} />
<ImageViewer images={release.screenshots} />
</Row>
<Divider />
<Paragraph

@ -17,13 +17,13 @@
*/
import React from 'react';
import '../../../../App.css';
import '../../../../../../App.css';
import { Skeleton, Typography, Row, Col, Card, Breadcrumb, Icon } from 'antd';
import ReleaseView from '../../../../components/apps/release/ReleaseView';
import ReleaseView from './components/ReleaseView';
import axios from 'axios';
import { withConfigContext } from '../../../../context/ConfigContext';
import { withConfigContext } from '../../../../../../components/context/ConfigContext';
import { Link } from 'react-router-dom';
import { handleApiError } from '../../../../js/Utils';
import { handleApiError } from '../../../../../../services/utils/errorHandler';
const { Title } = Typography;

@ -28,9 +28,9 @@ import {
Checkbox,
notification,
} from 'antd';
import './Login.css';
import './styles.css';
import axios from 'axios';
import { withConfigContext } from '../context/ConfigContext';
import { withConfigContext } from '../../components/context/ConfigContext';
const { Title } = Typography;
const { Text } = Typography;

@ -50,6 +50,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>${maven.checkstyle.vesion}</version>
</plugin>
</plugins>
<pluginManagement>

@ -32,9 +32,8 @@
<SchedulerTaskEnabled>true</SchedulerTaskEnabled>
<PushNotificationProviders>
<Provider>org.wso2.carbon.device.mgt.extensions.push.notification.provider.fcm.FCMBasedPushNotificationProvider</Provider>
<!--<Provider>org.wso2.carbon.device.mgt.mobile.impl.ios.apns.APNSBasedPushNotificationProvider</Provider>-->
<Provider>org.wso2.carbon.device.mgt.extensions.push.notification.provider.mqtt.MQTTBasedPushNotificationProvider</Provider>
<Provider>org.wso2.carbon.device.mgt.extensions.push.notification.provider.http.HTTPBasedPushNotificationProvider</Provider>
<Provider>org.wso2.carbon.device.mgt.extensions.push.notification.provider.http.HTTPBasedPushNotificationProvider</Provider>
<Provider>org.wso2.carbon.device.mgt.extensions.push.notification.provider.xmpp.XMPPBasedPushNotificationProvider</Provider>
</PushNotificationProviders>
</PushNotificationConfiguration>

@ -150,7 +150,7 @@ class App extends React.Component {
<ConfigContext.Provider value={this.state.config}>
<div>
<Switch>
<Redirect exact from="/entgra" to="/entgra/reports" />
<Redirect exact from="/entgra" to="/entgra/devices" />
{this.props.routes.map(route => (
<RouteWithSubRoutes key={route.path} {...route} />
))}

@ -32,6 +32,18 @@ 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/Configurations/scenes/Certificates';
import Devices from './scenes/Home/scenes/Devices';
import ViewPolicy from './scenes/Home/scenes/Policies/scenes/ViewPolicy';
import EditSelectedPolicy from './scenes/Home/scenes/Policies/scenes/EditSelectedPolicy';
const routes = [
{
@ -44,28 +56,18 @@ 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/geo',
{
path: '/entgra/devices',
component: Devices,
exact: true,
},
{
path: '/entgra/devices/enroll',
component: DeviceEnroll,
exact: true,
},
{
path: '/entgra/geo/history/:deviceType/:deviceIdentifier',
component: Geo,
exact: true,
},
@ -74,41 +76,56 @@ 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/policy/view/:policyId',
component: ViewPolicy,
exact: true,
},
{
path: '/entgra/policy/edit/:policyId',
component: EditSelectedPolicy,
exact: true,
},
{
path: '/entgra/roles',
component: Roles,
exact: true,
},
{
path: '/entgra/devicetypes',
component: DeviceTypes,
exact: true,
},
{
path: '/entgra/configurations/certificates',
component: Certificates,
exact: true,
},
{
path: '/entgra/notifications',
component: Notifications,
exact: true,
},
{
path: '/entgra/reports/enrollments',
component: EnrollmentsVsUnenrollmentsReport,

@ -20,7 +20,6 @@ import React from 'react';
import { Layout, Menu, Icon } from 'antd';
import { Switch, Link } from 'react-router-dom';
import RouteWithSubRoutes from '../../components/RouteWithSubRoutes';
import { Redirect } from 'react-router';
import './styles.css';
import { withConfigContext } from '../../components/ConfigContext';
import Logout from './components/Logout';
@ -72,43 +71,23 @@ class Home extends React.Component {
marginRight: 110,
}}
>
{/* <SubMenu*/}
{/* key="devices"*/}
{/* title={*/}
{/* <span>*/}
{/* <Icon type="appstore" />*/}
{/* <span>Devices</span>*/}
{/* </span>*/}
{/* }*/}
{/* >*/}
{/* <Menu.Item key="devices">*/}
{/* <Link to="/entgra/devices">*/}
{/* <span>View</span>*/}
{/* </Link>*/}
{/* </Menu.Item>*/}
{/* <Menu.Item key="deviceEnroll">*/}
{/* <Link to="/entgra/devices/enroll">*/}
{/* <span>Enroll</span>*/}
{/* </Link>*/}
{/* </Menu.Item>*/}
{/* </SubMenu>*/}
<SubMenu
key="geo"
key="devices"
title={
<span>
<Icon type="environment" />
<span>Geo</span>
<Icon type="appstore" />
<span>Devices</span>
</span>
}
>
<Menu.Item key="singleDevice">
<Link to="/entgra/geo">
<span>Single Device View</span>
<Menu.Item key="devices">
<Link to="/entgra/devices">
<span>View</span>
</Link>
</Menu.Item>
<Menu.Item key="deviceGroup">
<Link to="#">
<span>Device Group View</span>
<Menu.Item key="deviceEnroll">
<Link to="/entgra/devices/enroll">
<span>Enroll</span>
</Link>
</Menu.Item>
</SubMenu>
@ -118,66 +97,70 @@ class Home extends React.Component {
<span>Reports</span>
</Link>
</Menu.Item>
{/* <Menu.Item key="groups">*/}
{/* <Link to="/entgra/groups">*/}
{/* <Icon type="deployment-unit" />*/}
{/* <span>Groups</span>*/}
{/* </Link>*/}
{/* </Menu.Item>*/}
{/* <Menu.Item key="users">*/}
{/* <Link to="/entgra/users">*/}
{/* <Icon type="user" />*/}
{/* <span>Users</span>*/}
{/* </Link>*/}
{/* </Menu.Item>*/}
{/* <SubMenu*/}
{/* key="policies"*/}
{/* title={*/}
{/* <span>*/}
{/* <Icon type="audit" />*/}
{/* <span>Policies</span>*/}
{/* </span>*/}
{/* }*/}
{/* >*/}
{/* <Menu.Item key="policiesList">*/}
{/* <Link to="/entgra/policies">*/}
{/* <span>View</span>*/}
{/* </Link>*/}
{/* </Menu.Item>*/}
{/* <Menu.Item key="addPolicy">*/}
{/* <Link to="/entgra/policy/add">*/}
{/* <span>Add New Policy</span>*/}
{/* </Link>*/}
{/* </Menu.Item>*/}
{/* </SubMenu>*/}
{/* <Menu.Item key="roles">*/}
{/* <Link to="/entgra/roles">*/}
{/* <Icon type="book" />*/}
{/* <span>Roles</span>*/}
{/* </Link>*/}
{/* </Menu.Item>*/}
{/* <Menu.Item key="devicetypes">*/}
{/* <Link to="/entgra/devicetypes">*/}
{/* <Icon type="desktop" />*/}
{/* <span>Device Types</span>*/}
{/* </Link>*/}
{/* </Menu.Item>*/}
{/* <SubMenu*/}
{/* key="configurations"*/}
{/* title={*/}
{/* <span>*/}
{/* <Icon type="setting" />*/}
{/* <span>Configurations</span>*/}
{/* </span>*/}
{/* }*/}
{/* >*/}
{/* <Menu.Item key="certificates">*/}
{/* <Link to="/entgra/certificates">*/}
{/* <span>Certificates</span>*/}
{/* </Link>*/}
{/* </Menu.Item>*/}
{/* </SubMenu>*/}
<Menu.Item key="trigger"></Menu.Item>
<Menu.Item key="groups">
<Link to="/entgra/groups">
<Icon type="deployment-unit" />
<span>Groups</span>
</Link>
</Menu.Item>
<Menu.Item key="users">
<Link to="/entgra/users">
<Icon type="user" />
<span>Users</span>
</Link>
</Menu.Item>
<SubMenu
key="policies"
title={
<span>
<Icon type="audit" />
<span>Policies</span>
</span>
}
>
<Menu.Item key="policiesList">
<Link to="/entgra/policies">
<span>View</span>
</Link>
</Menu.Item>
<Menu.Item key="addPolicy">
<Link to="/entgra/policy/add">
<span>Add New Policy</span>
</Link>
</Menu.Item>
</SubMenu>
<Menu.Item key="roles">
<Link to="/entgra/roles">
<Icon type="book" />
<span>Roles</span>
</Link>
</Menu.Item>
<Menu.Item key="devicetypes">
<Link to="/entgra/devicetypes">
<Icon type="desktop" />
<span>Device Types</span>
</Link>
</Menu.Item>
<SubMenu
key="configurations"
title={
<span>
<Icon type="setting" />
<span>Configurations</span>
</span>
}
>
<Menu.Item key="certificates">
<Link to="/entgra/configurations/certificates">
<span>Certificates</span>
</Link>
</Menu.Item>
</SubMenu>
<Menu.Item className="profile" key="Notifications">
<Link to="/entgra/notifications">
<span>Notifications</span>
</Link>
</Menu.Item>
<SubMenu
className="profile"
title={
@ -194,7 +177,6 @@ class Home extends React.Component {
<Content>
<Switch>
<Redirect exact from="/entgra/devices" to="/entgra/reports" />
{this.state.routes.map(route => (
<RouteWithSubRoutes key={route.path} {...route} />
))}

@ -30,7 +30,7 @@ import {
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 { withConfigContext } from '../../../../../../../../components/ConfigContext';
import Moment from 'react-moment';
const { Paragraph, Text } = Typography;

@ -33,6 +33,7 @@ import TimeAgo from 'javascript-time-ago';
import en from 'javascript-time-ago/locale/en';
import { withConfigContext } from '../../../../../../components/ConfigContext';
import BulkActionBar from './components/BulkActionBar';
import { Link } from 'react-router-dom';
let config = null;
@ -126,6 +127,20 @@ const columns = [
},
// todo add filtering options
},
{
title: '',
dataIndex: 'deviceIdentifier',
key: 'actions',
// eslint-disable-next-line react/display-name
render: (data, row) => {
const { type, deviceIdentifier } = row;
return (
<Link to={`/entgra/geo/history/${type}/${deviceIdentifier}`}>
<Icon type="environment" /> Location History
</Link>
);
},
},
];
const getTimeAgo = time => {

@ -57,60 +57,42 @@ class CustomMap extends Component {
/**
* Polyline draw for historical locations
* @param locationData - location data object
* @param locationHistorySnapshots - location data object
* @returns content
*/
polylineMarker = locationData => {
const locationPoints = [...locationData];
polylineMarker = locationHistorySnapshots => {
const polyLines = [];
locationHistorySnapshots.forEach(locationHistorySnapshots => {
polyLines.push(
<Polyline
key={polyLines.length}
color="#414042"
positions={locationHistorySnapshots.map(snapshot => {
return [snapshot.latitude, snapshot.longitude];
})}
smoothFactor={10}
weight={5}
/>,
);
});
while (locationPoints.length > 0) {
// Array to store positions for next polyline
const positions = [];
// Make a copy of remaining location points
const cachedLocationPoints = [...locationPoints];
// Iterate the remaining cached locations
for (let i = 0; i < cachedLocationPoints.length; i++) {
positions.push([
cachedLocationPoints[i].latitude,
cachedLocationPoints[i].longitude,
]);
const currentPoint = cachedLocationPoints[i];
// Remove the current location from the locationPoints
locationPoints.shift();
if (i < cachedLocationPoints.length - 1) {
const nextPoint = cachedLocationPoints[i + 1];
// Draw a dashed line for long for location points with long interval
if (
nextPoint.timestamp - currentPoint.timestamp >
this.props.context.geoMap.timeout * 1000
) {
// Create a dashed line
polyLines.push(
<Polyline
key={polyLines.length}
color="#414042"
positions={[
[currentPoint.latitude, currentPoint.longitude],
[nextPoint.latitude, nextPoint.longitude],
]}
smoothFactor={10}
weight={5}
dashArray="7"
/>,
);
break;
}
}
}
// Create a polyline from provided positions
for (let i = 1; i < locationHistorySnapshots.length; i++) {
const startPosition = locationHistorySnapshots[i][0];
const endingPosition =
locationHistorySnapshots[i - 1][
locationHistorySnapshots[i - 1].length - 1
];
polyLines.push(
<Polyline
key={polyLines.length}
color="#414042"
positions={positions}
positions={[
[startPosition.latitude, startPosition.longitude],
[endingPosition.latitude, endingPosition.longitude],
]}
smoothFactor={10}
weight={5}
dashArray="7"
/>,
);
}
@ -125,22 +107,23 @@ class CustomMap extends Component {
};
render() {
const locationData = this.props.locationData;
const locationHistorySnapshots = this.props.locationHistorySnapshots;
const config = this.props.context;
const attribution = config.geoMap.attribution;
const url = config.geoMap.url;
const startingPoint = [locationData[0].latitude, locationData[0].longitude];
const endPoint = [
locationData[locationData.length - 1].latitude,
locationData[locationData.length - 1].longitude,
];
const firstSnapshot = locationHistorySnapshots[0][0];
const lastSnapshotList =
locationHistorySnapshots[locationHistorySnapshots.length - 1];
const lastSnapshot = lastSnapshotList[lastSnapshotList.length - 1];
const startingPoint = [firstSnapshot.latitude, firstSnapshot.longitude];
const endPoint = [lastSnapshot.latitude, lastSnapshot.longitude];
const zoom = config.geoMap.defaultZoomLevel;
return (
<div style={{ backgroundColor: '#ffffff', borderRadius: 5, padding: 5 }}>
<Map center={startingPoint} zoom={zoom}>
<TileLayer url={url} attribution={attribution} />
<Fragment>
{this.polylineMarker(locationData)}
{this.polylineMarker(locationHistorySnapshots)}
<Marker icon={pinStart} position={startingPoint}>
<Popup keepInView={true}>Start</Popup>
<Tooltip direction="top" permanent={true}>

@ -18,16 +18,7 @@
import React from 'react';
import moment from 'moment';
import {
Button,
Select,
message,
notification,
Tag,
Tooltip,
Empty,
DatePicker,
} from 'antd';
import { message, notification, Empty, DatePicker } from 'antd';
import axios from 'axios';
import { withConfigContext } from '../../../../../../components/ConfigContext';
import GeoCustomMap from '../CustomMap';
@ -53,7 +44,7 @@ class GeoDashboard extends React.Component {
this.state = {
deviceData: [],
selectedDevice: '',
locationData: [],
locationHistorySnapshots: [],
loading: false,
start: start,
end: end,
@ -62,119 +53,23 @@ class GeoDashboard extends React.Component {
}
componentDidMount() {
this.fetchDevices();
// this.fetchCurrentLocation();
this.fetchLocationHistory();
}
/**
* Call back on apply button in the date time picker
* @param startDate - start date
* @param endDate - end date
*/
applyCallback = (dates, dateStrings) => {
console.log('Apply Callback');
this.setState({
start: dateStrings[0],
end: dateStrings[1],
});
};
/**
* Api call handle on fetch location date button
*/
handleApiCall = () => {
if (this.state.selectedDevice && this.state.start && this.state.end) {
const toInMills = moment(this.state.end);
const fromInMills = moment(this.state.start);
const deviceType = this.state.selectedDevice.type;
const deviceId = this.state.selectedDevice.deviceIdentifier;
const config = this.props.context;
this.setState({ loading: true });
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/devices/' +
deviceType +
'/' +
deviceId +
'/location-history?' +
'from=' +
fromInMills +
'&to=' +
toInMills,
)
.then(res => {
if (res.status === 200) {
const locationData = JSON.parse(res.data.data);
this.setState({
loading: false,
locationData,
});
}
})
.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 fetch locations......',
});
}
this.setState({ loading: false });
console.log(error);
});
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Please provide a date range and a device.',
});
}
};
/**
* Device dropdown list handler
* @param e - selected device data
*/
handleDeviceList = e => {
let selectedDevice = this.state.deviceData[e];
this.setState({ selectedDevice });
};
/**
* render fetch location button
*/
fetchLocationButton = () => {
let flag;
let toolTip = '';
if (this.state.selectedDevice === '') {
flag = true;
toolTip = 'Please select a Device';
}
return (
<Tooltip placement="rightBottom" title={toolTip}>
<Button disabled={flag} onClick={this.handleApiCall}>
Fetch Locations
</Button>
</Tooltip>
);
};
/**
* fetches device data to populate the dropdown list
*/
fetchDevices = () => {
fetchLocationHistory = () => {
const toInMills = moment(this.state.end);
const fromInMills = moment(this.state.start);
const config = this.props.context;
this.setState({ loading: true });
@ -183,14 +78,21 @@ class GeoDashboard extends React.Component {
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/devices?status=ACTIVE&status=INACTIVE&status=UNCLAIMED&status=UNREACHABLE&status=SUSPENDED&' +
'status=DISENROLLMENT_REQUESTED&status=BLOCKED&status=CREATED',
'/devices/' +
this.props.deviceType +
'/' +
this.props.deviceIdentifier +
'/location-history?' +
'from=' +
fromInMills +
'&to=' +
toInMills,
)
.then(res => {
if (res.status === 200) {
this.setState({
loading: false,
deviceData: res.data.data.devices,
locationHistorySnapshots: res.data.data.locationHistorySnapshots,
});
}
})
@ -202,11 +104,12 @@ class GeoDashboard extends React.Component {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load devices.',
description: 'Error occurred while trying to fetch locations......',
});
}
this.setState({ loading: false });
console.log(error);
});
};
@ -235,76 +138,29 @@ class GeoDashboard extends React.Component {
'1 Month': [moment(start).subtract(1, 'months'), moment(end)],
};
let { deviceData } = this.state;
return (
<div className="controllerDiv">
<RangePicker
ranges={ranges}
style={{ marginRight: 20 }}
style={{ marginRight: 20, width: 400 }}
showTime
format="YYYY-MM-DD HH:mm:ss"
defaultValue={[this.state.start, this.state.end]}
onChange={this.applyCallback}
onOk={this.fetchLocationHistory}
size="large"
/>
<Select
showSearch
style={{ width: 220, marginRight: 20 }}
placeholder="Select a Device"
optionFilterProp="children"
onChange={this.handleDeviceList}
filterOption={(input, option) =>
option.props.children.toLowerCase().indexOf(input.toLowerCase()) >=
0
}
>
{deviceData.map((device, index) => (
<Select.Option key={index} value={index}>
{device.name + ' '}
{this.statusTag(device)}
</Select.Option>
))}
</Select>
{this.fetchLocationButton()}
</div>
);
};
/**
* Creates color based tags on device status
* @param device - device object
*/
statusTag = device => {
const status = device.enrolmentInfo.status.toLowerCase();
let color = '#f9ca24';
switch (status) {
case 'active':
color = '#badc58';
break;
case 'created':
color = '#6ab04c';
break;
case 'inactive':
color = '#f9ca24';
break;
case 'blocked':
color = '#636e72';
break;
}
return <Tag color={color}>{status}</Tag>;
};
render() {
const locationData = [...this.state.locationData];
const { locationHistorySnapshots } = this.state;
return (
<div className="container">
{this.controllerBar()}
{locationData.length > 0 ? (
<GeoCustomMap locationData={locationData} />
{locationHistorySnapshots.length > 0 ? (
<GeoCustomMap locationHistorySnapshots={locationHistorySnapshots} />
) : (
<Empty />
)}

@ -32,6 +32,7 @@ class Geo extends React.Component {
}
render() {
const { deviceIdentifier, deviceType } = this.props.match.params;
return (
<div>
<PageHeader style={{ paddingTop: 0 }}>
@ -41,11 +42,14 @@ class Geo extends React.Component {
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Geo</Breadcrumb.Item>
<Breadcrumb.Item>
<Link to="/entgra">Devices</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>{`Location History - ${deviceType} / ${deviceIdentifier}`}</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap">
<h3>Geo</h3>
<Paragraph>Geo Location Service</Paragraph>
<h3>Location History </h3>
<Paragraph>{`${deviceType} / ${deviceIdentifier}`}</Paragraph>
</div>
</PageHeader>
<div
@ -56,7 +60,10 @@ class Geo extends React.Component {
alignItems: 'center',
}}
>
<GeoDashboard />
<GeoDashboard
deviceIdentifier={deviceIdentifier}
deviceType={deviceType}
/>
</div>
</div>
);

@ -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 (
<span style={{ fontSize: 20, color: color, textAlign: 'center' }}>
<Icon type={icon} theme={theme} />
</span>
);
},
},
{
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 (
<div>
<Table
columns={columns}
rowKey={record => 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}
/>
</div>
);
}
}
export default withConfigContext(NotificationsTable);

@ -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 (
<div>
<PageHeader style={{ paddingTop: 0 }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/entgra">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Notifications</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap" style={{ marginBottom: '10px' }}>
<h3>DEVICE NOTIFICATIONS</h3>
<Radio.Group
onChange={this.handleModeChange}
defaultValue={'all'}
value={notificationType}
style={{ marginBottom: 8, marginRight: 5 }}
>
<Radio.Button value={'all'}>All Notifications</Radio.Button>
<Radio.Button value={'unread'}>Unread Notifications</Radio.Button>
</Radio.Group>
<Button
type="primary"
style={{
marginRight: 10,
marginBottom: 8,
display: notificationType === 'unread' ? 'inline' : 'none',
}}
onClick={this.clearNotifications}
>
Clear All Notifications
</Button>
<div style={{ backgroundColor: '#ffffff', borderRadius: 5 }}>
<NotificationsTable notificationType={notificationType} />
</div>
</div>
</PageHeader>
<div
style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}
></div>
</div>
);
}
}
export default withConfigContext(Notifications);

@ -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 (
<div>
<div>
<Radio.Group
defaultValue={'roleSelector'}
onChange={this.handleSetUserRoleFormItem}
>
<Radio value="roleSelector">Set User role(s)</Radio>
<Radio value="userSelector">Set User(s)</Radio>
</Radio.Group>
<div
id={'roleSelector'}
ref={this.roleSelector}
style={{ display: 'block' }}
>
<Form.Item>
{getFieldDecorator('roles', {
initialValue: 'ANY',
})(
<Select
mode="multiple"
style={{ width: '100%' }}
defaultActiveFirstOption={true}
>
<Option value={'ANY'}>Any</Option>
{this.state.roles.map(role => (
<Option key={role} value={role}>
{role}
</Option>
))}
</Select>,
)}
</Form.Item>
</div>
<div
id={'userSelector'}
ref={this.userSelector}
style={{ display: 'none' }}
>
<Form.Item>
{getFieldDecorator('users', {})(
<Select
mode="multiple"
style={{ width: '100%' }}
onSearch={this.getUsersList}
>
{this.state.users.map(user => (
<Option key={user.username} value={user.username}>
{user.username}
</Option>
))}
</Select>,
)}
</Form.Item>
</div>
</div>
<Form.Item label={'Select Groups'} style={{ display: 'block' }}>
{getFieldDecorator('deviceGroups', {
initialValue: 'NONE',
})(
<Select mode="multiple" style={{ width: '100%' }}>
<Option value={'NONE'}>NONE</Option>
{this.state.groups.map(group => (
<Option key={group.name} value={group.name}>
{group.name}
</Option>
))}
</Select>,
)}
</Form.Item>
<Col span={16} offset={20}>
<div style={{ marginTop: 24 }}>
<Button style={{ marginRight: 8 }} onClick={this.props.getPrevStep}>
Back
</Button>
<Button
type="primary"
onClick={e => this.onHandleContinue(e, 'groupData')}
>
Continue
</Button>
</div>
</Col>
</div>
);
}
}
export default withConfigContext(Form.create()(AssignGroups));

@ -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,
})(
<Select>
@ -469,9 +527,21 @@ class ConfigureProfile extends React.Component {
<div>
<div>
{item.optional.subPanel.map((panel, i) => {
subPanelList[panel.others.itemSwitch] =
panel.others.itemPayload;
if (
subPanelpayloadAttributes.hasOwnProperty(panelId)
) {
Object.assign(
subPanelpayloadAttributes[panelId],
subPanelList,
);
} else {
subPanelpayloadAttributes[panelId] = subPanelList;
}
return (
<div key={i}>
{this.getPanelItems(panel.panelItem)}
{this.getPanelItems(panel.panelItem, panelId)}
</div>
);
})}
@ -499,7 +569,6 @@ class ConfigureProfile extends React.Component {
)}
</Form.Item>
);
case 'textArea':
return (
<Form.Item
@ -514,7 +583,9 @@ class ConfigureProfile extends React.Component {
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {})(
{getFieldDecorator(`${item.id}`, {
initialValue: null,
})(
<TextArea
placeholder={item.optional.placeholder}
rows={item.optional.row}
@ -671,13 +742,9 @@ class ConfigureProfile extends React.Component {
render() {
const { policyUIConfigurationsList } = this.props;
const { getFieldDecorator } = this.props.form;
return (
<div className="tab-container">
{/* <div>*/}
{/* <Select style={{ width: 200 }}>*/}
{/* {this.getOptionForTimeSelectors(1440, 1410, 30)}*/}
{/* </Select>*/}
{/* </div>*/}
<Tabs tabPosition={'left'} size={'large'}>
{policyUIConfigurationsList.map((element, i) => {
return (
@ -717,9 +784,39 @@ class ConfigureProfile extends React.Component {
</div>
}
>
<div>
<Form>{this.getPanelItems(panel.panelItem)}</Form>
</div>
{panel.hasOwnProperty('panelItem') && (
<div>
<Form name={panel.panelId}>
<Form.Item style={{ display: 'none' }}>
{getFieldDecorator(`${panel.panelId}`, {
initialValue: ' ',
})(<Input />)}
</Form.Item>
{this.getPanelItems(
panel.panelItem,
panel.panelId,
)}
</Form>
</div>
)}
{panel.hasOwnProperty('subFormLists') && (
<div>
{Object.values(panel.subFormLists).map(
(form, i) => {
return (
<Form name={form.id} key={i}>
<Form.Item style={{ display: 'none' }}>
{getFieldDecorator(`${form.id}`, {
initialValue: ' ',
})(<Input />)}
</Form.Item>
{this.getPanelItems(form.panelItem)}
</Form>
);
},
)}
</div>
)}
</Collapse.Panel>
</Collapse>
</div>
@ -729,6 +826,19 @@ class ConfigureProfile extends React.Component {
);
})}
</Tabs>
<Col span={16} offset={20}>
<div style={{ marginTop: 24 }}>
<Button style={{ marginRight: 8 }} onClick={this.props.getPrevStep}>
Back
</Button>
<Button
type="primary"
onClick={e => this.onHandleContinue(e, 'configureProfileData')}
>
Continue
</Button>
</div>
</Col>
</div>
);
}

@ -0,0 +1,87 @@
/*
* 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, Input } from 'antd';
const { TextArea } = Input;
class PublishDevices extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
}
onClickSavePolicy = (event, isPublish, formName) => {
this.props.form.validateFields((err, values) => {
if (!err) {
values.active = isPublish;
this.props.getPolicyPayloadData(formName, values);
}
});
};
render() {
const { getFieldDecorator } = this.props.form;
return (
<div>
<Form.Item
label={'Set a name to your policy *'}
style={{ display: 'block' }}
>
{getFieldDecorator('policyName', {
rules: [
{
pattern: new RegExp('^.{1,30}$'),
message: 'Should be 1-to-30 characters long',
},
],
})(<Input placeholder={'Should be 1 to 30 characters long'} />)}
</Form.Item>
<Form.Item label={'Add a Description'} style={{ display: 'block' }}>
{getFieldDecorator('description', {})(<TextArea rows={8} />)}
</Form.Item>
<Col span={16} offset={18}>
<div style={{ marginTop: 24 }}>
<Button style={{ marginRight: 8 }} onClick={this.props.getPrevStep}>
Back
</Button>
<Button
type="primary"
style={{ marginRight: 8 }}
onClick={e =>
this.onClickSavePolicy(e, true, 'publishDevicesData')
}
>
Save & Publish
</Button>
<Button
type="primary"
onClick={e =>
this.onClickSavePolicy(e, false, 'publishDevicesData')
}
>
Save
</Button>
</div>
</Col>
</div>
);
}
}
export default withConfigContext(Form.create()(PublishDevices));

@ -19,34 +19,32 @@
import React from 'react';
import axios from 'axios';
import { Card, Col, Icon, message, notification, Row } 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';
class SelectPlatform extends React.Component {
constructor(props) {
super(props);
TimeAgo.addLocale(en);
this.config = this.props.context;
this.state = {
data: [],
pagination: {},
loading: false,
selectedRows: [],
};
}
componentDidMount() {
this.fetchUsers();
this.getDeviceTypes();
}
onClickCard = (e, type) => {
onClickCard = (e, type, formname) => {
this.props.getPolicyConfigJson(type);
let deviceType = {
deviceType: type,
};
this.props.getPolicyPayloadData(formname, deviceType);
};
// fetch data from api
fetchUsers = (params = {}) => {
getDeviceTypes() {
this.setState({ loading: true });
let apiUrl =
@ -55,16 +53,14 @@ class SelectPlatform extends React.Component {
this.config.serverConfig.invoker.deviceMgt +
'/device-types';
// send request to the invokerss
// send request to the invokers
axios
.get(apiUrl)
.then(res => {
if (res.status === 200) {
const pagination = { ...this.state.pagination };
this.setState({
loading: false,
data: JSON.parse(res.data.data),
pagination,
});
}
})
@ -83,22 +79,7 @@ class SelectPlatform extends React.Component {
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,
});
};
}
render() {
const { data } = this.state;
@ -110,10 +91,12 @@ class SelectPlatform extends React.Component {
size="default"
style={{ width: 150 }}
bordered={true}
onClick={e => this.onClickCard(e, data.name)}
onClick={e =>
this.onClickCard(e, data.name, 'selectedPlatformData')
}
cover={
<Icon
type="android"
type={data.name === 'ios' ? 'apple' : data.name}
key="device-types"
style={{
color: '#ffffff',

@ -0,0 +1,171 @@
/*
* 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,
Col,
Form,
Icon,
message,
notification,
Radio,
Select,
Tooltip,
} from 'antd';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import axios from 'axios';
const { Option } = Select;
class SelectPolicyType extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
this.state = {
correctivePoliciesList: [],
};
}
componentDidMount() {
this.fetchPolicies();
}
// generate payload using Select policy type
onHandleContinue = (e, formName) => {
this.props.form.validateFields((err, values) => {
if (!err) {
if (values.correctiveActions === 'NONE') {
values.correctiveActions = [];
}
this.props.getPolicyPayloadData(formName, values);
this.props.getNextStep();
}
});
};
fetchPolicies = () => {
let apiUrl =
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/policies';
// send request to the invokerss
axios
.get(apiUrl)
.then(res => {
if (res.status === 200) {
let policies = res.data.data.policies;
let correctivePolicies = [];
for (let i = 0; i < policies.length; i++) {
if (policies[i].policyType === 'CORRECTIVE') {
correctivePolicies.push(
<Option key={policies[i].profileId}>
{policies[i].policyName}
</Option>,
);
}
}
this.setState({
correctivePoliciesList: correctivePolicies,
});
}
})
.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 });
});
};
handlePolicyTypes = event => {
if (event.target.value === 'GENERAL') {
document.getElementById('generalPolicySubPanel').style.display = 'block';
} else {
document.getElementById('generalPolicySubPanel').style.display = 'none';
}
};
render() {
const { getFieldDecorator } = this.props.form;
return (
<div>
<Form.Item style={{ display: 'block' }}>
{getFieldDecorator('policyType', {
initialValue: 'GENERAL',
})(
<Radio.Group onChange={this.handlePolicyTypes}>
<Radio value="GENERAL">General Policy</Radio>
<Radio value="CORRECTIVE">Corrective Policy</Radio>
</Radio.Group>,
)}
</Form.Item>
<div id="generalPolicySubPanel" style={{ display: 'block' }}>
<Form.Item
label={
<span>
Select Corrective Policy&nbsp;
<Tooltip
title={
'Select the corrective policy to be applied when this general policy is violated'
}
placement="right"
>
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
>
{getFieldDecorator('correctiveActions', {
initialValue: 'NONE',
})(
<Select style={{ width: '100%' }}>
<Option value="NONE">None</Option>
{this.state.correctivePoliciesList}
</Select>,
)}
</Form.Item>
</div>
<Col span={16} offset={20}>
<div style={{ marginTop: 24 }}>
<Button style={{ marginRight: 8 }} onClick={this.props.getPrevStep}>
Back
</Button>
<Button
type="primary"
onClick={e => this.onHandleContinue(e, 'policyTypeData')}
>
Continue
</Button>
</div>
</Col>
</div>
);
}
}
export default withConfigContext(Form.create()(SelectPolicyType));

@ -17,19 +17,13 @@
*/
import React from 'react';
import {
Button,
Form,
Row,
Col,
Card,
Steps,
message,
notification,
} from 'antd';
import { Form, Row, Col, Card, Steps, message, notification } from 'antd';
import { withConfigContext } from '../../../../../../components/ConfigContext';
import SelectPlatform from './components/SelectPlatform';
import ConfigureProfile from './components/ConfigureProfile';
import SelectPolicyType from './components/SelectPolicyType';
import AssignGroups from './components/AssignGroups';
import PublishDevices from './components/PublishDevices';
import axios from 'axios';
const { Step } = Steps;
@ -41,9 +35,49 @@ class AddPolicy extends React.Component {
currentStepIndex: 0,
isLoading: false,
policyUIConfigurationsList: [],
newPolicyPayload: { compliance: 'enforce' },
policyProfile: {},
payloadData: {},
};
}
getPolicyPayloadData = (dataName, dataValue) => {
Object.defineProperty(this.state.payloadData, dataName, {
value: dataValue,
writable: true,
});
if (dataName === 'publishDevicesData') {
this.createPayload();
}
};
createPayload = () => {
const {
publishDevicesData,
selectedPlatformData,
configureProfileData,
policyTypeData,
groupData,
} = this.state.payloadData;
const profile = {
profileName: publishDevicesData.policyName,
deviceType: selectedPlatformData.deviceType,
profileFeaturesList: configureProfileData,
};
const payload = {
policyName: publishDevicesData.policyName,
description: publishDevicesData.description,
compliance: 'enforce',
ownershipType: null,
active: publishDevicesData.active,
...policyTypeData,
profile: profile,
...groupData,
};
this.onAddNewPolicy(JSON.stringify(payload));
};
getPolicyConfigJson = type => {
this.setState({ isLoading: true });
@ -82,12 +116,46 @@ class AddPolicy extends React.Component {
});
};
onHandleNext = () => {
onAddNewPolicy = value => {
axios
.post(
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/policies/',
value,
{ headers: { 'Content-Type': 'application-json' } },
)
.then(res => {
if (res.status === 201) {
notification.success({
message: 'Done',
duration: 4,
description: 'Successfully added new Policy.',
});
}
})
.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 add New Policy.',
});
}
});
};
getNextStep = () => {
const currentStepIndex = this.state.currentStepIndex + 1;
this.setState({ currentStepIndex });
};
onHandlePrev = () => {
getPrevStep = () => {
const currentStepIndex = this.state.currentStepIndex - 1;
this.setState({ currentStepIndex });
};
@ -104,7 +172,6 @@ class AddPolicy extends React.Component {
<Step key="PolicyType" title="Select policy type" />
<Step key="AssignGroups" title="Assign to groups" />
<Step key="Publish" title="Publish to devices" />
<Step key="Result" title="Result" />
</Steps>
</Col>
<Col span={16} offset={4}>
@ -114,6 +181,7 @@ class AddPolicy extends React.Component {
>
<SelectPlatform
getPolicyConfigJson={this.getPolicyConfigJson}
getPolicyPayloadData={this.getPolicyPayloadData}
/>
</div>
<div
@ -121,40 +189,39 @@ class AddPolicy extends React.Component {
>
<ConfigureProfile
policyUIConfigurationsList={policyUIConfigurationsList}
getPolicyPayloadData={this.getPolicyPayloadData}
getPrevStep={this.getPrevStep}
getNextStep={this.getNextStep}
/>
</div>
<div
style={{ display: currentStepIndex === 2 ? 'unset' : 'none' }}
></div>
>
<SelectPolicyType
getPolicyPayloadData={this.getPolicyPayloadData}
getPrevStep={this.getPrevStep}
getNextStep={this.getNextStep}
/>
</div>
<div
style={{ display: currentStepIndex === 3 ? 'unset' : 'none' }}
></div>
>
<AssignGroups
getPolicyPayloadData={this.getPolicyPayloadData}
getPrevStep={this.getPrevStep}
getNextStep={this.getNextStep}
/>
</div>
<div
style={{ display: currentStepIndex === 4 ? 'unset' : 'none' }}
></div>
<div
style={{ display: currentStepIndex === 5 ? 'unset' : 'none' }}
></div>
>
<PublishDevices
getPolicyPayloadData={this.getPolicyPayloadData}
getPrevStep={this.getPrevStep}
/>
</div>
</Card>
</Col>
<Col span={16} offset={4}>
<div style={{ marginTop: 24 }}>
{currentStepIndex > 0 && (
<Button
style={{ marginRight: 8 }}
onClick={() => this.onHandlePrev()}
>
Previous
</Button>
)}
{currentStepIndex > 0 && currentStepIndex < 5 && (
<Button type="primary" onClick={() => this.onHandleNext()}>
Next
</Button>
)}
{currentStepIndex === 5 && <Button type="primary">Done</Button>}
</div>
</Col>
</Row>
</div>
);

@ -0,0 +1,269 @@
/*
* 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 { policyData } = this.props;
const { getFieldDecorator } = this.props.form;
let deviceGroups = null;
if (policyData.deviceGroups.length > 0) {
deviceGroups = policyData.deviceGroups;
} else {
deviceGroups = 'NONE';
}
return (
<div>
<div>
<Radio.Group
defaultValue={'roleSelector'}
onChange={this.handleSetUserRoleFormItem}
>
<Radio value="roleSelector">Set User role(s)</Radio>
<Radio value="userSelector">Set User(s)</Radio>
</Radio.Group>
<div
id={'roleSelector'}
ref={this.roleSelector}
style={{ display: 'block' }}
>
<Form.Item>
{getFieldDecorator('roles', {
initialValue: policyData.roles,
})(
<Select
mode="multiple"
style={{ width: '100%' }}
defaultActiveFirstOption={true}
>
<Option value={'ANY'}>Any</Option>
{this.state.roles.map(role => (
<Option key={role} value={role}>
{role}
</Option>
))}
</Select>,
)}
</Form.Item>
</div>
<div
id={'userSelector'}
ref={this.userSelector}
style={{ display: 'none' }}
>
<Form.Item>
{getFieldDecorator('users', {
initialValue: policyData.users,
})(
<Select
mode="multiple"
style={{ width: '100%' }}
onSearch={this.getUsersList}
>
{this.state.users.map(user => (
<Option key={user.username} value={user.username}>
{user.username}
</Option>
))}
</Select>,
)}
</Form.Item>
</div>
</div>
<Form.Item label={'Select Groups'} style={{ display: 'block' }}>
{getFieldDecorator('deviceGroups', {
initialValue: deviceGroups,
})(
<Select mode="multiple" style={{ width: '100%' }}>
<Option value={'NONE'}>NONE</Option>
{this.state.groups.map(group => (
<Option key={group.name} value={group.name}>
{group.name}
</Option>
))}
</Select>,
)}
</Form.Item>
<Col span={16} offset={20}>
<div style={{ marginTop: 24 }}>
<Button style={{ marginRight: 8 }} onClick={this.props.getPrevStep}>
Back
</Button>
<Button
type="primary"
onClick={e => this.onHandleContinue(e, 'groupData')}
>
Continue
</Button>
</div>
</Col>
</div>
);
}
}
export default withConfigContext(Form.create()(AssignGroups));

@ -0,0 +1,873 @@
/*
* 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 {
Tabs,
Row,
Col,
Switch,
Input,
Typography,
Form,
Collapse,
Checkbox,
Select,
Tooltip,
Icon,
Table,
Alert,
Upload,
Popconfirm,
Button,
Radio,
} from 'antd';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import '../../../../styles.css';
import moment from 'moment';
const { Text, Title, Paragraph } = Typography;
const { TabPane } = Tabs;
const { Option } = Select;
const { TextArea } = Input;
const subPanelpayloadAttributes = {};
class ConfigureProfile extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
this.state = {
loading: false,
activePanelKeys: [],
activeSubPanelKeys: [],
subFormList: [],
subPanelpayloadAttributes: {},
customInputDataArray: [],
inputTableDataSources: {},
};
}
setProfileInfo = e => {
let activePolicies = [];
let activePolicyFields = {};
const allFields = this.props.form.getFieldsValue();
this.props.policyFeatureList.map(element => {
activePolicies.push(element.featureCode);
let featureData = JSON.parse(element.content);
Object.keys(featureData).map(key => {
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.setState({
activePanelKeys: activePolicies,
});
};
// convert time from 24h format to 12h format
timeConverter = time => {
time = time
.toString()
.match(/^([01]\d|2[0-3])(:)([0-5]\d)(:[0-5]\d)?$/) || [time];
if (time.length > 1) {
time = time.slice(1);
time[5] = +time[0] < 12 ? ' AM' : ' PM';
time[0] = +time[0] % 12 || 12;
}
return time.join('');
};
// get Option value from start Time, end Time and time difference between 2 values
getOptionForTimeSelectors = (startTimeValue, endTimeValue, timeIncrement) => {
let timeOptions = [];
let time = new Date(
moment()
.startOf('day')
.format('YYYY/MM/DD'),
);
let tempValue = startTimeValue;
time.setMinutes(time.getMinutes() + tempValue);
let startOption = (
<Option value={String(tempValue)}>
{this.timeConverter(
`${String(time)
.split(' ')[4]
.substring(0, 5)}`,
)}
</Option>
);
timeOptions.push(startOption);
while (tempValue !== endTimeValue) {
time = new Date(
moment()
.startOf('day')
.format('YYYY/MM/DD'),
);
tempValue += timeIncrement;
if (tempValue > 1440) {
tempValue = 0;
continue;
}
time.setMinutes(time.getMinutes() + tempValue);
let option = (
<Option value={String(tempValue)}>
{this.timeConverter(
`${String(time)
.split(' ')[4]
.substring(0, 5)}`,
)}
</Option>
);
timeOptions.push(option);
}
return timeOptions;
};
// handle items which handle from radio buttons
handleRadioPanel = (e, subPanel) => {
{
subPanel.map((panel, i) => {
if (panel.value === e.target.value) {
document.getElementById(panel.value).style.display = 'block';
} else {
document.getElementById(panel.value).style.display = 'none';
}
});
}
};
// handle items which handle from select options
handleSelectedPanel = (e, subPanel) => {
{
subPanel.map((panel, i) => {
if (panel.id === e) {
document.getElementById(panel.id).style.display = 'block';
} else {
document.getElementById(panel.id).style.display = 'none';
}
});
}
};
// handle items which handle from checkbox
handleSubPanel = e => {
if (e.target.checked) {
let joined = this.state.activeSubPanelKeys.concat(e.target.id);
this.setState({ activeSubPanelKeys: joined });
} else {
let index = this.state.activeSubPanelKeys.indexOf(e.target.id);
if (index !== -1) {
this.state.activeSubPanelKeys.splice(index, 1);
let removed = this.state.activeSubPanelKeys;
this.setState({ activeSubPanelKeys: removed });
}
}
};
// handle Switch on off button
handleMainPanel = (e, ref) => {
if (e) {
let joined = this.state.activePanelKeys.concat(ref);
this.setState({ activePanelKeys: joined });
} else {
let index = this.state.activePanelKeys.indexOf(ref);
if (index !== -1) {
this.state.activePanelKeys.splice(index, 1);
let removed = this.state.activePanelKeys;
this.setState({ activePanelKeys: removed });
}
}
};
handleCustomInputTable = event => {
const { count, customInputDataArray } = this.state;
const newData = [
{
key: count,
CERT_NAME: `${event.file.name}`,
},
];
this.setState({
customInputDataArray: [...customInputDataArray, newData],
count: count + 1,
});
};
handleAdd = array => {
const { count, inputTableDataSources } = this.state;
const newData = [
{
key: count,
},
];
inputTableDataSources[array].push(newData);
Object.defineProperty(inputTableDataSources, array, {
value: inputTableDataSources[array],
});
this.setState({
inputTableDataSources,
count: count + 1,
});
};
getColumns = ({ getFieldDecorator }, arr) => {
const columnArray = [];
const actionColumn = [
{
title: '',
dataIndex: 'operation',
render: (name, row) => (
<Form.Item>
<Popconfirm title="Sure to delete?">
<a>
<Text type="danger">
<Icon type="delete" />
</Text>
</a>
</Popconfirm>
</Form.Item>
),
},
];
Object.values(arr).map((columnData, c) => {
if (columnData.type === 'input') {
const column = {
title: `${columnData.name}`,
dataIndex: `${columnData.key}`,
key: `${columnData.key}`,
render: (name, row, i) => (
<Form.Item>
{getFieldDecorator(`${columnData.key}${i}`, {})(
<Input
type={columnData.others.inputType}
placeholder={columnData.others.placeholder}
/>,
)}
</Form.Item>
),
};
columnArray.push(column);
} else if (columnData.type === 'upload') {
const column = {
title: `${columnData.name}`,
dataIndex: `${columnData.key}`,
key: `${columnData.key}`,
render: (name, row, i) => (
<Form.Item>
{getFieldDecorator(`${columnData.key}${i}`, {})(
<Upload>
<Button>
<Icon type="upload" /> Choose file
</Button>
</Upload>,
)}
</Form.Item>
),
};
columnArray.push(column);
} else if (columnData.type === 'select') {
const column = {
title: `${columnData.name}`,
dataIndex: `${columnData.key}`,
key: `${columnData.key}`,
render: (name, row, i) => (
<Form.Item>
{getFieldDecorator(`${columnData.key}${i}`, {
initialValue: columnData.others.initialDataIndex,
})(
<Select>
{columnData.others.option.map((option, i) => {
return (
<Option key={i} value={option.key}>
{option.value}
</Option>
);
})}
</Select>,
)}
</Form.Item>
),
};
columnArray.push(column);
}
});
const columns = columnArray.concat(actionColumn);
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);
}
}
});
}
this.onFieldValidate(activeFields, formname);
};
onFieldValidate = (fields, formName) => {
// validate fields and get profile features list
this.props.form.validateFields(fields, (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: this.props.deviceType,
content: content,
};
profileFeaturesList.push(feature);
}
this.props.getPolicyPayloadData(formName, profileFeaturesList);
this.props.getNextStep();
}
});
};
// generate form items
getPanelItems = (panel, panelId) => {
const { getFieldDecorator } = this.props.form;
const subPanelList = {};
return panel.map((item, k) => {
switch (item.type) {
case 'select':
if (item.optional.hasOwnProperty('subPanel')) {
return (
<div>
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
initialValue: `${item.optional.option[0].name}`,
})(
<Select
onChange={e =>
this.handleSelectedPanel(e, item.optional.subPanel)
}
>
{item.optional.option.map((option, i) => {
return (
<Option key={i} value={option.value}>
{option.name}
</Option>
);
})}
</Select>,
)}
</Form.Item>
<div className={'sub-panel-container'}>
{item.optional.subPanel.map((panel, i) => {
return (
<div
id={panel.id}
key={i}
style={
panel.id === item.optional.initialDataIndex
? { display: 'block' }
: { display: 'none' }
}
>
{this.getPanelItems(panel.panelItem)}
</div>
);
})}
</div>
</div>
);
}
return (
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
initialValue: `${item.optional.option[0].name}`,
})(
<Select>
{item.optional.option.map((option, i) => {
return (
<Option key={i} value={option.value}>
{option.name}
</Option>
);
})}
</Select>,
)}
</Form.Item>
);
case 'timeSelector':
return (
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
initialValue: item.optional.initialDataIndex,
})(
<Select>
{this.getOptionForTimeSelectors(
item.optional.firstOptionValue,
item.optional.lastOptionValue,
item.optional.valueDifference,
)}
</Select>,
)}
</Form.Item>
);
case 'input':
return (
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
rules: [
{
pattern: new RegExp(`${item.optional.rules.regex}`),
message: `${item.optional.rules.validationMsg}`,
},
],
})(<Input placeholder={item.optional.placeholder} />)}
</Form.Item>
);
case 'checkbox':
if (item.optional.hasOwnProperty('subPanel')) {
return (
<div key={k}>
<Collapse
bordered={false}
activeKey={this.state.activeSubPanelKeys}
>
<Collapse.Panel
key={item.id}
showArrow={false}
style={{ border: 0 }}
header={
<Form.Item key={k}>
{getFieldDecorator(`${item.id}`, {
valuePropName: 'checked',
initialValue: item.optional.ischecked,
})(
<Checkbox onChange={this.handleSubPanel}>
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
</Checkbox>,
)}
</Form.Item>
}
>
<div>
<div>
{item.optional.subPanel.map((panel, i) => {
subPanelList[panel.others.itemSwitch] =
panel.others.itemPayload;
if (
subPanelpayloadAttributes.hasOwnProperty(panelId)
) {
Object.assign(
subPanelpayloadAttributes[panelId],
subPanelList,
);
} else {
subPanelpayloadAttributes[panelId] = subPanelList;
}
return (
<div key={i}>
{this.getPanelItems(panel.panelItem, panelId)}
</div>
);
})}
</div>
</div>
</Collapse.Panel>
</Collapse>
</div>
);
}
return (
<Form.Item key={k}>
{getFieldDecorator(`${item.id}`, {
valuePropName: 'checked',
initialValue: item.optional.ischecked,
})(
<Checkbox>
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
</Checkbox>,
)}
</Form.Item>
);
case 'textArea':
return (
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
initialValue: null,
})(
<TextArea
placeholder={item.optional.placeholder}
rows={item.optional.row}
/>,
)}
</Form.Item>
);
case 'radioGroup':
return (
<div>
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
initialValue: `${item.optional.initialValue}`,
})(
<Radio.Group
onChange={e =>
this.handleRadioPanel(e, item.optional.radio)
}
>
{item.optional.radio.map((option, i) => {
return (
<Radio key={i} value={option.value}>
{option.name}
</Radio>
);
})}
</Radio.Group>,
)}
</Form.Item>
<div className={'sub-panel-container'}>
{item.optional.subPanel.map((panel, i) => {
return (
<div
key={i}
id={panel.id}
style={
panel.id === item.optional.initialValue
? { display: 'block' }
: { display: 'none' }
}
>
{this.getPanelItems(panel.panelItem)}
</div>
);
})}
</div>
</div>
);
case 'title':
return (
<Title key={k} level={4}>
{item.label}{' '}
</Title>
);
case 'paragraph':
return (
<Paragraph key={k} style={{ marginTop: 20 }}>
{item.label}{' '}
</Paragraph>
);
case 'alert':
return (
<Alert key={k} description={item.label} type="warning" showIcon />
);
case 'upload':
return (
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
>
{getFieldDecorator('upload', {})(
<Upload>
<Button>
<Icon type="upload" /> Click to upload
</Button>
</Upload>,
)}
</Form.Item>
);
case 'inputTable':
if (
!(`${item.optional.dataSource}` in this.state.inputTableDataSources)
) {
Object.defineProperty(
this.state.inputTableDataSources,
`${item.optional.dataSource}`,
{ value: [], writable: true },
);
}
return (
<div key={k}>
<Button
onClick={() => this.handleAdd(item.optional.dataSource)}
type="primary"
style={{ marginBottom: 16 }}
>
<Icon type="plus-circle" />
{item.optional.button.name}
</Button>
<Table
id={item.id}
dataSource={
this.state.inputTableDataSources[item.optional.dataSource]
}
columns={this.getColumns(
{ getFieldDecorator },
item.optional.columns,
)}
/>
</div>
);
case 'customInputTable':
return (
<div key={k}>
<Upload onChange={this.handleCustomInputTable}>
<Button type="primary" style={{ marginBottom: 16 }}>
<Icon type="plus-circle" />
{item.optional.button.name}
</Button>
</Upload>
<Table
id={item.id}
dataSource={this.state.customInputDataArray}
columns={this.getColumns(
{ getFieldDecorator },
item.optional.columns,
)}
/>
</div>
);
default:
return null;
}
});
};
render() {
const { policyUIConfigurationsList } = this.props;
const { getFieldDecorator } = this.props.form;
return (
<div className="tab-container">
<Tabs
tabPosition={'left'}
size={'large'}
onTabClick={this.setProfileInfo}
>
{policyUIConfigurationsList.map((element, i) => {
return (
<TabPane tab={<span>{element.name}</span>} key={i}>
{Object.values(element.panels).map((panel, j) => {
panel = panel.panel;
return (
<div key={j}>
<Collapse
bordered={false}
activeKey={this.state.activePanelKeys}
>
<Collapse.Panel
key={panel.panelId}
showArrow={false}
style={{ border: 0 }}
header={
<div>
<Row>
<Col offset={0} span={14}>
<Title level={4}> {panel.title} </Title>
</Col>
<Col offset={8} span={1}>
<Switch
checkedChildren="ON"
unCheckedChildren="OFF"
onChange={e =>
this.handleMainPanel(
e,
`${panel.panelId}`,
)
}
/>
</Col>
</Row>
<Row>{panel.description}</Row>
</div>
}
>
{panel.hasOwnProperty('panelItem') && (
<div>
<Form name={panel.panelId}>
<Form.Item style={{ display: 'none' }}>
{getFieldDecorator(`${panel.panelId}`, {
initialValue: ' ',
})(<Input />)}
</Form.Item>
{this.getPanelItems(
panel.panelItem,
panel.panelId,
)}
</Form>
</div>
)}
{panel.hasOwnProperty('subFormLists') && (
<div>
{Object.values(panel.subFormLists).map(
(form, i) => {
return (
<Form name={form.id} key={i}>
<Form.Item style={{ display: 'none' }}>
{getFieldDecorator(`${form.id}`, {
initialValue: ' ',
})(<Input />)}
</Form.Item>
{this.getPanelItems(form.panelItem)}
</Form>
);
},
)}
</div>
)}
</Collapse.Panel>
</Collapse>
</div>
);
})}
</TabPane>
);
})}
</Tabs>
<Col span={16} offset={20}>
<div style={{ marginTop: 24 }}>
<Button style={{ marginRight: 8 }} onClick={this.props.getPrevStep}>
Back
</Button>
<Button
type="primary"
onClick={e => this.onHandleContinue(e, 'configureProfileData')}
>
Continue
</Button>
</div>
</Col>
</div>
);
}
}
export default withConfigContext(Form.create()(ConfigureProfile));

@ -0,0 +1,86 @@
/*
* 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, Input } from 'antd';
const { TextArea } = Input;
class PublishDevices extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
}
onClickSavePolicy = (event, formName) => {
this.props.form.validateFields((err, values) => {
if (!err) {
this.props.getPolicyPayloadData(formName, values);
}
});
};
render() {
const { policyData } = this.props;
const { getFieldDecorator } = this.props.form;
return (
<div>
<Form.Item
label={'Set a name to your policy *'}
style={{ display: 'block' }}
>
{getFieldDecorator('policyName', {
initialValue: policyData.policyName,
rules: [
{
pattern: new RegExp('^.{1,30}$'),
message: 'Should be 1-to-30 characters long',
},
],
})(<Input placeholder={'Should be 1 to 30 characters long'} />)}
</Form.Item>
<Form.Item label={'Add a Description'} style={{ display: 'block' }}>
{getFieldDecorator('description', {
initialValue: policyData.description,
})(<TextArea rows={8} />)}
</Form.Item>
<Col span={16} offset={18}>
<div style={{ marginTop: 24 }}>
<Button style={{ marginRight: 8 }} onClick={this.props.getPrevStep}>
Back
</Button>
<Button
type="primary"
style={{ marginRight: 8 }}
onClick={e => this.onClickSavePolicy(e, 'publishDevicesData')}
>
Save & Publish
</Button>
<Button
type="primary"
onClick={e => this.onClickSavePolicy(e, 'publishDevicesData')}
>
Save
</Button>
</div>
</Col>
</div>
);
}
}
export default withConfigContext(Form.create()(PublishDevices));

@ -0,0 +1,182 @@
/*
* 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,
Col,
Form,
Icon,
message,
notification,
Radio,
Select,
Tooltip,
} from 'antd';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import axios from 'axios';
const { Option } = Select;
class SelectPolicyType extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
this.state = {
correctivePoliciesList: [],
};
}
componentDidMount() {
this.fetchPolicies();
}
// generate payload using Select policy type
onHandleContinue = (e, formName) => {
this.props.form.validateFields((err, values) => {
if (!err) {
if (
values.policyType === 'CORRECTIVE' ||
values.correctiveActions === 'NONE'
) {
values.correctiveActions = [];
}
this.props.getPolicyPayloadData(formName, values);
this.props.getNextStep();
}
});
};
fetchPolicies = () => {
let apiUrl =
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/policies';
// send request to the invokerss
axios
.get(apiUrl)
.then(res => {
if (res.status === 200) {
let policies = res.data.data.policies;
let correctivePolicies = [];
for (let i = 0; i < policies.length; i++) {
if (policies[i].policyType === 'CORRECTIVE') {
correctivePolicies.push(
<Option key={policies[i].profileId}>
{policies[i].policyName}
</Option>,
);
}
}
this.setState({
correctivePoliciesList: correctivePolicies,
});
}
})
.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 });
});
};
handlePolicyTypes = event => {
if (event.target.value === 'GENERAL') {
document.getElementById('generalPolicySubPanel').style.display = 'block';
} else {
document.getElementById('generalPolicySubPanel').style.display = 'none';
}
};
render() {
const { policyData } = this.props;
const { getFieldDecorator } = this.props.form;
let correctiveActions = null;
if (policyData.correctiveActions.length > 0) {
correctiveActions = '';
} else {
correctiveActions = 'NONE';
}
return (
<div>
<Form.Item style={{ display: 'block' }}>
{getFieldDecorator('policyType', {
initialValue: policyData.policyType,
})(
<Radio.Group onChange={this.handlePolicyTypes}>
<Radio value="GENERAL">General Policy</Radio>
<Radio value="CORRECTIVE">Corrective Policy</Radio>
</Radio.Group>,
)}
</Form.Item>
<div id="generalPolicySubPanel" style={{ display: 'block' }}>
<Form.Item
label={
<span>
Select Corrective Policy&nbsp;
<Tooltip
title={
'Select the corrective policy to be applied when this general policy is violated'
}
placement="right"
>
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
>
{getFieldDecorator('correctiveActions', {
initialValue: correctiveActions,
})(
<Select style={{ width: '100%' }}>
<Option value="NONE">None</Option>
{this.state.correctivePoliciesList}
</Select>,
)}
</Form.Item>
</div>
<Col span={16} offset={20}>
<div style={{ marginTop: 24 }}>
<Button style={{ marginRight: 8 }} onClick={this.props.getPrevStep}>
Back
</Button>
<Button
type="primary"
onClick={e => this.onHandleContinue(e, 'policyTypeData')}
>
Continue
</Button>
</div>
</Col>
</div>
);
}
}
export default withConfigContext(Form.create()(SelectPolicyType));

@ -0,0 +1,273 @@
/*
* 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 { Form, Row, Col, Card, Steps, message, notification } from 'antd';
import { withConfigContext } from '../../../../../../components/ConfigContext';
import ConfigureProfile from './components/ConfigureProfile';
import SelectPolicyType from './components/SelectPolicyType';
import AssignGroups from './components/AssignGroups';
import PublishDevices from './components/PublishDevices';
import axios from 'axios';
const { Step } = Steps;
class EditPolicy extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
this.state = {
currentStepIndex: 0,
policyUIConfigurationsList: null,
newPolicyPayload: { compliance: 'enforce' },
policyProfile: {},
payloadData: {},
policyFeatureList: [],
policyData: {},
deviceType: null,
};
}
componentDidMount() {
this.getSelectedPolicy(this.props.policyId);
}
getPolicyPayloadData = (dataName, dataValue) => {
Object.defineProperty(this.state.payloadData, dataName, {
value: dataValue,
writable: true,
});
if (dataName === 'publishDevicesData') {
this.createPayload();
}
};
createPayload = () => {
const {
publishDevicesData,
configureProfileData,
policyTypeData,
groupData,
} = this.state.payloadData;
const profile = {
profileName: publishDevicesData.policyName,
deviceType: this.state.deviceType,
profileFeaturesList: configureProfileData,
};
const payload = {
...publishDevicesData,
compliance: 'enforce',
ownershipType: null,
...policyTypeData,
profile: profile,
...groupData,
};
this.onEditPolicy(JSON.stringify(payload));
};
getSelectedPolicy = policyId => {
let apiUrl =
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/policies/' +
policyId;
// send request to the invokers
axios
.get(apiUrl)
.then(res => {
if (res.status === 200) {
this.setState({
isLoading: true,
policyData: res.data.data,
deviceType: res.data.data.profile.deviceType,
policyFeatureList: res.data.data.profile.profileFeaturesList,
});
this.getPolicyConfigJson(res.data.data.profile.deviceType);
}
})
.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 selected policy.',
});
}
});
};
getPolicyConfigJson = type => {
this.setState({ isLoading: true });
let apiUrl =
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/device-types/' +
type +
'/ui-policy-configurations';
// send request to the invokers
axios
.get(apiUrl)
.then(res => {
if (res.status === 200) {
this.setState({
policyUIConfigurationsList: 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 Policy details.',
});
}
});
};
onEditPolicy = value => {
axios
.put(
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/policies/' +
this.props.policyId,
value,
{ headers: { 'Content-Type': 'application-json' } },
)
.then(res => {
if (res.status === 200) {
notification.success({
message: 'Done',
duration: 4,
description: 'Successfully Updated the Policy.',
});
}
})
.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 Updated the Policy.',
});
}
});
};
getNextStep = () => {
const currentStepIndex = this.state.currentStepIndex + 1;
this.setState({ currentStepIndex });
};
getPrevStep = () => {
const currentStepIndex = this.state.currentStepIndex - 1;
this.setState({ currentStepIndex });
};
render() {
const {
currentStepIndex,
policyUIConfigurationsList,
policyFeatureList,
policyData,
deviceType,
} = this.state;
return (
<div>
{policyUIConfigurationsList != null && (
<Row>
<Col span={20} offset={2}>
<Steps style={{ minHeight: 32 }} current={currentStepIndex}>
<Step key="ProfileConfigure" title="Configure profile" />
<Step key="PolicyType" title="Select policy type" />
<Step key="AssignGroups" title="Assign to groups" />
<Step key="Publish" title="Publish to devices" />
</Steps>
</Col>
<Col span={16} offset={4}>
<Card style={{ marginTop: 24 }}>
<div
style={{ display: currentStepIndex === 0 ? 'unset' : 'none' }}
>
<ConfigureProfile
policyUIConfigurationsList={policyUIConfigurationsList}
getPolicyPayloadData={this.getPolicyPayloadData}
getNextStep={this.getNextStep}
policyFeatureList={policyFeatureList}
deviceType={deviceType}
/>
</div>
<div
style={{ display: currentStepIndex === 1 ? 'unset' : 'none' }}
>
<SelectPolicyType
getPolicyPayloadData={this.getPolicyPayloadData}
policyData={policyData}
getPrevStep={this.getPrevStep}
getNextStep={this.getNextStep}
/>
</div>
<div
style={{ display: currentStepIndex === 2 ? 'unset' : 'none' }}
>
<AssignGroups
getPolicyPayloadData={this.getPolicyPayloadData}
policyData={policyData}
getPrevStep={this.getPrevStep}
getNextStep={this.getNextStep}
/>
</div>
<div
style={{ display: currentStepIndex === 3 ? 'unset' : 'none' }}
>
<PublishDevices
policyData={policyData}
getPolicyPayloadData={this.getPolicyPayloadData}
getPrevStep={this.getPrevStep}
/>
</div>
</Card>
</Col>
</Row>
)}
</div>
);
}
}
export default withConfigContext(
Form.create({ name: 'edit-policy' })(EditPolicy),
);

@ -0,0 +1,55 @@
/*
* 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 { Divider, Icon, Tooltip } from 'antd';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import { Link } from 'react-router-dom';
class PolicyAction extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
}
render() {
return (
<div>
<div>
<Tooltip placement="top" title={'Edit Policy'}>
<Link
to={`/entgra/policy/edit/${this.props.selectedPolicyData.id}`}
>
<Icon type="edit" />
</Link>
</Tooltip>
<Divider type="vertical" />
<Tooltip placement="top" title={''}>
<Link
to={`/entgra/policy/view/${this.props.selectedPolicyData.id}`}
>
<Icon type="eye" />
</Link>
</Tooltip>
</div>
</div>
);
}
}
export default withConfigContext(PolicyAction);

@ -0,0 +1,170 @@
/*
* 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, Tooltip, Popconfirm, Divider } from 'antd';
class BulkActionBar extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedMultiple: false,
selectedSingle: false,
isPolicyActive: true,
};
}
// This method checks whether active devices are selected
onCheckPolicyStatus = () => {
let tempIsPolicyActive;
for (let i = 0; i < this.props.selectedRows.length; i++) {
if (this.props.selectedRows[i].active) {
tempIsPolicyActive = true;
break;
}
tempIsPolicyActive = false;
}
this.setState({ isPolicyActive: tempIsPolicyActive });
};
onConfirmRemove = () => {
if (!this.state.isPolicyActive) {
this.props.removePolicy();
}
};
onConfirmPublish = () => {
if (!this.state.isPolicyActive) {
this.props.publishPolicy();
}
};
onConfirmUnpublish = () => {
if (this.state.isPolicyActive) {
this.props.unpublishPolicy();
}
};
render() {
const isSelected = this.props.selectedRows.length > 0;
return (
<div>
<div style={{ padding: '8px' }}>
<Tooltip placement="bottom" title={'Apply Changes to Device'}>
<Popconfirm
placement="topLeft"
title={'Do you really want to apply changes to all policies?'}
onConfirm={this.props.applyChanges}
okText="Yes"
cancelText="No"
>
<Button
type="link"
shape="circle"
icon="check-circle"
size={'default'}
style={{ margin: '2px' }}
/>
</Popconfirm>
</Tooltip>
</div>
<div
style={{ display: isSelected ? 'inline' : 'none', padding: '8px' }}
>
<Tooltip
placement="bottom"
title={'Remove'}
autoAdjustOverflow={true}
>
<Popconfirm
placement="topLeft"
title={
!this.state.isPolicyActive
? 'Do you really want to remove the selected policy(s)?'
: 'You cannot select already active policies. Please deselect active policies and try again.'
}
onConfirm={this.onConfirmRemove}
okText="Yes"
cancelText="No"
>
<Button
type="link"
shape="circle"
icon="delete"
size={'default'}
onClick={this.onCheckPolicyStatus}
style={{ margin: '2px' }}
/>
</Popconfirm>
</Tooltip>
<Divider type="vertical" />
<Tooltip placement="bottom" title={'Publish'}>
<Popconfirm
placement="topLeft"
title={
!this.state.isPolicyActive
? 'Do you really want to publish the selected policy(s)??'
: 'You cannot select already active policies. Please deselect active policies and try again.'
}
okText="Yes"
onConfirm={this.onConfirmPublish}
cancelText="No"
>
<Button
type="link"
shape="circle"
icon="import"
onClick={this.onCheckPolicyStatus}
size={'default'}
style={{
margin: '2px',
}}
/>
</Popconfirm>
</Tooltip>
<Divider type="vertical" />
<Tooltip placement="bottom" title={'Unpublish'}>
<Popconfirm
placement="topLeft"
title={
this.state.isPolicyActive
? 'Do you really want to unpublish the selected policy(s)?'
: 'You cannot select already inactive policies to be unpublished. Please deselect inactive policies and try again.'
}
okText="Yes"
onConfirm={this.onConfirmUnpublish}
cancelText="No"
>
<Button
type="link"
shape="circle"
icon="export"
onClick={this.onCheckPolicyStatus}
size={'default'}
style={{ margin: '2px' }}
/>
</Popconfirm>
</Tooltip>
</div>
</div>
);
}
}
export default BulkActionBar;

@ -24,42 +24,16 @@ 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 PolicyAction from './component/PolicyAction';
import PolicyBulkActionBar from './component/PolicyBulkActionBar';
let apiUrl;
const columns = [
{
title: 'Policy Name',
dataIndex: 'policyName',
width: 100,
},
{
title: 'Description',
dataIndex: 'description',
key: 'description',
// render: enrolmentInfo => enrolmentInfo.owner
// todo add filtering options
},
{
title: 'Compilance',
dataIndex: 'compliance',
key: 'compliance',
// render: enrolmentInfo => enrolmentInfo.ownership
// todo add filtering options
},
{
title: 'Policy Type',
dataIndex: 'policyType',
key: 'policyType',
// render: enrolmentInfo => enrolmentInfo.ownership
// todo add filtering options
},
];
class PoliciesTable extends React.Component {
constructor(props) {
super(props);
TimeAgo.addLocale(en);
this.config = this.props.context;
this.state = {
data: [],
pagination: {},
@ -149,10 +123,203 @@ class PoliciesTable extends React.Component {
});
};
unpublishPolicy = () => {
const policyIDs = this.state.selectedRows.map(obj => obj.id);
// send request to the invoker
axios
.post(
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/policies/deactivate-policy',
policyIDs,
{ headers: { 'Content-Type': 'application/json' } },
)
.then(res => {
if (res.status === 200) {
this.fetchGroups();
notification.success({
message: 'Done',
duration: 4,
description: 'Selected policy(s) was successfully unpublished',
});
}
})
.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 unpublish policy(s).',
});
}
});
};
publishPolicy = () => {
const policyIDs = this.state.selectedRows.map(obj => obj.id);
// send request to the invoker
axios
.post(
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/policies/activate-policy',
policyIDs,
{ headers: { 'Content-Type': 'application/json' } },
)
.then(res => {
if (res.status === 200) {
this.fetchGroups();
notification.success({
message: 'Done',
duration: 4,
description: 'Selected policy(s) was successfully unpublished',
});
}
})
.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 unpublish policy(s).',
});
}
});
};
removePolicy = () => {
const policyIDs = this.state.selectedRows.map(obj => obj.id);
// send request to the invoker
axios
.post(
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/policies/remove-policy',
policyIDs,
{ headers: { 'Content-Type': 'application/json' } },
)
.then(res => {
if (res.status === 200) {
this.fetchGroups();
notification.success({
message: 'Done',
duration: 4,
description: 'Selected policy(s) was successfully removed.',
});
}
})
.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 remove policy(s).',
});
}
});
};
applyChanges = () => {
// send request to the invoker
axios
.put(
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/policies/apply-changes',
'null',
{ headers: { 'Content-Type': 'application/json' } },
)
.then(res => {
if (res.status === 200) {
this.fetchGroups();
notification.success({
message: 'Done',
duration: 4,
description: 'Changes applied successfully.',
});
}
})
.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 apply changes to device.',
});
}
});
};
render() {
const { data, pagination, loading } = this.state;
const { data, pagination, loading, selectedRows } = this.state;
const columns = [
{
title: 'Policy Name',
dataIndex: 'policyName',
width: 100,
},
{
title: 'Description',
dataIndex: 'description',
key: 'description',
// render: enrolmentInfo => enrolmentInfo.owner
// todo add filtering options
},
{
title: 'Compilance',
dataIndex: 'compliance',
key: 'compliance',
// render: enrolmentInfo => enrolmentInfo.ownership
// todo add filtering options
},
{
title: 'Policy Type',
dataIndex: 'policyType',
key: 'policyType',
// render: enrolmentInfo => enrolmentInfo.ownership
// todo add filtering options
},
{
title: 'Action',
dataIndex: 'id',
key: 'action',
render: (id, row) => (
<span>
<PolicyAction selectedPolicyData={row} />
</span>
),
},
];
return (
<div>
<PolicyBulkActionBar
selectedRows={selectedRows}
unpublishPolicy={this.unpublishPolicy}
publishPolicy={this.publishPolicy}
removePolicy={this.removePolicy}
applyChanges={this.applyChanges}
/>
<Table
columns={columns}
rowKey={record => record.id}

@ -0,0 +1,756 @@
/*
* 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 {
Alert,
Button,
Checkbox,
Col,
Collapse,
Form,
Icon,
Input,
Popconfirm,
Radio,
Row,
Select,
Table,
Tabs,
Tooltip,
Typography,
Upload,
} from 'antd';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import moment from 'moment';
const { Text, Title, Paragraph } = Typography;
const { TabPane } = Tabs;
const { Option } = Select;
const { TextArea } = Input;
const subPanelpayloadAttributes = {};
const fieldKeys = [];
class PolicyInfo extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
this.state = {
data: {},
policyFeatureList: [],
activePanelKeys: [],
profilePreviewKey: '',
customInputDataArray: [],
inputTableDataSources: {},
isInfoPreview: false,
};
}
setProfileInfo = e => {
let activePolicies = [];
let activePolicyFields = {};
const allFields = this.props.form.getFieldsValue();
this.props.policyFeatureList.map(element => {
activePolicies.push(element.featureCode);
let featureData = JSON.parse(element.content);
Object.keys(featureData).map(key => {
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.setState({
activePanelKeys: activePolicies,
});
};
// convert time from 24h format to 12h format
timeConverter = time => {
time = time
.toString()
.match(/^([01]\d|2[0-3])(:)([0-5]\d)(:[0-5]\d)?$/) || [time];
if (time.length > 1) {
time = time.slice(1);
time[5] = +time[0] < 12 ? ' AM' : ' PM';
time[0] = +time[0] % 12 || 12;
}
return time.join('');
};
// get Option value from start Time, end Time and time difference between 2 values
getOptionForTimeSelectors = (startTimeValue, endTimeValue, timeIncrement) => {
let timeOptions = [];
let time = new Date(
moment()
.startOf('day')
.format('YYYY/MM/DD'),
);
let tempValue = startTimeValue;
time.setMinutes(time.getMinutes() + tempValue);
let startOption = (
<Option value={String(tempValue)}>
{this.timeConverter(
`${String(time)
.split(' ')[4]
.substring(0, 5)}`,
)}
</Option>
);
timeOptions.push(startOption);
while (tempValue !== endTimeValue) {
time = new Date(
moment()
.startOf('day')
.format('YYYY/MM/DD'),
);
tempValue += timeIncrement;
if (tempValue > 1440) {
tempValue = 0;
continue;
}
time.setMinutes(time.getMinutes() + tempValue);
let option = (
<Option value={String(tempValue)}>
{this.timeConverter(
`${String(time)
.split(' ')[4]
.substring(0, 5)}`,
)}
</Option>
);
timeOptions.push(option);
}
return timeOptions;
};
// handle items which handle from radio buttons
handleRadioPanel = (e, subPanel) => {
{
subPanel.map((panel, i) => {
if (panel.value === e.target.value) {
document.getElementById(panel.value).style.display = 'block';
} else {
document.getElementById(panel.value).style.display = 'none';
}
});
}
};
// handle items which handle from select options
handleSelectedPanel = (e, subPanel) => {
{
subPanel.map((panel, i) => {
if (panel.id === e) {
document.getElementById(panel.id).style.display = 'block';
} else {
document.getElementById(panel.id).style.display = 'none';
}
});
}
};
getColumns = ({ getFieldDecorator }, arr) => {
const columnArray = [];
const actionColumn = [
{
title: '',
dataIndex: 'operation',
render: (name, row) => (
<Form.Item>
<Popconfirm title="Sure to delete?">
<a>
<Text type="danger">
<Icon type="delete" />
</Text>
</a>
</Popconfirm>
</Form.Item>
),
},
];
Object.values(arr).map((columnData, c) => {
if (columnData.type === 'input') {
const column = {
title: `${columnData.name}`,
dataIndex: `${columnData.key}`,
key: `${columnData.key}`,
render: (name, row, i) => (
<Form.Item>
{getFieldDecorator(`${columnData.key}${i}`, {})(
<Input
type={columnData.others.inputType}
placeholder={columnData.others.placeholder}
/>,
)}
</Form.Item>
),
};
columnArray.push(column);
} else if (columnData.type === 'upload') {
const column = {
title: `${columnData.name}`,
dataIndex: `${columnData.key}`,
key: `${columnData.key}`,
render: (name, row, i) => (
<Form.Item>
{getFieldDecorator(`${columnData.key}${i}`, {})(
<Upload>
<Button>
<Icon type="upload" /> Choose file
</Button>
</Upload>,
)}
</Form.Item>
),
};
columnArray.push(column);
} else if (columnData.type === 'select') {
const column = {
title: `${columnData.name}`,
dataIndex: `${columnData.key}`,
key: `${columnData.key}`,
render: (name, row, i) => (
<Form.Item>
{getFieldDecorator(`${columnData.key}${i}`, {
initialValue: columnData.others.initialDataIndex,
})(
<Select>
{columnData.others.option.map((option, i) => {
return (
<Option key={i} value={option.key}>
{option.value}
</Option>
);
})}
</Select>,
)}
</Form.Item>
),
};
columnArray.push(column);
}
});
const columns = columnArray.concat(actionColumn);
return columns;
};
// generate form items
getPanelItems = (panel, panelId) => {
const { getFieldDecorator } = this.props.form;
const subPanelList = {};
return panel.map((item, k) => {
fieldKeys.push(item.id);
switch (item.type) {
case 'select':
if (item.optional.hasOwnProperty('subPanel')) {
return (
<div>
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
initialValue: `${item.optional.option[0].name}`,
})(
<Select
onChange={e =>
this.handleSelectedPanel(e, item.optional.subPanel)
}
>
{item.optional.option.map((option, i) => {
return (
<Option key={i} value={option.value}>
{option.name}
</Option>
);
})}
</Select>,
)}
</Form.Item>
<div className={'sub-panel-container'}>
{item.optional.subPanel.map((panel, i) => {
return (
<div
id={panel.id}
key={i}
style={
panel.id === item.optional.initialDataIndex
? { display: 'block' }
: { display: 'none' }
}
>
{this.getPanelItems(panel.panelItem)}
</div>
);
})}
</div>
</div>
);
}
return (
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
initialValue: `${item.optional.option[0].name}`,
})(
<Select>
{item.optional.option.map((option, i) => {
return (
<Option key={i} value={option.value}>
{option.name}
</Option>
);
})}
</Select>,
)}
</Form.Item>
);
case 'timeSelector':
return (
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
initialValue: item.optional.initialDataIndex,
})(
<Select>
{this.getOptionForTimeSelectors(
item.optional.firstOptionValue,
item.optional.lastOptionValue,
item.optional.valueDifference,
)}
</Select>,
)}
</Form.Item>
);
case 'input':
return (
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
rules: [
{
pattern: new RegExp(`${item.optional.rules.regex}`),
message: `${item.optional.rules.validationMsg}`,
},
],
})(<Input placeholder={item.optional.placeholder} disabled />)}
</Form.Item>
);
case 'checkbox':
if (item.optional.hasOwnProperty('subPanel')) {
return (
<div key={k}>
<Collapse
bordered={false}
activeKey={this.state.activeSubPanelKeys}
>
<Collapse.Panel
key={item.id}
showArrow={false}
style={{ border: 0 }}
header={
<Form.Item key={k}>
{getFieldDecorator(`${item.id}`, {
valuePropName: 'checked',
initialValue: item.optional.ischecked,
})(
<Checkbox>
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
</Checkbox>,
)}
</Form.Item>
}
>
<div>
<div>
{item.optional.subPanel.map((panel, i) => {
subPanelList[panel.others.itemSwitch] =
panel.others.itemPayload;
if (
subPanelpayloadAttributes.hasOwnProperty(panelId)
) {
Object.assign(
subPanelpayloadAttributes[panelId],
subPanelList,
);
} else {
subPanelpayloadAttributes[panelId] = subPanelList;
}
return (
<div key={i}>
{this.getPanelItems(panel.panelItem, panelId)}
</div>
);
})}
</div>
</div>
</Collapse.Panel>
</Collapse>
</div>
);
}
return (
<Form.Item key={k}>
{getFieldDecorator(`${item.id}`, {
valuePropName: 'checked',
initialValue: item.optional.ischecked,
})(
<Checkbox>
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
</Checkbox>,
)}
</Form.Item>
);
case 'textArea':
return (
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
initialValue: null,
})(
<TextArea
placeholder={item.optional.placeholder}
rows={item.optional.row}
disabled
/>,
)}
</Form.Item>
);
case 'radioGroup':
return (
<div>
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
initialValue: `${item.optional.initialValue}`,
})(
<Radio.Group
onChange={e =>
this.handleRadioPanel(e, item.optional.radio)
}
>
{item.optional.radio.map((option, i) => {
return (
<Radio key={i} value={option.value}>
{option.name}
</Radio>
);
})}
</Radio.Group>,
)}
</Form.Item>
<div className={'sub-panel-container'}>
{item.optional.subPanel.map((panel, i) => {
return (
<div
key={i}
id={panel.id}
style={
panel.id === item.optional.initialValue
? { display: 'block' }
: { display: 'none' }
}
>
{this.getPanelItems(panel.panelItem)}
</div>
);
})}
</div>
</div>
);
case 'title':
return (
<Title key={k} level={4}>
{item.label}{' '}
</Title>
);
case 'paragraph':
return (
<Paragraph key={k} style={{ marginTop: 20 }}>
{item.label}{' '}
</Paragraph>
);
case 'alert':
return (
<Alert key={k} description={item.label} type="warning" showIcon />
);
case 'upload':
return (
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
>
{getFieldDecorator('upload', {})(
<Upload>
<Button>
<Icon type="upload" /> Click to upload
</Button>
</Upload>,
)}
</Form.Item>
);
case 'inputTable':
if (
!(`${item.optional.dataSource}` in this.state.inputTableDataSources)
) {
Object.defineProperty(
this.state.inputTableDataSources,
`${item.optional.dataSource}`,
{ value: [], writable: true },
);
}
return (
<div key={k}>
<Table
id={item.id}
dataSource={
this.state.inputTableDataSources[item.optional.dataSource]
}
columns={this.getColumns(
{ getFieldDecorator },
item.optional.columns,
)}
/>
</div>
);
case 'customInputTable':
return (
<div key={k}>
<Table
id={item.id}
dataSource={this.state.customInputDataArray}
columns={this.getColumns(
{ getFieldDecorator },
item.optional.columns,
)}
/>
</div>
);
default:
return null;
}
});
};
onPreview = e => {
this.setState({
profilePreviewKey: 'profileInfo',
isInfoPreview: true,
});
};
render() {
const { policyUIConfigurationsList } = this.props;
const { getFieldDecorator } = this.props.form;
return (
<div style={{ marginTop: 20 }}>
<Row>
<Col span={4}>
<Title level={4}>Profile Information</Title>
</Col>
<Col span={16}>
<Button type="link" icon="eye" onClick={this.onPreview}>
<Text
style={{
fontSize: 'small',
display: this.state.isInfoPreview ? 'none' : 'inline',
}}
>
(Click to view policy information)
</Text>
</Button>
</Col>
</Row>
<Collapse
bordered={false}
activeKey={this.state.profilePreviewKey}
style={{ display: this.state.isInfoPreview ? 'block' : 'none' }}
>
<Collapse.Panel
key={'profileInfo'}
showArrow={false}
style={{
border: 0,
}}
>
<div className="tab-container">
<Tabs
tabPosition={'left'}
size={'large'}
onChange={this.setProfileInfo}
>
{policyUIConfigurationsList.map((element, i) => {
return (
<TabPane tab={<span>{element.name}</span>} key={i}>
{Object.values(element.panels).map((panel, j) => {
panel = panel.panel;
return (
<div key={j}>
<Collapse
bordered={false}
activeKey={this.state.activePanelKeys}
>
<Collapse.Panel
key={panel.panelId}
showArrow={false}
style={{ border: 0 }}
header={
<div>
<Row>
<Col offset={0} span={14}>
<Title level={4}> {panel.title} </Title>
</Col>
</Row>
<Row>{panel.description}</Row>
</div>
}
>
{panel.hasOwnProperty('panelItem') && (
<div>
<Form name={panel.panelId}>
<Form.Item style={{ display: 'none' }}>
{getFieldDecorator(`${panel.panelId}`, {
initialValue: ' ',
})(<Input />)}
</Form.Item>
{this.getPanelItems(
panel.panelItem,
panel.panelId,
)}
</Form>
</div>
)}
{panel.hasOwnProperty('subFormLists') && (
<div>
{Object.values(panel.subFormLists).map(
(form, i) => {
return (
<Form name={form.id} key={i}>
<Form.Item
style={{ display: 'none' }}
>
{getFieldDecorator(`${form.id}`, {
initialValue: ' ',
})(<Input />)}
</Form.Item>
{this.getPanelItems(form.panelItem)}
</Form>
);
},
)}
</div>
)}
</Collapse.Panel>
</Collapse>
</div>
);
})}
</TabPane>
);
})}
</Tabs>
</div>
</Collapse.Panel>
</Collapse>
</div>
);
}
}
export default withConfigContext(Form.create()(PolicyInfo));

@ -0,0 +1,131 @@
/*
* 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 { Card, Col, Icon, Row, Typography } from 'antd';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
const { Title, Text } = Typography;
class ProfileOverview extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
this.state = {
data: {},
};
}
render() {
const { policyData } = this.props;
return (
<div>
<div style={{ marginTop: 20 }}>
<div>
<Title level={4}>Policy Overview</Title>
</div>
<Card>
<div style={{ paddingLeft: 4 }}>
<Row style={{ marginTop: 8 }}>
<Col span={8}>
<Text>Platform</Text>
</Col>
<Col span={8}>
<Text>{policyData.profile.deviceType.toUpperCase()}</Text>
</Col>
</Row>
<hr style={{ backgroundColor: 'grey' }} />
<Row style={{ marginTop: 15 }}>
<Col span={8}>
<Text>Groups</Text>
</Col>
<Col span={8}>
<Text>{policyData.deviceGroups}</Text>
</Col>
</Row>
<hr />
<Row style={{ marginTop: 15 }}>
<Col span={8}>
<Text>Action upon non-compliance</Text>
</Col>
<Col span={8}>
<Text>{policyData.compliance.toUpperCase()}</Text>
</Col>
</Row>
<hr />
<Row style={{ marginTop: 15 }}>
<Col span={8}>
<Text>Status</Text>
</Col>
<Col span={8}>
<Text>
{policyData.active ? (
<span>
<Icon
type="exclamation-circle"
style={{ color: '#6ab04c' }}
/>
Active
</span>
) : (
<span>
<Icon
type="exclamation-circle"
style={{ color: '#f9ca24' }}
/>
Inactive
</span>
)}
{policyData.updated ? <span>/Updated</span> : <span></span>}
</Text>
</Col>
</Row>
<hr />
<Row style={{ marginTop: 15 }}>
<Col span={8}>
<Text>Assigned Roles</Text>
</Col>
<Col span={8}>{policyData.roles}</Col>
</Row>
<hr />
<Row style={{ marginTop: 15 }}>
<Col span={8}>
<Text type={8}>Policy Type</Text>
</Col>
<Col span={8}>
<Text>{policyData.policyType}</Text>
</Col>
</Row>
</div>
</Card>
</div>
<div style={{ marginTop: 20 }}>
<Title level={4}>Description</Title>
<Card>
<Row>
<Col span={4}>
<Text>{policyData.description}</Text>
</Col>
</Row>
</Card>
</div>
</div>
);
}
}
export default withConfigContext(ProfileOverview);

@ -0,0 +1,147 @@
/*
* 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 { Col, message, notification } from 'antd';
import { withConfigContext } from '../../../../../../components/ConfigContext';
import PolicyInfo from './component/PolicyInfo';
import ProfileOverview from './component/ProfileOverview';
import axios from 'axios';
class PolicyProfile extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
this.state = {
policyId: this.props.policyId,
policyData: null,
policyUIConfigurationsList: [],
policyFeatureList: [],
};
}
componentDidMount() {
this.getSelectedPolicy(this.props.policyId);
}
getSelectedPolicy = policyId => {
let apiUrl =
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/policies/' +
policyId;
// send request to the invokers
axios
.get(apiUrl)
.then(res => {
if (res.status === 200) {
this.setState({
policyData: res.data.data,
policyFeatureList: res.data.data.profile.profileFeaturesList,
});
this.getPolicyConfigJson(res.data.data.profile.deviceType);
}
})
.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 selected policy.',
});
}
});
};
getPolicyConfigJson = type => {
this.setState({ isLoading: true });
let apiUrl =
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/device-types/' +
type +
'/ui-policy-configurations';
// send request to the invokers
axios
.get(apiUrl)
.then(res => {
if (res.status === 200) {
this.setState({
isLoading: false,
policyUIConfigurationsList: 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 Policy details.',
});
}
this.setState({ isLoading: false });
});
};
render() {
const {
policyData,
policyUIConfigurationsList,
policyFeatureList,
} = this.state;
return (
<div>
<Col span={16} offset={4}>
{/* <Card style={{ marginTop: 24 }}>*/}
<div>
{policyData != null && (
<ProfileOverview
policyId={this.props.policyId}
policyData={policyData}
/>
)}
</div>
<div>
{policyData != null && (
<PolicyInfo
policyId={this.state.policyId}
policyFeatureList={policyFeatureList}
policyUIConfigurationsList={policyUIConfigurationsList}
/>
)}
</div>
</Col>
</div>
);
}
}
export default withConfigContext(PolicyProfile);

@ -0,0 +1,69 @@
/*
* 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, { Component } from 'react';
import { Breadcrumb, Icon, PageHeader } from 'antd';
import { Link } from 'react-router-dom';
import EditPolicy from '../../components/EditPolicy';
import { withConfigContext } from '../../../../../../components/ConfigContext';
class EditSelectedPolicy extends Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
this.config = this.props.context;
this.state = {
data: {},
policyOverview: {},
policyId: '',
};
}
render() {
const {
match: { params },
} = this.props;
return (
<div>
<PageHeader style={{ paddingTop: 0 }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/entgra">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Policies</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap">
{/* <h3>Policies</h3>*/}
{/* <Paragraph>Create new policy on IoT Server.</Paragraph>*/}
</div>
<div style={{ borderRadius: 5 }}>
<EditPolicy policyId={params.policyId} />
</div>
</PageHeader>
<div
style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}
></div>
</div>
);
}
}
export default withConfigContext(EditSelectedPolicy);

@ -0,0 +1,68 @@
/*
* 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, { Component } from 'react';
import { Breadcrumb, Icon, PageHeader } from 'antd';
import { Link } from 'react-router-dom';
import { withConfigContext } from '../../../../../../components/ConfigContext';
import PolicyProfile from '../../components/PolicyProfile';
class ViewPolicy extends Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
this.config = this.props.context;
this.state = {
data: {},
policyOverview: {},
policyId: '',
};
}
render() {
const {
match: { params },
} = this.props;
return (
<div>
<PageHeader style={{ paddingTop: 0 }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/entgra">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Policies</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap">
{/* <h3>Policies</h3>*/}
{/* <Paragraph>Create new policy on IoT Server.</Paragraph>*/}
</div>
<div style={{ borderRadius: 5 }}>
<PolicyProfile policyId={params.policyId} />
</div>
</PageHeader>
<div
style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}
></div>
</div>
);
}
}
export default withConfigContext(ViewPolicy);

@ -116,12 +116,6 @@ const columns = [
},
// todo add filtering options
},
{
title: 'OS Version',
dataIndex: 'deviceInfo',
key: 'osVersion',
render: deviceInfo => deviceInfo.osVersion,
},
];
const getTimeAgo = time => {
@ -185,6 +179,17 @@ class deviceTable extends React.Component {
.then(res => {
if (res.status === 200) {
const pagination = { ...this.state.pagination };
if (
res.data.data.devices.length &&
res.data.data.devices[0].hasOwnProperty('deviceInfo')
) {
columns.push({
title: 'OS Version',
dataIndex: 'deviceInfo',
key: 'osVersion',
render: deviceInfo => deviceInfo.osVersion,
});
}
this.setState({
loading: false,
data: res.data.data,

@ -178,7 +178,7 @@ class Reports extends React.Component {
style={{ fontSize: '25px', color: '#08c' }}
/>
<h2>
<b>Expired Devices Report</b>
<b>Outdated OS Version Report</b>
</h2>
<p>Report based on device OS version</p>
</div>

@ -23,7 +23,7 @@ import { Link } from 'react-router-dom';
import { withConfigContext } from '../../../../../../components/ConfigContext';
import AppListDropDown from './components/AppListDropDown';
import ReportDevicesTable from '../../components/DevicesTable';
import DevicesTable from './../../components/DevicesTable';
import AppVersionDropDown from './components/AppVersionDropDown';
// eslint-disable-next-line no-unused-vars
@ -121,7 +121,7 @@ class AppNotInstalledDevicesReport extends React.Component {
</Button>
</div>
<div style={{ backgroundColor: '#ffffff', borderRadius: 5 }}>
<ReportDevicesTable apiUrl={apiUrl} />
<DevicesTable apiUrl={apiUrl} />
</div>
</div>
</PageHeader>

@ -121,10 +121,14 @@ class OutdatedOSversionReport extends React.Component {
<Breadcrumb.Item>
<Link to="/entgra/reports">Reports</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Expired Devices</Breadcrumb.Item>
<Breadcrumb.Item>Outdated OS Version</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap" style={{ marginBottom: '10px' }}>
<h3>Expired Devices Report</h3>
<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

@ -0,0 +1,221 @@
/*
* 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, Form, Input, Modal, notification, Col, Row } from 'antd';
import axios from 'axios';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import { handleApiError } from '../../../../../../../../services/utils/errorHandler';
const InputGroup = Input.Group;
class ExternalDevicesModal extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
this.state = {
isDeviceEditModalVisible: false,
metaData: [],
};
}
openDeviceEditModal = () => {
this.setState({
isDeviceEditModalVisible: true,
});
this.getExternalDevicesForUser(this.props.user);
};
onCancelHandler = () => {
this.setState({
isDeviceEditModalVisible: false,
});
};
getExternalDevicesForUser = userName => {
let apiURL =
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
`/users/claims/${userName}`;
axios
.get(apiURL)
.then(res => {
if (res.status === 200) {
if (res.data.data.hasOwnProperty('http://wso2.org/claims/devices')) {
this.setState({
metaData: JSON.parse(
res.data.data['http://wso2.org/claims/devices'],
),
});
}
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to retrieve claims.',
);
});
};
setExternalDevicesForUser = (userName, payload) => {
let apiURL =
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
`/users/claims/${userName}`;
axios
.put(apiURL, payload)
.then(res => {
if (res.status === 200) {
notification.success({
message: 'Done',
duration: 0,
description: 'Succussfully updated.',
});
}
this.setState({
isDeviceEditModalVisible: false,
});
})
.catch(error => {
handleApiError(error, 'Error occurred while trying to update claims.');
});
};
onSubmitClaims = e => {
this.props.form.validateFields(['meta'], (err, values) => {
if (!err) {
this.setExternalDevicesForUser(this.props.user, this.state.metaData);
}
});
};
addNewMetaData = () => {
this.setState({
metaData: [...this.state.metaData, { deviceName: '', id: '' }],
});
};
render() {
const { getFieldDecorator } = this.props.form;
const { metaData } = this.state;
return (
<div>
<div>
<Button
type="primary"
size={'small'}
icon="desktop"
onClick={this.openDeviceEditModal}
>
External Devices
</Button>
</div>
<div>
<Modal
title="EDIT EXTERNAL DEVICE CLAIMS"
width="40%"
visible={this.state.isDeviceEditModalVisible}
onOk={this.onSubmitClaims}
onCancel={this.onCancelHandler}
footer={[
<Button key="cancel" onClick={this.onCancelHandler}>
Cancel
</Button>,
<Button key="submit" type="primary" onClick={this.onSubmitClaims}>
Update
</Button>,
]}
>
<div style={{ alignItems: 'center' }}>
<p>Add or edit external device information</p>
<Form labelCol={{ span: 5 }} wrapperCol={{ span: 18 }}>
<Form.Item style={{ display: 'block' }}>
{getFieldDecorator('meta', {})(
<div>
{metaData.map((data, index) => {
return (
<InputGroup key={index}>
<Row gutter={8}>
<Col span={5}>
<Input
placeholder="key"
defaultValue={data.deviceName}
onChange={e => {
metaData[index].deviceName =
e.currentTarget.value;
this.setState({
metaData,
});
}}
/>
</Col>
<Col span={8}>
<Input
placeholder="value"
defaultValue={data.id}
onChange={e => {
metaData[index].id = e.currentTarget.value;
this.setState({
metaData,
});
}}
/>
</Col>
<Col span={3}>
<Button
type="dashed"
shape="circle"
icon="minus"
onClick={() => {
metaData.splice(index, 1);
this.setState({
metaData,
});
}}
/>
</Col>
</Row>
</InputGroup>
);
})}
<Button
type="dashed"
icon="plus"
onClick={this.addNewMetaData}
>
Addd
</Button>
</div>,
)}
</Form.Item>
</Form>
</div>
</Modal>
</div>
</div>
);
}
}
export default withConfigContext(
Form.create({ name: 'external-device-modal' })(ExternalDevicesModal),
);

@ -27,6 +27,7 @@ import UsersDevices from './components/UserDevices';
import AddUser from './components/AddUser';
import UserActions from './components/UserActions';
import Filter from '../../../../components/Filter';
import ExternalDevicesModal from './components/ExternalDevicesModal';
const ButtonGroup = Button.Group;
let apiUrl;
@ -114,8 +115,6 @@ class UsersTable extends React.Component {
data: res.data.data.users,
pagination,
});
console.log(res.data.data);
}
})
.catch(error => {
@ -224,6 +223,7 @@ class UsersTable extends React.Component {
title: 'First Name',
dataIndex: 'firstname',
key: 'firstname',
width: 200,
},
{
title: 'Last Name',
@ -260,6 +260,12 @@ class UsersTable extends React.Component {
</ButtonGroup>
),
},
{
title: 'External Device Claims',
dataIndex: 'claims',
key: 'claims',
render: (id, row) => <ExternalDevicesModal user={row.username} />,
},
{
title: 'Action',
dataIndex: 'id',

@ -34,6 +34,8 @@
*/
package org.wso2.carbon.device.mgt.jaxrs.service.api;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import io.swagger.annotations.SwaggerDefinition;
import io.swagger.annotations.Info;
import io.swagger.annotations.ExtensionProperty;
@ -1009,4 +1011,161 @@ public interface UserManagementService {
"Provide the value in the following format: EEE, d MMM yyyy HH:mm:ss Z\n." +
"Example: Mon, 05 Jan 2014 15:10:00 +0200")
@HeaderParam("If-Modified-Since") String ifModifiedSince);
@PUT
@Path("/claims/{username}")
@ApiOperation(
produces = MediaType.APPLICATION_JSON,
httpMethod = "PUT",
value = "Updating external device claims of user",
notes = "Update external device claims of a user registered with Entgra IoTS using the REST API.",
response = BasicUserInfo.class,
tags = "User Management",
extensions = {
@Extension(properties = {
@ExtensionProperty(name = Constants.SCOPE, value = "perm:users:details")
})
}
)
@ApiResponses(value = {
@ApiResponse(
code = 200,
message = "OK. \n Successfully updated external device claims of user.",
response = BasicUserInfo.class,
responseHeaders = {
@ResponseHeader(
name = "Content-Type",
description = "The content type of the body"),
@ResponseHeader(
name = "ETag",
description = "Entity Tag of the response resource.\n" +
"Used by caches, or in conditional requests."),
@ResponseHeader(
name = "Last-Modified",
description = "Date and time the resource was last modified.\n" +
"Used by caches, or in conditional requests."),
}),
@ApiResponse(
code = 404,
message = "Not Found. \n The specified resource does not exist.",
response = ErrorResponse.class),
@ApiResponse(
code = 500,
message = "Internal Server ErrorResponse. \n Server error occurred while" +
" fetching the user details.",
response = ErrorResponse.class)
})
Response updateUserClaimsForDevices(
@ApiParam(
name = "username",
value = "Provide the username of the user.",
required = true,
defaultValue = "admin")
@PathParam("username") String username,
@ApiParam(
name = "device list",
value = "Array of objects with device details",
required = true) JsonArray deviceList);
@GET
@Path("/claims/{username}")
@ApiOperation(
produces = MediaType.APPLICATION_JSON,
httpMethod = "GET",
value = "Getting external device claims of user",
notes = "Get external device claims of a user registered with Entgra IoTS using the REST API.",
response = BasicUserInfo.class,
tags = "User Management",
extensions = {
@Extension(properties = {
@ExtensionProperty(name = Constants.SCOPE, value = "perm:users:details")
})
}
)
@ApiResponses(value = {
@ApiResponse(
code = 200,
message = "OK. \n Successfully fetched external device claims of user.",
response = BasicUserInfo.class,
responseHeaders = {
@ResponseHeader(
name = "Content-Type",
description = "The content type of the body"),
@ResponseHeader(
name = "ETag",
description = "Entity Tag of the response resource.\n" +
"Used by caches, or in conditional requests."),
@ResponseHeader(
name = "Last-Modified",
description = "Date and time the resource was last modified.\n" +
"Used by caches, or in conditional requests."),
}),
@ApiResponse(
code = 404,
message = "Not Found. \n The specified resource does not exist.",
response = ErrorResponse.class),
@ApiResponse(
code = 500,
message = "Internal Server ErrorResponse. \n Server error occurred while" +
" fetching the ruser details.",
response = ErrorResponse.class)
})
Response getUserClaimsForDevices(
@ApiParam(
name = "username",
value = "Provide the username of the user.",
required = true,
defaultValue = "admin")
@PathParam("username") String username);
@DELETE
@Path("/claims/{username}")
@ApiOperation(
produces = MediaType.APPLICATION_JSON,
httpMethod = "DELETE",
value = "Deleting external device claims of user",
notes = "Delete external device claims of user registered with Entgra IoTS using the REST API.",
response = BasicUserInfo.class,
tags = "User Management",
extensions = {
@Extension(properties = {
@ExtensionProperty(name = Constants.SCOPE, value = "perm:users:details")
})
}
)
@ApiResponses(value = {
@ApiResponse(
code = 200,
message = "OK. \n Successfully deleted external device claims of user.",
response = BasicUserInfo.class,
responseHeaders = {
@ResponseHeader(
name = "Content-Type",
description = "The content type of the body"),
@ResponseHeader(
name = "ETag",
description = "Entity Tag of the response resource.\n" +
"Used by caches, or in conditional requests."),
@ResponseHeader(
name = "Last-Modified",
description = "Date and time the resource was last modified.\n" +
"Used by caches, or in conditional requests."),
}),
@ApiResponse(
code = 404,
message = "Not Found. \n The specified resource does not exist.",
response = ErrorResponse.class),
@ApiResponse(
code = 500,
message = "Internal Server ErrorResponse. \n Server error occurred while" +
" fetching the ruser details.",
response = ErrorResponse.class)
})
Response deleteUserClaimsForDevices(
@ApiParam(
name = "username",
value = "Provide the username of the user.",
required = true,
defaultValue = "admin")
@PathParam("username") String username);
}

@ -36,6 +36,8 @@
package org.wso2.carbon.device.mgt.jaxrs.service.impl;
import java.util.LinkedList;
import java.util.Queue;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
@ -46,6 +48,8 @@ import org.wso2.carbon.device.mgt.common.DeviceIdentifier;
import org.wso2.carbon.device.mgt.common.EnrolmentInfo;
import org.wso2.carbon.device.mgt.common.Feature;
import org.wso2.carbon.device.mgt.common.FeatureManager;
import org.wso2.carbon.device.mgt.common.MonitoringOperation;
import org.wso2.carbon.device.mgt.common.OperationMonitoringTaskConfig;
import org.wso2.carbon.device.mgt.common.PaginationRequest;
import org.wso2.carbon.device.mgt.common.PaginationResult;
import org.wso2.carbon.device.mgt.common.app.mgt.Application;
@ -56,6 +60,7 @@ import org.wso2.carbon.device.mgt.common.device.details.DeviceData;
import org.wso2.carbon.device.mgt.common.device.details.DeviceInfo;
import org.wso2.carbon.device.mgt.common.device.details.DeviceLocation;
import org.wso2.carbon.device.mgt.common.device.details.DeviceLocationHistory;
import org.wso2.carbon.device.mgt.common.device.details.DeviceLocationHistorySnapshot;
import org.wso2.carbon.device.mgt.common.exceptions.DeviceManagementException;
import org.wso2.carbon.device.mgt.common.exceptions.DeviceTypeNotFoundException;
import org.wso2.carbon.device.mgt.common.exceptions.InvalidConfigurationException;
@ -494,8 +499,8 @@ public class DeviceManagementServiceImpl implements DeviceManagementService {
@PathParam("deviceId") String deviceId,
@QueryParam("from") long from, @QueryParam("to") long to) {
List<DeviceLocationHistory> deviceLocationHistory;
String errorMessage;
DeviceLocationHistory deviceLocationHistory = new DeviceLocationHistory();
try {
RequestValidationUtil.validateDeviceIdentifier(deviceType, deviceId);
@ -521,7 +526,46 @@ public class DeviceManagementServiceImpl implements DeviceManagementService {
new ErrorResponse.ErrorResponseBuilder().setCode(400l).setMessage(errorMessage)).build();
}
deviceLocationHistory = dms.getDeviceLocationInfo(deviceIdentifier, from, to);
List<List<DeviceLocationHistorySnapshot>> locationHistorySnapshotList = new ArrayList<>();
// Get the location history snapshots for the given period
List<DeviceLocationHistorySnapshot> deviceLocationHistorySnapshots = dms.getDeviceLocationInfo(deviceIdentifier, from, to);
OperationMonitoringTaskConfig operationMonitoringTaskConfig = dms.getDeviceMonitoringConfig(deviceType);
int taskFrequency = operationMonitoringTaskConfig.getFrequency();
int operationRecurrentTimes = 0;
List<MonitoringOperation> monitoringOperations = operationMonitoringTaskConfig.getMonitoringOperation();
for (MonitoringOperation monitoringOperation :
monitoringOperations) {
if (monitoringOperation.getTaskName().equals("DEVICE_LOCATION")) {
operationRecurrentTimes = monitoringOperation.getRecurrentTimes();
break;
}
}
// Device Location operation frequency in milliseconds. Adding 100000 ms as an error
long operationFrequency = taskFrequency * operationRecurrentTimes + 100000;
Queue<DeviceLocationHistorySnapshot> deviceLocationHistorySnapshotsQueue = new LinkedList<>(deviceLocationHistorySnapshots);
while (deviceLocationHistorySnapshotsQueue.size() > 0) {
List<DeviceLocationHistorySnapshot> snapshots = new ArrayList<>();
// Make a copy of remaining snapshots
List<DeviceLocationHistorySnapshot> cachedSnapshots = new ArrayList<>(deviceLocationHistorySnapshotsQueue);
for (int i = 0; i < cachedSnapshots.size(); i++) {
DeviceLocationHistorySnapshot currentSnapshot = deviceLocationHistorySnapshotsQueue.poll();
snapshots.add(currentSnapshot);
if (deviceLocationHistorySnapshotsQueue.size() > 0) {
DeviceLocationHistorySnapshot nextSnapshot = deviceLocationHistorySnapshotsQueue.peek();
if (nextSnapshot.getUpdatedTime().getTime() - currentSnapshot.getUpdatedTime().getTime() > operationFrequency) {
break;
}
}
}
locationHistorySnapshotList.add(snapshots);
}
deviceLocationHistory.setLocationHistorySnapshots(locationHistorySnapshotList);
} catch (DeviceManagementException e) {
errorMessage = "Error occurred while fetching the device information.";

@ -33,6 +33,7 @@
*/
package org.wso2.carbon.device.mgt.jaxrs.service.impl;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
@ -71,6 +72,7 @@ import org.wso2.carbon.user.api.RealmConfiguration;
import org.wso2.carbon.user.api.UserRealm;
import org.wso2.carbon.user.api.UserStoreException;
import org.wso2.carbon.user.api.UserStoreManager;
import org.wso2.carbon.user.core.UserCoreConstants;
import org.wso2.carbon.user.core.service.RealmService;
import org.wso2.carbon.utils.CarbonUtils;
import org.wso2.carbon.utils.multitenancy.MultitenantConstants;
@ -920,6 +922,113 @@ public class UserManagementServiceImpl implements UserManagementService {
}
}
@PUT
@Override
@Path("/claims/{username}")
public Response updateUserClaimsForDevices(
@PathParam("username") String username,
JsonArray deviceList) {
try {
RealmConfiguration realmConfiguration = PrivilegedCarbonContext.getThreadLocalCarbonContext()
.getUserRealm()
.getRealmConfiguration();
String domain = realmConfiguration
.getUserStoreProperty(UserCoreConstants.RealmConfig.PROPERTY_DOMAIN_NAME);
if (!StringUtils.isBlank(domain)) {
username = domain + Constants.FORWARD_SLASH + username;
}
UserStoreManager userStoreManager = DeviceMgtAPIUtils.getUserStoreManager();
if (!userStoreManager.isExistingUser(username)) {
if (log.isDebugEnabled()) {
log.debug("User by username: " + username + " does not exist.");
}
return Response.status(Response.Status.NOT_FOUND).entity(
new ErrorResponse.ErrorResponseBuilder().setMessage(
"User doesn't exist.").build()).build();
}
Map<String, String> userClaims =
this.buildExternalDevicesUserClaims(username, domain, deviceList, userStoreManager);
userStoreManager.setUserClaimValues(username, userClaims, domain);
return Response.status(Response.Status.OK).entity(userClaims).build();
} catch (UserStoreException e) {
String msg = "Error occurred while updating external device claims of the user '" + username + "'";
log.error(msg, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(
new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build();
}
}
@GET
@Override
@Path("/claims/{username}")
public Response getUserClaimsForDevices(
@PathParam("username") String username) {
try {
RealmConfiguration realmConfiguration = PrivilegedCarbonContext.getThreadLocalCarbonContext()
.getUserRealm()
.getRealmConfiguration();
String domain = realmConfiguration
.getUserStoreProperty(UserCoreConstants.RealmConfig.PROPERTY_DOMAIN_NAME);
if (!StringUtils.isBlank(domain)) {
username = domain + Constants.FORWARD_SLASH + username;
}
UserStoreManager userStoreManager = DeviceMgtAPIUtils.getUserStoreManager();
if (!userStoreManager.isExistingUser(username)) {
if (log.isDebugEnabled()) {
log.debug("User by username: " + username + " does not exist.");
}
return Response.status(Response.Status.NOT_FOUND).entity(
new ErrorResponse.ErrorResponseBuilder().setMessage(
"User doesn't exist.").build()).build();
}
String[] claimArray = {Constants.USER_CLAIM_DEVICES};
Map<String, String> claims = userStoreManager.getUserClaimValues(username, claimArray, domain);
return Response.status(Response.Status.OK).entity(claims).build();
} catch (UserStoreException e) {
String msg = "Error occurred while retrieving external device claims of the user '" + username + "'";
log.error(msg, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(
new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build();
}
}
@DELETE
@Override
@Path("/claims/{username}")
public Response deleteUserClaimsForDevices(
@PathParam("username") String username) {
try {
RealmConfiguration realmConfiguration = PrivilegedCarbonContext.getThreadLocalCarbonContext()
.getUserRealm()
.getRealmConfiguration();
String domain = realmConfiguration
.getUserStoreProperty(UserCoreConstants.RealmConfig.PROPERTY_DOMAIN_NAME);
if (!StringUtils.isBlank(domain)) {
username = domain + Constants.FORWARD_SLASH + username;
}
UserStoreManager userStoreManager = DeviceMgtAPIUtils.getUserStoreManager();
if (!userStoreManager.isExistingUser(username)) {
if (log.isDebugEnabled()) {
log.debug("User by username: " + username + " does not exist.");
}
return Response.status(Response.Status.NOT_FOUND).entity(
new ErrorResponse.ErrorResponseBuilder().setMessage(
"User doesn't exist.").build()).build();
}
String[] claimArray = {Constants.USER_CLAIM_DEVICES};
userStoreManager.deleteUserClaimValues(
username,
claimArray,
domain);
return Response.status(Response.Status.OK).entity(claimArray).build();
} catch (UserStoreException e) {
String msg = "Error occurred while deleting external device claims of the user '" + username + "'";
log.error(msg, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(
new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build();
}
}
private Map<String, String> buildDefaultUserClaims(String firstName, String lastName, String emailAddress,
boolean isFresh) {
Map<String, String> defaultUserClaims = new HashMap<>();
@ -937,6 +1046,40 @@ public class UserManagementServiceImpl implements UserManagementService {
return defaultUserClaims;
}
/**
* This method is used to build String map for user claims with updated external device details
*
* @param username username of the particular user
* @param domain domain of the particular user
* @param deviceList Array of external device details
* @param userStoreManager {@link UserStoreManager} instance
* @return String map
* @throws UserStoreException If any error occurs while calling into UserStoreManager service
*/
private Map<String, String> buildExternalDevicesUserClaims(
String username,
String domain,
JsonArray deviceList,
UserStoreManager userStoreManager) throws UserStoreException {
Map<String, String> userClaims;
String[] claimArray = {
Constants.USER_CLAIM_FIRST_NAME,
Constants.USER_CLAIM_LAST_NAME,
Constants.USER_CLAIM_EMAIL_ADDRESS,
Constants.USER_CLAIM_MODIFIED
};
userClaims = userStoreManager.getUserClaimValues(username, claimArray, domain);
if (userClaims.containsKey(Constants.USER_CLAIM_DEVICES)) {
userClaims.replace(Constants.USER_CLAIM_DEVICES, deviceList.toString());
} else {
userClaims.put(Constants.USER_CLAIM_DEVICES, deviceList.toString());
}
if (log.isDebugEnabled()) {
log.debug("Claim map is created for user: " + username + ", claims:" + userClaims.toString());
}
return userClaims;
}
private String generateInitialUserPassword() {
int passwordLength = 6;
//defining the pool of characters to be used for initial password generation

@ -45,12 +45,14 @@ public class Constants {
public static final String USER_CLAIM_LAST_NAME = "http://wso2.org/claims/lastname";
public static final String USER_CLAIM_CREATED = "http://wso2.org/claims/created";
public static final String USER_CLAIM_MODIFIED = "http://wso2.org/claims/modified";
public static final String USER_CLAIM_DEVICES = "http://wso2.org/claims/devices";
public static final String PRIMARY_USER_STORE = "PRIMARY";
public static final String DEFAULT_STREAM_VERSION = "1.0.0";
public static final String SCOPE = "scope";
public static final String JDBC_USERSTOREMANAGER = "org.wso2.carbon.user.core.jdbc.JDBCUserStoreManager";
public static final String DEFAULT_SIMPLE_DATE_FORMAT = "EEE, d MMM yyyy HH:mm:ss Z";
public static final int DEFAULT_PAGE_LIMIT = 50;
public static final String FORWARD_SLASH = "/";
public final class ErrorMessages {

@ -18,147 +18,18 @@
package org.wso2.carbon.device.mgt.common.device.details;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
@ApiModel(value = "DeviceLocationHistory", description = "This class carries all information related to the device location History" +
"details provided by a device.")
import java.util.ArrayList;
import java.util.List;
public class DeviceLocationHistory implements Serializable {
private List<List<DeviceLocationHistorySnapshot>> locationHistorySnapshots = new ArrayList<>();
@ApiModelProperty(name = "deviceId", value = "Device id", required = true)
private int deviceId;
@ApiModelProperty(name = "geoHash", value = "Geo Hash", required = true)
private String geoHash;
@ApiModelProperty(name = "deviceType", value = "Device type", required = true)
private String deviceType;
@ApiModelProperty(name = "deviceIdentifier", value = "Device Id Name", required = true)
private String deviceIdentifier;
@ApiModelProperty(name = "latitude", value = "Device GPS latitude.", required = true)
private Double latitude;
@ApiModelProperty(name = "longitude", value = "Device GPS longitude.", required = true)
private Double longitude;
@ApiModelProperty(name = "tenantId", value = "Tenant Id.", required = true)
private int tenantId;
@ApiModelProperty(name = "altitude", value = "Device altitude.", required = true)
private Double altitude;
@ApiModelProperty(name = "speed", value = "Device speed.", required = true)
private Float speed;
@ApiModelProperty(name = "bearing", value = "Device bearing.", required = true)
private Float bearing;
@ApiModelProperty(name = "distance", value = "Device distance.", required = true)
private Double distance;
@ApiModelProperty(name = "timestamp", value = "Timestamp.", required = true)
private Long timestamp;
@ApiModelProperty(name = "owner", value = "Owner.", required = true)
private String owner;
public DeviceLocationHistory() {
}
public String getGeoHash() {
return geoHash;
}
public void setGeoHash(String geoHash) {
this.geoHash = geoHash;
}
public String getDeviceType() {
return deviceType;
}
public void setDeviceType(String deviceType) {
this.deviceType = deviceType;
}
public int getDeviceId() {
return deviceId;
}
public void setDeviceId(int deviceId) {
this.deviceId = deviceId;
}
public String getDeviceIdentifier() {
return deviceIdentifier;
}
public void setDeviceIdentifier(String deviceIdentifier) {
this.deviceIdentifier = deviceIdentifier;
}
public Double getLatitude() {
return latitude;
}
public void setLatitude(Double latitude) {
this.latitude = latitude;
}
public Double getLongitude() {
return longitude;
}
public void setLongitude(Double longitude) {
this.longitude = longitude;
}
public int getTenantId() {
return tenantId;
}
public void setTenantId(int tenantId) {
this.tenantId = tenantId;
}
public Double getAltitude() {
return altitude;
}
public void setAltitude(double altitude) {
this.altitude = altitude;
}
public Float getSpeed() {
return speed;
}
public void setSpeed(Float speed) {
this.speed = speed;
}
public Float getBearing() {
return bearing;
}
public void setBearing(Float bearing) {
this.bearing = bearing;
}
public Double getDistance() {
return distance;
}
public void setDistance(Double distance) {
this.distance = distance;
}
public Long getTimestamp() {
return timestamp;
}
public void setTimestamp(Long timestamp) {
this.timestamp = timestamp;
}
public String getOwner() {
return owner;
public List<List<DeviceLocationHistorySnapshot>> getLocationHistorySnapshots() {
return locationHistorySnapshots;
}
public void setOwner(String owner) {
this.owner = owner;
public void setLocationHistorySnapshots(List<List<DeviceLocationHistorySnapshot>> locationHistorySnapshots) {
this.locationHistorySnapshots = locationHistorySnapshots;
}
}

@ -0,0 +1,63 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.wso2.carbon.device.mgt.common.device.details;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
@ApiModel(value = "DeviceLocationHistory", description = "This class carries all information related to the device location History" +
"details provided by a device.")
public class DeviceLocationHistorySnapshot extends DeviceLocation implements Serializable {
@ApiModelProperty(name = "geoHash", value = "Geo Hash", required = true)
private String geoHash;
@ApiModelProperty(name = "tenantId", value = "Tenant Id.", required = true)
private int tenantId;
@ApiModelProperty(name = "owner", value = "Owner.", required = true)
private String owner;
public DeviceLocationHistorySnapshot() {
}
public String getGeoHash() {
return geoHash;
}
public void setGeoHash(String geoHash) {
this.geoHash = geoHash;
}
public int getTenantId() {
return tenantId;
}
public void setTenantId(int tenantId) {
this.tenantId = tenantId;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
}

@ -41,9 +41,9 @@ import org.wso2.carbon.device.mgt.common.EnrolmentInfo;
import org.wso2.carbon.device.mgt.common.EnrolmentInfo.Status;
import org.wso2.carbon.device.mgt.common.PaginationRequest;
import org.wso2.carbon.device.mgt.common.Count;
import org.wso2.carbon.device.mgt.common.device.details.DeviceLocationHistory;
import org.wso2.carbon.device.mgt.common.configuration.mgt.DevicePropertyInfo;
import org.wso2.carbon.device.mgt.common.device.details.DeviceData;
import org.wso2.carbon.device.mgt.common.device.details.DeviceLocationHistorySnapshot;
import org.wso2.carbon.device.mgt.core.dto.DeviceType;
import org.wso2.carbon.device.mgt.core.geo.GeoCluster;
import org.wso2.carbon.device.mgt.core.geo.geoHash.GeoCoordinate;
@ -595,7 +595,7 @@ public interface DeviceDAO {
* @return
* @throws DeviceManagementDAOException
*/
List<DeviceLocationHistory> getDeviceLocationInfo(DeviceIdentifier deviceIdentifier, long from, long to)
List<DeviceLocationHistorySnapshot> getDeviceLocationInfo(DeviceIdentifier deviceIdentifier, long from, long to)
throws DeviceManagementDAOException;
/**

@ -45,8 +45,8 @@ import org.wso2.carbon.device.mgt.common.EnrolmentInfo.Status;
import org.wso2.carbon.device.mgt.common.PaginationRequest;
import org.wso2.carbon.device.mgt.common.configuration.mgt.DevicePropertyInfo;
import org.wso2.carbon.device.mgt.common.device.details.DeviceData;
import org.wso2.carbon.device.mgt.common.device.details.DeviceInfo;
import org.wso2.carbon.device.mgt.common.device.details.DeviceLocationHistory;
import org.wso2.carbon.device.mgt.common.device.details.DeviceLocationHistorySnapshot;
import org.wso2.carbon.device.mgt.core.dao.DeviceDAO;
import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOException;
import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOFactory;
@ -54,7 +54,6 @@ import org.wso2.carbon.device.mgt.core.dao.util.DeviceManagementDAOUtil;
import org.wso2.carbon.device.mgt.core.dto.DeviceType;
import org.wso2.carbon.device.mgt.core.geo.GeoCluster;
import org.wso2.carbon.device.mgt.core.geo.geoHash.GeoCoordinate;
import org.wso2.carbon.device.mgt.core.report.mgt.Constants;
import java.sql.Connection;
import java.sql.PreparedStatement;
@ -1802,13 +1801,13 @@ public abstract class AbstractDeviceDAOImpl implements DeviceDAO {
}
@Override
public List<DeviceLocationHistory> getDeviceLocationInfo(DeviceIdentifier deviceIdentifier, long from, long to)
public List<DeviceLocationHistorySnapshot> getDeviceLocationInfo(DeviceIdentifier deviceIdentifier, long from, long to)
throws DeviceManagementDAOException {
Connection conn;
PreparedStatement stmt = null;
ResultSet rs = null;
List<DeviceLocationHistory> deviceLocationHistories = new ArrayList<>();
List<DeviceLocationHistorySnapshot> deviceLocationHistories = new ArrayList<>();
try {
conn = this.getConnection();
@ -1867,11 +1866,6 @@ public abstract class AbstractDeviceDAOImpl implements DeviceDAO {
if (log.isDebugEnabled()) {
log.debug("Successfully removed device notification data of devices: " + deviceIdentifiers);
}
removeDeviceApplicationMapping(conn, deviceIds);
if (log.isDebugEnabled()) {
log.debug("Successfully removed device application mapping data of devices: "
+ deviceIdentifiers);
}
removeDevicePolicyApplied(conn, deviceIds);
if (log.isDebugEnabled()) {
log.debug("Successfully removed device applied policy data of devices: " + deviceIdentifiers);
@ -1880,6 +1874,10 @@ public abstract class AbstractDeviceDAOImpl implements DeviceDAO {
if (log.isDebugEnabled()) {
log.debug("Successfully removed device policy data of devices: " + deviceIdentifiers);
}
removeDeviceApplication(conn, deviceIds);
if (log.isDebugEnabled()) {
log.debug("Successfully removed device application data of devices: " + deviceIdentifiers);
}
if (log.isDebugEnabled()) {
log.debug("Starting to remove " + enrollmentIds.size() + " enrollment data of devices with " +
"identifiers: " + deviceIdentifiers);
@ -1887,7 +1885,6 @@ public abstract class AbstractDeviceDAOImpl implements DeviceDAO {
removeEnrollmentDeviceDetail(conn, enrollmentIds);
removeEnrollmentDeviceLocation(conn, enrollmentIds);
removeEnrollmentDeviceInfo(conn, enrollmentIds);
removeEnrollmentDeviceApplicationMapping(conn, enrollmentIds);
removeDeviceOperationResponse(conn, enrollmentIds);
removeEnrollmentOperationMapping(conn, enrollmentIds);
if (log.isDebugEnabled()) {
@ -1915,7 +1912,7 @@ public abstract class AbstractDeviceDAOImpl implements DeviceDAO {
throw new DeviceManagementDAOException(msg, e);
}
}
@Override
public List<Device> getAppNotInstalledDevices(
PaginationRequest request, int tenantId, String packageName, String version)
@ -1925,24 +1922,24 @@ public abstract class AbstractDeviceDAOImpl implements DeviceDAO {
boolean isVersionProvided = false;
String sql = "SELECT " +
"d.ID AS DEVICE_ID, " +
"d.DESCRIPTION,d.NAME AS DEVICE_NAME, " +
"t.NAME AS DEVICE_TYPE, " +
"d.DEVICE_IDENTIFICATION, " +
"e.OWNER, " +
"e.OWNERSHIP, " +
"e.STATUS, " +
"e.DATE_OF_LAST_UPDATE, " +
"e.DATE_OF_ENROLMENT, " +
"e.ID AS ENROLMENT_ID " +
"FROM DM_DEVICE AS d " +
"INNER JOIN DM_ENROLMENT AS e ON d.ID = e.DEVICE_ID " +
"INNER JOIN DM_DEVICE_TYPE AS t ON d.DEVICE_TYPE_ID = t.ID " +
"WHERE t.NAME = ? AND e.TENANT_ID = ? AND d.ID " +
"NOT IN (SELECT m.DEVICE_ID " +
"FROM DM_DEVICE_APPLICATION_MAPPING AS m " +
"INNER JOIN DM_APPLICATION AS a ON m.APPLICATION_ID=a.ID " +
"WHERE a.APP_IDENTIFIER = ?";
"d.ID AS DEVICE_ID, " +
"d.DESCRIPTION, " +
"d.NAME AS DEVICE_NAME, " +
"t.NAME AS DEVICE_TYPE, " +
"d.DEVICE_IDENTIFICATION, " +
"e.OWNER, " +
"e.OWNERSHIP, " +
"e.STATUS, " +
"e.DATE_OF_LAST_UPDATE, " +
"e.DATE_OF_ENROLMENT, " +
"e.ID AS ENROLMENT_ID " +
"FROM DM_DEVICE AS d " +
"INNER JOIN DM_ENROLMENT AS e ON d.ID = e.DEVICE_ID " +
"INNER JOIN DM_DEVICE_TYPE AS t ON d.DEVICE_TYPE_ID = t.ID " +
"WHERE " +
"t.NAME = ? AND e.TENANT_ID = ? AND d.ID " +
"NOT IN " +
"(SELECT a.DEVICE_ID FROM DM_APPLICATION AS a WHERE a.APP_IDENTIFIER = ?";
if (!StringUtils.isBlank(version)) {
sql = sql + " AND a.VERSION = ? ";
@ -1991,14 +1988,12 @@ public abstract class AbstractDeviceDAOImpl implements DeviceDAO {
String sql = "SELECT " +
"COUNT(d.ID) AS DEVICE_COUNT " +
"FROM DM_DEVICE AS d " +
"INNER JOIN DM_ENROLMENT AS e ON d.ID = e.DEVICE_ID " +
"INNER JOIN DM_DEVICE_TYPE AS t ON d.DEVICE_TYPE_ID = t.ID " +
"WHERE t.NAME = ? AND e.TENANT_ID = ? AND d.ID " +
"INNER JOIN DM_ENROLMENT AS e ON d.ID = e.DEVICE_ID " +
"INNER JOIN DM_DEVICE_TYPE AS t ON d.DEVICE_TYPE_ID = t.ID " +
"WHERE " +
"t.NAME = ? AND e.TENANT_ID = ? AND d.ID " +
"NOT IN " +
"(SELECT m.DEVICE_ID " +
"FROM DM_DEVICE_APPLICATION_MAPPING AS m " +
"INNER JOIN DM_APPLICATION AS a ON m.APPLICATION_ID=a.ID " +
"WHERE a.APP_IDENTIFIER = ?";
"(SELECT a.DEVICE_ID FROM DM_APPLICATION AS a WHERE a.APP_IDENTIFIER = ?";
if (!StringUtils.isBlank(version)) {
sql = sql + " AND a.VERSION = ? ";
@ -2224,70 +2219,70 @@ public abstract class AbstractDeviceDAOImpl implements DeviceDAO {
}
/***
* This method removes records of a given list of devices from the DM_DEVICE_APPLICATION_MAPPING table
* This method removes records of a given list of devices from the DM_DEVICE_POLICY_APPLIED table
* @param conn Connection object
* @param deviceIds list of device ids (primary keys)
* @throws DeviceManagementDAOException if deletion fails
*/
private void removeDeviceApplicationMapping(Connection conn, List<Integer> deviceIds)
private void removeDevicePolicyApplied(Connection conn, List<Integer> deviceIds)
throws DeviceManagementDAOException {
String sql = "DELETE FROM DM_DEVICE_APPLICATION_MAPPING WHERE DEVICE_ID = ?";
String sql = "DELETE FROM DM_DEVICE_POLICY_APPLIED WHERE DEVICE_ID = ?";
try {
if (!executeBatchOperation(conn, sql, deviceIds)) {
String msg = "Failed to remove device application mapping of of devices with deviceIds : " + deviceIds +
String msg = "Failed to remove policies applied on devices with deviceIds : " + deviceIds +
" while executing batch operation";
log.error(msg);
throw new DeviceManagementDAOException(msg);
}
} catch (SQLException e) {
String msg = "SQL error occurred while removing device application mapping of devices with deviceIds : "
+ deviceIds;
String msg = "SQL error occurred while removing policies applied on devices with deviceIds : " + deviceIds;
log.error(msg, e);
throw new DeviceManagementDAOException(msg, e);
}
}
/***
* This method removes records of a given list of devices from the DM_DEVICE_POLICY_APPLIED table
* This method removes records of a given list of devices from the DM_DEVICE_POLICY table
* @param conn Connection object
* @param deviceIds list of device ids (primary keys)
* @throws DeviceManagementDAOException if deletion fails
*/
private void removeDevicePolicyApplied(Connection conn, List<Integer> deviceIds)
throws DeviceManagementDAOException {
String sql = "DELETE FROM DM_DEVICE_POLICY_APPLIED WHERE DEVICE_ID = ?";
private void removeDevicePolicy(Connection conn, List<Integer> deviceIds) throws DeviceManagementDAOException {
String sql = "DELETE FROM DM_DEVICE_POLICY WHERE DEVICE_ID = ?";
try {
if (!executeBatchOperation(conn, sql, deviceIds)) {
String msg = "Failed to remove policies applied on devices with deviceIds : " + deviceIds +
String msg = "Failed to remove policies of devices with deviceIds : " + deviceIds +
" while executing batch operation";
log.error(msg);
throw new DeviceManagementDAOException(msg);
}
} catch (SQLException e) {
String msg = "SQL error occurred while removing policies applied on devices with deviceIds : " + deviceIds;
String msg = "SQL error occurred while removing policies of devices with deviceIds : " + deviceIds;
log.error(msg, e);
throw new DeviceManagementDAOException(msg, e);
}
}
/***
* This method removes records of a given list of devices from the DM_DEVICE_POLICY table
* This method removes records of a given list of devices from the DM_APPLICATION table
* @param conn Connection object
* @param deviceIds list of device ids (primary keys)
* @throws DeviceManagementDAOException if deletion fails
*/
private void removeDevicePolicy(Connection conn, List<Integer> deviceIds) throws DeviceManagementDAOException {
String sql = "DELETE FROM DM_DEVICE_POLICY WHERE DEVICE_ID = ?";
private void removeDeviceApplication(Connection conn, List<Integer> deviceIds)
throws DeviceManagementDAOException {
String sql = "DELETE FROM DM_APPLICATION WHERE DEVICE_ID = ?";
try {
if (!executeBatchOperation(conn, sql, deviceIds)) {
String msg = "Failed to remove policies of devices with deviceIds : " + deviceIds +
String msg = "Failed to remove applications of devices with deviceIds : " + deviceIds +
" while executing batch operation";
log.error(msg);
throw new DeviceManagementDAOException(msg);
}
} catch (SQLException e) {
String msg = "SQL error occurred while removing policies of devices with deviceIds : " + deviceIds;
String msg = "SQL error occurred while removing applications of devices devices with deviceIds : " + deviceIds;
log.error(msg, e);
throw new DeviceManagementDAOException(msg, e);
}
@ -2365,29 +2360,6 @@ public abstract class AbstractDeviceDAOImpl implements DeviceDAO {
}
}
/***
* This method removes records of a given list of enrollments from the DM_DEVICE_APPLICATION_MAPPING table
* @param conn Connection object
* @param enrollmentIds list of enrollment ids (primary keys)
* @throws DeviceManagementDAOException if deletion fails
*/
private void removeEnrollmentDeviceApplicationMapping(Connection conn, List<Integer> enrollmentIds)
throws DeviceManagementDAOException {
String sql = "DELETE FROM DM_DEVICE_APPLICATION_MAPPING WHERE ENROLMENT_ID = ?";
try {
if (!executeBatchOperation(conn, sql, enrollmentIds)) {
String msg = "Failed to remove enrollment device application mapping of devices with enrollmentIds : "
+ enrollmentIds + " while executing batch operation";
log.error(msg);
throw new DeviceManagementDAOException(msg);
}
} catch (SQLException e) {
String msg = "SQL error occurred while removing enrollment device application mapping of devices with " +
"enrollmentIds : " + enrollmentIds;
log.error(msg, e);
throw new DeviceManagementDAOException(msg, e);
}
}
/***
* This method removes records of a given list of enrollments from the DM_DEVICE_OPERATION_RESPONSE table

@ -29,9 +29,7 @@ import org.wso2.carbon.device.mgt.core.dao.util.DeviceManagementDAOUtil;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
@ -279,9 +277,9 @@ public class ApplicationDAOImpl implements ApplicationDAO {
throws DeviceManagementDAOException {
List<Application> applications = new ArrayList<>();
Application application;
String sql = "Select " +
"ID," +
" NAME, " +
String sql = "SELECT " +
"ID, " +
"NAME, " +
"APP_IDENTIFIER, " +
"PLATFORM, " +
"CATEGORY, " +
@ -293,9 +291,15 @@ public class ApplicationDAOImpl implements ApplicationDAO {
"MEMORY_USAGE, " +
"IS_ACTIVE, " +
"TENANT_ID " +
"From DM_APPLICATION " +
"WHERE PLATFORM = ? " +
"AND TENANT_ID = ? LIMIT ? OFFSET ?";
"FROM DM_APPLICATION " +
"WHERE NOT EXISTS " +
"(SELECT " +
"ID " +
"FROM DM_APPLICATION A " +
"WHERE A.NAME = DM_APPLICATION.NAME " +
"AND A.ID < DM_APPLICATION.ID) " +
"AND PLATFORM = ? " +
"AND TENANT_ID = ? LIMIT ? OFFSET ?";
try {
Connection conn = this.getConnection();
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
@ -322,7 +326,7 @@ public class ApplicationDAOImpl implements ApplicationDAO {
@Override
public List<String> getAppVersions(int tenantId, String packageName) throws DeviceManagementDAOException {
String sql = "SELECT " +
String sql = "SELECT DISTINCT " +
"VERSION " +
"FROM DM_APPLICATION " +
"WHERE TENANT_ID=? " +

@ -17,14 +17,15 @@
*/
package org.wso2.carbon.device.mgt.core.dao.util;
import java.util.Date;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.context.CarbonContext;
import org.wso2.carbon.device.mgt.common.Device;
import org.wso2.carbon.device.mgt.common.DeviceIdentifier;
import org.wso2.carbon.device.mgt.common.EnrolmentInfo;
import org.wso2.carbon.device.mgt.common.device.details.DeviceInfo;
import org.wso2.carbon.device.mgt.common.device.details.DeviceLocation;
import org.wso2.carbon.device.mgt.common.device.details.DeviceLocationHistory;
import org.wso2.carbon.device.mgt.common.device.details.DeviceLocationHistorySnapshot;
import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOException;
import org.wso2.carbon.device.mgt.core.dto.DeviceType;
import org.wso2.carbon.device.mgt.core.internal.DeviceManagementDataHolder;
@ -248,12 +249,13 @@ public final class DeviceManagementDAOUtil {
return deviceInfo;
}
public static DeviceLocationHistory loadDeviceLocation(ResultSet rs) throws SQLException {
DeviceLocationHistory deviceLocationHistory = new DeviceLocationHistory();
deviceLocationHistory.setDeviceId(rs.getInt("DEVICE_ID"));
deviceLocationHistory.setDeviceIdentifier(rs.getString("DEVICE_ID_NAME"));
public static DeviceLocationHistorySnapshot loadDeviceLocation(ResultSet rs) throws SQLException {
DeviceLocationHistorySnapshot deviceLocationHistory = new DeviceLocationHistorySnapshot();
DeviceIdentifier deviceIdentifier = new DeviceIdentifier();
deviceIdentifier.setId(rs.getString("DEVICE_ID_NAME"));
deviceIdentifier.setType(rs.getString("DEVICE_TYPE_NAME"));
deviceLocationHistory.setDeviceIdentifier(deviceIdentifier);
deviceLocationHistory.setTenantId(rs.getInt("TENANT_ID"));
deviceLocationHistory.setDeviceType(rs.getString("DEVICE_TYPE_NAME"));
deviceLocationHistory.setLatitude(rs.getDouble("LATITUDE"));
deviceLocationHistory.setLongitude(rs.getDouble("LONGITUDE"));
deviceLocationHistory.setSpeed(rs.getFloat("SPEED"));
@ -261,7 +263,7 @@ public final class DeviceManagementDAOUtil {
deviceLocationHistory.setAltitude(rs.getDouble("DEVICE_ALTITUDE"));
deviceLocationHistory.setDistance(rs.getDouble("DISTANCE"));
deviceLocationHistory.setOwner(rs.getString("DEVICE_OWNER"));
deviceLocationHistory.setTimestamp(rs.getLong("TIMESTAMP"));
deviceLocationHistory.setUpdatedTime(new Date(rs.getLong("TIMESTAMP")));
deviceLocationHistory.setGeoHash(rs.getString("GEO_HASH"));
return deviceLocationHistory;
}

@ -312,6 +312,8 @@ public class DeviceInformationManagerImpl implements DeviceInformationManager {
} else {
deviceDetailsDAO.updateDeviceLocation(deviceLocation, device.getEnrolmentInfo().getId());
}
deviceDetailsDAO.addDeviceLocationInfo(device, deviceLocation,
CarbonContext.getThreadLocalCarbonContext().getTenantId());
if (DeviceManagerUtil.isPublishLocationResponseEnabled()) {
Object[] metaData = {device.getDeviceIdentifier(), device.getEnrolmentInfo().getOwner(), device.getType()};
Object[] payload = new Object[]{

@ -46,6 +46,7 @@ import org.wso2.carbon.device.mgt.common.MonitoringOperation;
import org.wso2.carbon.device.mgt.common.StartupOperationConfig;
import org.wso2.carbon.device.mgt.common.OperationMonitoringTaskConfig;
import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManagementException;
import org.wso2.carbon.device.mgt.common.device.details.DeviceLocationHistorySnapshot;
import org.wso2.carbon.device.mgt.common.exceptions.DeviceManagementException;
import org.wso2.carbon.device.mgt.common.exceptions.DeviceNotFoundException;
import org.wso2.carbon.device.mgt.common.exceptions.DeviceTypeNotFoundException;
@ -56,7 +57,6 @@ import org.wso2.carbon.device.mgt.common.configuration.mgt.AmbiguousConfiguratio
import org.wso2.carbon.device.mgt.common.configuration.mgt.ConfigurationManagementException;
import org.wso2.carbon.device.mgt.common.configuration.mgt.DeviceConfiguration;
import org.wso2.carbon.device.mgt.common.configuration.mgt.PlatformConfiguration;
import org.wso2.carbon.device.mgt.common.device.details.DeviceLocationHistory;
import org.wso2.carbon.device.mgt.common.device.details.DeviceData;
import org.wso2.carbon.device.mgt.common.license.mgt.License;
import org.wso2.carbon.device.mgt.common.operation.mgt.Activity;
@ -765,7 +765,7 @@ public interface DeviceManagementProviderService {
* @throws DeviceManagementException
* @return list of device's location histories
*/
List<DeviceLocationHistory> getDeviceLocationInfo(DeviceIdentifier deviceIdentifier, long from, long to)
List<DeviceLocationHistorySnapshot> getDeviceLocationInfo(DeviceIdentifier deviceIdentifier, long from, long to)
throws DeviceManagementException;
/**

@ -64,6 +64,7 @@ import org.wso2.carbon.device.mgt.common.DevicePropertyNotification;
import org.wso2.carbon.device.mgt.common.DeviceEnrollmentInfoNotification;
import org.wso2.carbon.device.mgt.common.DeviceNotification;
import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManagementException;
import org.wso2.carbon.device.mgt.common.device.details.DeviceLocationHistorySnapshot;
import org.wso2.carbon.device.mgt.common.exceptions.DeviceManagementException;
import org.wso2.carbon.device.mgt.common.exceptions.DeviceNotFoundException;
import org.wso2.carbon.device.mgt.common.exceptions.DeviceTypeNotFoundException;
@ -81,7 +82,6 @@ import org.wso2.carbon.device.mgt.common.configuration.mgt.PlatformConfiguration
import org.wso2.carbon.device.mgt.common.device.details.DeviceData;
import org.wso2.carbon.device.mgt.common.device.details.DeviceInfo;
import org.wso2.carbon.device.mgt.common.device.details.DeviceLocation;
import org.wso2.carbon.device.mgt.common.device.details.DeviceLocationHistory;
import org.wso2.carbon.device.mgt.common.enrollment.notification.EnrollmentNotificationConfiguration;
import org.wso2.carbon.device.mgt.common.enrollment.notification.EnrollmentNotifier;
import org.wso2.carbon.device.mgt.common.enrollment.notification.EnrollmentNotifierException;
@ -3013,14 +3013,14 @@ public class DeviceManagementProviderServiceImpl implements DeviceManagementProv
}
@Override
public List<DeviceLocationHistory> getDeviceLocationInfo(DeviceIdentifier deviceIdentifier, long from, long to)
public List<DeviceLocationHistorySnapshot> getDeviceLocationInfo(DeviceIdentifier deviceIdentifier, long from, long to)
throws DeviceManagementException {
if (log.isDebugEnabled()) {
log.debug("Get device location information");
}
List<DeviceLocationHistory> deviceLocationHistory;
List<DeviceLocationHistorySnapshot> deviceLocationHistory;
String errMessage;
try {

@ -32,9 +32,8 @@
<SchedulerTaskEnabled>true</SchedulerTaskEnabled>
<PushNotificationProviders>
<Provider>org.wso2.carbon.device.mgt.extensions.push.notification.provider.fcm.FCMBasedPushNotificationProvider</Provider>
<!--<Provider>org.wso2.carbon.device.mgt.mobile.impl.ios.apns.APNSBasedPushNotificationProvider</Provider>-->
<Provider>org.wso2.carbon.device.mgt.extensions.push.notification.provider.mqtt.MQTTBasedPushNotificationProvider</Provider>
<Provider>org.wso2.carbon.device.mgt.extensions.push.notification.provider.http.HTTPBasedPushNotificationProvider</Provider>
<Provider>org.wso2.carbon.device.mgt.extensions.push.notification.provider.http.HTTPBasedPushNotificationProvider</Provider>
<Provider>org.wso2.carbon.device.mgt.extensions.push.notification.provider.xmpp.XMPPBasedPushNotificationProvider</Provider>
</PushNotificationProviders>
</PushNotificationConfiguration>

@ -32,9 +32,8 @@
<SchedulerTaskEnabled>true</SchedulerTaskEnabled>
<PushNotificationProviders>
<Provider>org.wso2.carbon.device.mgt.extensions.push.notification.provider.fcm.FCMBasedPushNotificationProvider</Provider>
<!--<Provider>org.wso2.carbon.device.mgt.mobile.impl.ios.apns.APNSBasedPushNotificationProvider</Provider>-->
<Provider>org.wso2.carbon.device.mgt.extensions.push.notification.provider.mqtt.MQTTBasedPushNotificationProvider</Provider>
<Provider>org.wso2.carbon.device.mgt.extensions.push.notification.provider.http.HTTPBasedPushNotificationProvider</Provider>
<Provider>org.wso2.carbon.device.mgt.extensions.push.notification.provider.http.HTTPBasedPushNotificationProvider</Provider>
<Provider>org.wso2.carbon.device.mgt.extensions.push.notification.provider.xmpp.XMPPBasedPushNotificationProvider</Provider>
</PushNotificationProviders>
</PushNotificationConfiguration>

@ -67,6 +67,10 @@ public class DataPanel {
@XmlElement(name = "PanelItem")
private List<PanelItem> panelItem;
@XmlElementWrapper(name = "SubFormsList")
@XmlElement(name = "SubForm")
private List<SubFormList> subFormLists;
public String getPaneId() {
return panelId;
}
@ -99,4 +103,11 @@ public class DataPanel {
this.panelItem = panelItem;
}
public List<SubFormList> getSubPanelLists() {
return subFormLists;
}
public void setSubPanelLists(List<SubFormList> subFormLists) {
this.subFormLists = subFormLists;
}
}

@ -0,0 +1,41 @@
/*
* 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;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import java.util.List;
public class SubFormList {
@XmlAttribute(name = "id", required = true)
protected String id;
@XmlElementWrapper(name = "PanelItems")
@XmlElement(name = "PanelItem")
private List<PanelItem> panelItem;
public List<PanelItem> getPanelItemList() {
return panelItem;
}
public void setPanelItemList(List<PanelItem> panelItem) {
this.panelItem = panelItem;
}
}

@ -25,6 +25,9 @@ public class SubPanel {
@XmlElement(name = "PanelKey", required = true)
protected String id;
@XmlElement(name = "SubPanelValues")
private SubPanelValuesList others;
@XmlElementWrapper(name = "PanelItems")
@XmlElement(name = "PanelItem")
private List<PanelItem> panelItem;
@ -37,6 +40,14 @@ public class SubPanel {
this.id = id;
}
public SubPanelValuesList getPayloadAttributes() {
return others;
}
public void setPayloadAttributes(SubPanelValuesList others) {
this.others = others;
}
public List<PanelItem> getPanelItemList1() {
return panelItem;
}

@ -0,0 +1,27 @@
package org.wso2.carbon.device.mgt.extensions.device.type.template.config;
import javax.xml.bind.annotation.XmlElement;
public class SubPanelValuesList {
@XmlElement(name = "PanelSwitch")
private String itemSwitch;
@XmlElement(name = "PayloadKey")
private String itemPayload;
public String getSwitchItem() {
return itemSwitch;
}
public void setSwitchItem(String itemSwitch) {
this.itemSwitch = itemSwitch;
}
public String getPayloadItem() {
return itemPayload;
}
public void setPayloadItem(String itemPayload) {
this.itemPayload = itemPayload;
}
}

@ -16,37 +16,27 @@
under the License.
}}
{{#zone "topCss"}}
{{css "css/platform-configuration.css"}}
{{/zone}}
{{#zone "content"}}
{{#if isAuthorized}}
<div class="row">
<div class="col-md-12">
<!-- content -->
<div id="config-save-form" class="container col-centered wr-content">
<br>
<div class="row">
<div class="col-md-12">
<!-- content -->
<div id="config-save-form" class="container col-centered wr-content">
<br>
<h1 class="page-sub-title">
Platform Configurations
</h1>
<br>
{{#if isCloud}}
iOS Mobile Management Specific Server Settings for the Tenant
{{else}}
<h1 class="page-sub-title">
Platform Configurations
</h1>
<br>
General and Platform Specific Server Settings for the Tenant
{{/if}}
<br>
<br>
<div class="wr-advance-operations">
<div class="row">
<div class="wr-hidden-operations-nav col-lg-4">
<br>
<br>
{{#unless isCloud}}
<a id="generalConfigLink" href="javascript:void(0)" onclick="showAdvanceOperation('general', this)" class="selected">
<div class="wr-advance-operations">
<div class="row">
<div class="wr-hidden-operations-nav col-lg-4">
<a id="generalConfigLink" href="javascript:void(0)"
onclick="showAdvanceOperation('general', this)" class="selected">
<span class="wr-hidden-operations-icon fw-stack">
<i class="fw fw-settings fw-stack-2x"></i>
<span class="fw-stack fw-move-right fw-move-bottom">
@ -57,12 +47,9 @@
</span>
General Configurations
</a>
{{/unless}}
{{#each deviceTypes}}
<a id="{{name}}ConfigLink" href="javascript:void(0)" onclick="showAdvanceOperation('{{name}}', this)"
{{#if isCloud}}
class="selected"
{{/if}} >
{{#each deviceTypes}}
<a id="{{name}}ConfigLink" href="javascript:void(0)"
onclick="showAdvanceOperation('{{name}}', this)">
<span class="wr-hidden-operations-icon fw-stack">
<i class="fw fw-settings fw-stack-2x"></i>
<span class="fw-stack fw-move-right fw-move-bottom">
@ -75,14 +62,13 @@
{{/if}}
</span>
</span>
{{label}} Configurations
</a>
{{/each}}
</div>
{{label}} Configurations
</a>
{{/each}}
</div>
<div class="wr-hidden-operations-content col-lg-8">
<!-- general -->
{{#unless isCloud}}
<div class="wr-hidden-operations-content col-lg-8">
<!-- general -->
<div class="wr-hidden-operation" data-operation="general" style="display: block">
<div class="panel panel-default">
<div id="general-config-heading" class="panel-heading" role="tab">
@ -99,7 +85,7 @@
<div class="wr-input-control">
<label class="wr-input-label" for="email-config-host">
Monitoring Frequency
<span class="helper" title="SMTP Server Host">
<span class="helper" title="SMTP Server Host">
<span class="wr-help-tip glyphicon glyphicon-question-sign"></span>
</span>
<br>
@ -129,54 +115,54 @@
</div>
</div>
{{#if geoServicesEnabled}}
<div id="general-config-heading" role="tab">
<h2 class="sub-title panel-title">
Geo Analytics
</h2>
</div>
<div class="panel-collapse panel-body" role="tabpanel">
<div class="wr-input-control">
<button class="wr-btn" onclick="artifactGeoUpload();">
Deploy Geo Analytics Artifacts
</button>
<div id="general-config-heading" role="tab">
<h2 class="sub-title panel-title">
Geo Analytics
</h2>
</div>
<div class="panel-collapse panel-body" role="tabpanel">
<div class="wr-input-control">
<button class="wr-btn" onclick="artifactGeoUpload();">
Deploy Geo Analytics Artifacts
</button>
</div>
</div>
</div>
{{/if}}
</div>
</div>
{{/unless}}
<!-- general-->
{{#each deviceTypes}}
<div class="wr-hidden-operation" data-operation="{{name}}" style="display: none;">
{{#if unitName}}
{{unit unitName deviceType=name}}
{{else}}
{{unit "cdmf.unit.dynamic.platform.configuration" deviceType=name}}
{{/if}}
</div>
{{/each}}
<!-- general-->
{{#each deviceTypes}}
<div class="wr-hidden-operation" data-operation="{{name}}" style="display: none;">
{{#if unitName}}
{{unit unitName deviceType=name}}
{{else}}
{{unit "cdmf.unit.dynamic.platform.configuration" deviceType=name}}
{{/if}}
</div>
{{/each}}
</div>
</div>
</div>
</div>
</div>
<div id="record-created-msg" class="container col-centered wr-content hidden">
<div class="wr-form">
<p class="page-sub-title">Configuration was saved successfully.</p>
<br>
<br>Please click <b>"Go back to configurations"</b>, if you wish to save another
<div id="record-created-msg" class="container col-centered wr-content hidden">
<div class="wr-form">
<p class="page-sub-title">Configuration was saved successfully.</p>
<br>
<br>Please click <b>"Go back to configurations"</b>, if you wish to save another
configuration or click
<b>"Exit"</b> to complete the process and go back to the dashboard.
<hr/>
<button class="wr-btn"
onclick="window.location.href='{{@app.context}}/platform-configuration'">Go back to configurations</button>
<button class="wr-btn" onclick="window.location.href='{{@app.context}}'">Exit</button>
<b>"Exit"</b> to complete the process and go back to the dashboard.
<hr/>
<button class="wr-btn"
onclick="window.location.href='{{@app.context}}/platform-configuration'">Go back to
configurations
</button>
<button class="wr-btn" onclick="window.location.href='{{@app.context}}'">Exit</button>
</div>
</div>
<!-- /content -->
</div>
<!-- /content -->
</div>
</div>
{{else}}
<h1 class="page-sub-title">
Permission Denied

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save