Device enrollment on new devicemgt react app

4.x.x
Shamalka Navod 5 years ago committed by Dharmakeerthi Lasantha
parent 1e1d2acd01
commit 276414aa24

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 804 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 798 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 KiB

@ -89,12 +89,8 @@ class App extends React.Component {
if (lastURLSegment === 'login') { if (lastURLSegment === 'login') {
window.location.href = window.location.origin + '/entgra/'; window.location.href = window.location.origin + '/entgra/';
} else { } else {
this.setState({
loading: false,
config: config,
});
}
this.getDeviceTypes(config); this.getDeviceTypes(config);
}
}) })
.catch(error => { .catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) { if (error.hasOwnProperty('response') && error.response.status === 401) {

@ -1,74 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { Form, Row, Col, Card, Steps } from 'antd';
import { withConfigContext } from '../../context/ConfigContext';
import DeviceType from './DeviceType';
import EnrollAgent from './EnrollAgent';
const { Step } = Steps;
class AddDevice extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
this.state = {
isAddDeviceModalVisible: false,
current: 0,
};
}
onClickType = () => {
this.setState({
current: 1,
});
};
render() {
const { current } = this.state;
return (
<div>
<Row>
<Col span={16} offset={4}>
<Steps style={{ minHeight: 32 }} current={current}>
<Step key="DeviceType" title="Device Type" />
<Step key="EnrollAgent" title="Enroll Agent" />
<Step key="Result" title="Result" />
</Steps>
</Col>
<Col span={16} offset={4}>
<Card style={{ marginTop: 24 }}>
<div style={{ display: current === 0 ? 'unset' : 'none' }}>
<DeviceType onClickType={this.onClickType} />
</div>
<div style={{ display: current === 1 ? 'unset' : 'none' }}>
<EnrollAgent />
</div>
<div style={{ display: current === 2 ? 'unset' : 'none' }}></div>
</Card>
</Col>
</Row>
</div>
);
}
}
export default withConfigContext(
Form.create({ name: 'add-device' })(AddDevice),
);

@ -17,90 +17,27 @@
*/ */
import React from 'react'; import React from 'react';
import axios from 'axios'; import { Card, Col, Icon, Row } from 'antd';
import { Card, Col, Icon, message, notification, Row } from 'antd';
import TimeAgo from 'javascript-time-ago'; import TimeAgo from 'javascript-time-ago';
// Load locale-specific relative date/time formatting rules. // Load locale-specific relative date/time formatting rules.
import en from 'javascript-time-ago/locale/en'; import en from 'javascript-time-ago/locale/en';
import { withConfigContext } from '../../context/ConfigContext'; import { withConfigContext } from '../../context/ConfigContext';
let apiUrl;
class DeviceType extends React.Component { class DeviceType extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
TimeAgo.addLocale(en); TimeAgo.addLocale(en);
this.config = this.props.context;
this.state = { this.state = {
data: [], data: this.config.deviceTypes,
pagination: {}, pagination: {},
loading: false, loading: false,
selectedRows: [], selectedRows: [],
}; };
} }
componentDidMount() { onClickCard = (e, deviceType) => {
this.fetchUsers(); this.props.getDeviceType(deviceType);
}
onClickCard = data => {
console.log(data);
this.props.onClickType();
};
// fetch data from api
fetchUsers = (params = {}) => {
const config = this.props.context;
this.setState({ loading: true });
apiUrl =
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/device-types';
// send request to the invokerss
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,
});
}
})
.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 types.',
});
}
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() { render() {
@ -112,7 +49,7 @@ class DeviceType extends React.Component {
size="default" size="default"
style={{ width: 150 }} style={{ width: 150 }}
bordered={true} bordered={true}
onClick={this.onClickCard} onClick={e => this.onClickCard(e, data.name)}
cover={ cover={
<Icon <Icon
type="android" type="android"

@ -0,0 +1,128 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { Form, Row, Col, Card, Steps } from 'antd';
import { withConfigContext } from '../../../context/ConfigContext';
import DeviceType from '../DeviceType';
import SelectEnrollmentType from './SelectEnrollmentType';
import EnrollDevice from './EnrollDevice';
import DownloadAgent from './DownloadAgent';
const { Step } = Steps;
class AddDevice extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
this.state = {
isAddDeviceModalVisible: false,
current: 0,
deviceType: 'android',
enrollmentType: 'qr',
};
}
getDeviceType = deviceType => {
this.setState({
current: 1,
deviceType: deviceType,
});
};
goNext = () => {
this.setState({
current: 2,
});
};
goBackToDeviceType = () => {
this.setState({
current: 0,
});
};
goBackToDownloadAgent = () => {
this.setState({
current: 1,
});
};
goBackToEnrollmentType = () => {
this.setState({
current: 2,
});
};
getEnrollmentData = enrollmentType => {
this.setState({
current: 3,
enrollmentType: enrollmentType,
});
};
render() {
const { current, deviceType, enrollmentType } = this.state;
return (
<div>
<Row>
<Col span={16} offset={4}>
<Steps style={{ minHeight: 32 }} current={current}>
<Step key="DeviceType" title="Select Device Type" />
<Step key="DownloadAgent" title="Download Agent" />
<Step key="EnrollmentType" title="Enrollment Type" />
<Step key="EnrollDevice" title="Enroll Device" />
</Steps>
</Col>
<Col span={16} offset={4}>
<Card style={{ marginTop: 24 }}>
<div style={{ display: current === 0 ? 'unset' : 'none' }}>
<DeviceType getDeviceType={this.getDeviceType} />
</div>
<div style={{ display: current === 1 ? 'unset' : 'none' }}>
<DownloadAgent
deviceType={deviceType}
goNext={this.goNext}
goBack={this.goBackToDeviceType}
/>
</div>
<div style={{ display: current === 2 ? 'unset' : 'none' }}>
<SelectEnrollmentType
deviceType={deviceType}
getEnrollmentData={this.getEnrollmentData}
goBack={this.goBackToDownloadAgent}
/>
</div>
<div style={{ display: current === 3 ? 'unset' : 'none' }}>
<EnrollDevice
deviceType={deviceType}
enrollmentType={enrollmentType}
goBack={this.goBackToEnrollmentType}
/>
</div>
</Card>
</Col>
</Row>
</div>
);
}
}
export default withConfigContext(
Form.create({ name: 'add-device' })(AddDevice),
);

@ -0,0 +1,173 @@
/*
* 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, Card, Divider, message, notification } from 'antd';
import TimeAgo from 'javascript-time-ago/modules/JavascriptTimeAgo';
import en from 'javascript-time-ago/locale/en';
import axios from 'axios';
import { withConfigContext } from '../../../context/ConfigContext';
import QRCode from 'qrcode.react';
class SelectEnrollmentType extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
TimeAgo.addLocale(en);
this.state = {
pagination: {},
loading: false,
selectedRows: [],
buttonTitle: 'Download Agent',
skipButtonTitle: 'Skip',
};
}
onClickSkip = () => {
this.props.goNext();
};
onClickGoBack = () => {
this.props.goBack();
};
onClickDownloadAgent = () => {
this.downloadAgent();
};
// fetch data from api
downloadAgent = () => {
const { deviceType } = this.props;
this.setState({ loading: true, buttonTitle: 'Downloading..' });
const apiUrl =
window.location.origin +
'/api/application-mgt/v1.0/artifact/' +
deviceType +
'/agent/-1234';
// send request to the invokerss
axios
.get(apiUrl)
.then(res => {
if (res.status === 200) {
// Download file in same window
const url = window.URL.createObjectURL(new Blob([res.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'android-agent.apk'); // or any other extension
document.body.appendChild(link);
link.click();
this.setState({
loading: false,
buttonTitle: 'Download Agent',
skipButtonTitle: 'Next',
});
}
})
.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 download Entgra Android Agent',
});
}
this.setState({ loading: false });
});
};
render() {
const { loading, buttonTitle, skipButtonTitle } = this.state;
const { deviceType } = this.props;
const apiUrl =
window.location.origin +
'/api/application-mgt/v1.0/artifact/' +
deviceType +
'/agent/-1234';
return (
<div>
<Divider orientation="left">Step 01 - Get your Android Agent.</Divider>
<div>
<p>
The Android agent can be downloaded by using following QR. The
generated QR code can be scanned, and the agent APK downloaded from
the link, and transferred to the device and then installed.
</p>
</div>
<div style={{ margin: '30px', textAlign: 'center' }}>
<Divider>Scan to get the Android Agent.</Divider>
<div
style={{
marginBottm: 10,
display: 'inline-block',
}}
>
<Card hoverable>
<QRCode size={200} value={apiUrl} />
</Card>
</div>
<Divider>OR</Divider>
<div style={{ textAlign: 'center', marginTop: 10, marginBottom: 10 }}>
<Button
type="primary"
size={'default'}
onClick={this.onClickDownloadAgent}
loading={loading}
>
{buttonTitle}
</Button>
</div>
<p>
Need help? Read&nbsp;
<a
href={
'https://entgra-documentation.gitlab.io/v3.8.0/docs/guide-to-work-with-the-product/' +
'enrollment-guide/enroll-android/'
}
>
Entgra IoT Server documentation.
</a>
</p>
</div>
<div style={{ textAlign: 'right' }}>
<Button type="primary" size={'default'} onClick={this.onClickSkip}>
{skipButtonTitle}
</Button>
<Button
style={{ marginLeft: 10 }}
type="default"
size={'default'}
onClick={this.onClickGoBack}
>
Back
</Button>
</div>
</div>
);
}
}
export default withConfigContext(SelectEnrollmentType);

@ -0,0 +1,217 @@
/*
* 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,
message,
notification,
Select,
Card,
Spin,
Button,
Row,
Col,
} from 'antd';
import TimeAgo from 'javascript-time-ago/modules/JavascriptTimeAgo';
import en from 'javascript-time-ago/locale/en';
import { withConfigContext } from '../../../context/ConfigContext';
import axios from 'axios';
import QRCode from 'qrcode.react';
import QRPlaceholder from '../../../../public/images/qr-code.png';
import installAgent from '../../../../public/images/install_agent.png';
import register from '../../../../public/images/register.png';
import registration from '../../../../public/images/registration.png';
import setProfile from '../../../../public/images/set_profile.png';
const { Option } = Select;
class EnrollDevice extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
TimeAgo.addLocale(en);
this.state = {
isSelected: false,
loading: false,
payload: {},
};
}
// fetch data from api
generateQRCode = ownershipType => {
const { deviceType } = this.props;
this.setState({ loading: true });
let apiUrl =
window.location.origin +
this.config.serverConfig.invoker.uri +
'/device-mgt/' +
deviceType +
'/v1.0/configuration/enrollment-qr-config/' +
ownershipType;
// send request to the invokerss
axios
.get(apiUrl)
.then(res => {
if (res.status === 200) {
this.setState({
loading: false,
payload: res.data.data,
isSelected: true,
});
}
})
.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 get QR code payload.',
});
}
this.setState({ loading: false });
});
};
onChange = value => {
this.generateQRCode(value);
};
onClick = () => {
this.props.goBack();
};
render() {
const { payload, isSelected, loading } = this.state;
const { enrollmentType } = this.props;
return (
<div style={{ textAlign: 'center', fontSize: 12 }}>
<div
style={{
display: enrollmentType === 'manual' ? 'inline-block' : 'none',
}}
>
<Row gutter={[16, 16]}>
<Col span={6}>
<h1>Step 1</h1>
<p>
{/* eslint-disable-next-line react/no-unescaped-entities */}
Let's start by installing the Android agent on your device. Open
the downloaded file, and tap INSTALL.
</p>
<img src={installAgent} />
</Col>
<Col span={6}>
<h1>Step 2</h1>
<p>Tap Skip to proceed with the default enrollment process.</p>
<img src={setProfile} />
</Col>
<Col span={6}>
<h1>Step 3</h1>
<p>
Enter the server address based on your environment, in the text
box provided.
</p>
<img src={registration} />
</Col>
<Col span={6}>
<h1>Step 4</h1>
<p>Enter your:</p>
<div>
<p>Organization: carbon.super</p>
<p>Username: admin</p>
<p>Password: Your password</p>
</div>
<img src={register} />
</Col>
</Row>
</div>
<div
style={{
display: enrollmentType === 'qr' ? 'inline-block' : 'none',
}}
>
<Divider>Generate QR code to QR based Provisioning.</Divider>
<div style={{ textAlign: 'center' }}>
<div style={{ marginBottom: 10 }}>
<Select
showSearch
style={{ width: 200, textAlign: 'center' }}
placeholder="Select device ownership"
optionFilterProp="children"
onChange={this.onChange}
filterOption={(input, option) =>
option.props.children
.toLowerCase()
.indexOf(input.toLowerCase()) >= 0
}
>
<Option key={'byod'} value={'BYOD'}>
{'BYOD'}
</Option>
<Option key={'cope'} value={'COPE'}>
{'COPE'}
</Option>
<Option key={'COSU'} value={'COSU'}>
{'COSU (KIOSK)'}
</Option>
<Option key={'WORK PROFILE'} value={'WORK_PROFILE'}>
{'WORK PROFILE'}
</Option>
<Option key={'GOOGLE_ENTERPRISE'} value={'GOOGLE_ENTERPRISE'}>
{'GOOGLE_WORK_PROFILE'}
</Option>
</Select>
</div>
<Spin spinning={loading}>
<div
style={{
display: isSelected ? 'inline-block' : 'none',
}}
>
<Card hoverable>
<QRCode size={300} value={JSON.stringify(payload)} />
</Card>
</div>
<div style={{ display: !isSelected ? 'inline-block' : 'none' }}>
<Card hoverable>
<img src={QRPlaceholder} />
</Card>
</div>
</Spin>
</div>
</div>
<div style={{ textAlign: 'right', marginTop: 10 }}>
<Button type="default" size={'default'} onClick={this.onClick}>
Back
</Button>
</div>
</div>
);
}
}
export default withConfigContext(EnrollDevice);

@ -17,73 +17,38 @@
*/ */
import React from 'react'; import React from 'react';
import { Button, Divider, message, notification } from 'antd'; import { Button, Divider } from 'antd';
import TimeAgo from 'javascript-time-ago/modules/JavascriptTimeAgo'; import TimeAgo from 'javascript-time-ago/modules/JavascriptTimeAgo';
import en from 'javascript-time-ago/locale/en'; import en from 'javascript-time-ago/locale/en';
import axios from 'axios'; import { withConfigContext } from '../../../context/ConfigContext';
import { withConfigContext } from '../../context/ConfigContext';
class EnrollAgent extends React.Component { class SelectEnrollmentType extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.config = this.props.context; this.config = this.props.context;
TimeAgo.addLocale(en); TimeAgo.addLocale(en);
this.state = { this.state = {
data: [],
pagination: {}, pagination: {},
loading: false, loading: false,
selectedRows: [], selectedRows: [],
visibleSelector: { display: 'none' },
}; };
} }
componentDidMount() {
this.getConfigData();
}
onGetEnrollmentQR = () => { onEnrollWithQR = () => {
this.setState({ this.props.getEnrollmentData('qr');
visibleSelector: { display: 'block' },
});
}; };
getConfigData = () => { onEnrollManually = () => {
axios this.props.getEnrollmentData('manual');
.get( };
window.location.origin +
this.config.serverConfig.invoker.uri + onClick = () => {
'/device-mgt/android/v1.0/configuration', this.props.goBack();
)
.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 retrieving device groups.',
});
}
});
}; };
render() { render() {
return ( return (
<div> <div>
<Divider orientation="left">Step 01 - Get your Android Agent.</Divider>
<div>
<p>
The Android agent can be downloaded by using following QR. The
generated QR code can be scanned, and the agent APK downloaded from
the link, and transferred to the device and then installed.
</p>
</div>
<div style={{ margin: '30px' }}>
<Button type="primary" size={'default'}>
Get Android Agent
</Button>
</div>
<Divider orientation="left"> <Divider orientation="left">
Step 02 - Enroll the Android Agent. Step 02 - Enroll the Android Agent.
</Divider> </Divider>
@ -102,14 +67,28 @@ class EnrollAgent extends React.Component {
<Button <Button
type="primary" type="primary"
size={'default'} size={'default'}
onClick={this.onGetEnrollmentQR} onClick={this.onEnrollWithQR}
style={{ marginRight: 10 }}
> >
Enroll Using QR Enroll Using QR
</Button> </Button>
<Button
type="primary"
size={'default'}
onClick={this.onEnrollManually}
>
Enroll Manually
</Button>
</div>
<div style={{ textAlign: 'right' }}>
<Button type="default" size={'default'} onClick={this.onClick}>
Back
</Button>
</div> </div>
</div> </div>
); );
} }
} }
export default withConfigContext(EnrollAgent); export default withConfigContext(SelectEnrollmentType);

@ -32,6 +32,8 @@ import DeviceStatusReport from './components/Reports/Templates/DeviceStatusRepor
import PolicyReportHome from './pages/Dashboard/Reports/PolicyReportHome'; import PolicyReportHome from './pages/Dashboard/Reports/PolicyReportHome';
import ReportDurationItemList from './pages/Dashboard/Reports/ReportDurationItemList'; import ReportDurationItemList from './pages/Dashboard/Reports/ReportDurationItemList';
import AppNotInstalledDevicesReport from './components/Reports/Templates/AppNotInstalledDevicesReport'; import AppNotInstalledDevicesReport from './components/Reports/Templates/AppNotInstalledDevicesReport';
import Devices from './pages/Dashboard/Devices/Devices';
import DeviceEnroll from './pages/Dashboard/Devices/DeviceEnroll';
const routes = [ const routes = [
{ {
@ -44,16 +46,16 @@ const routes = [
exact: false, exact: false,
component: Dashboard, component: Dashboard,
routes: [ routes: [
// { {
// path: '/entgra/devices', path: '/entgra/devices',
// component: Devices, component: Devices,
// exact: true, exact: true,
// }, },
// { {
// path: '/entgra/devices/enroll', path: '/entgra/devices/enroll',
// component: DeviceEnroll, component: DeviceEnroll,
// exact: true, exact: true,
// }, },
{ {
path: '/entgra/geo', path: '/entgra/geo',
component: Geo, component: Geo,

@ -72,26 +72,26 @@ class Dashboard extends React.Component {
marginRight: 110, marginRight: 110,
}} }}
> >
{/* <SubMenu*/} <SubMenu
{/* key="devices"*/} key="devices"
{/* title={*/} title={
{/* <span>*/} <span>
{/* <Icon type="appstore" />*/} <Icon type="appstore" />
{/* <span>Devices</span>*/} <span>Devices</span>
{/* </span>*/} </span>
{/* }*/} }
{/* >*/} >
{/* <Menu.Item key="devices">*/} <Menu.Item key="devices">
{/* <Link to="/entgra/devices">*/} <Link to="/entgra/devices">
{/* <span>View</span>*/} <span>View</span>
{/* </Link>*/} </Link>
{/* </Menu.Item>*/} </Menu.Item>
{/* <Menu.Item key="deviceEnroll">*/} <Menu.Item key="deviceEnroll">
{/* <Link to="/entgra/devices/enroll">*/} <Link to="/entgra/devices/enroll">
{/* <span>Enroll</span>*/} <span>Enroll</span>
{/* </Link>*/} </Link>
{/* </Menu.Item>*/} </Menu.Item>
{/* </SubMenu>*/} </SubMenu>
<SubMenu <SubMenu
key="geo" key="geo"
title={ title={

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { PageHeader, Typography, Breadcrumb, Icon } from 'antd'; import { PageHeader, Typography, Breadcrumb, Icon } from 'antd';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import AddDevice from '../../../components/Devices/AddDevice'; import AddDevice from '../../../components/Devices/Enroll-Device/AddDevice';
const { Paragraph } = Typography; const { Paragraph } = Typography;

Loading…
Cancel
Save