Merge branch 'application-mgt-new' of gitlab.com:entgra/carbon-device-mgt into application-mgt-new

feature/appm-store/pbac
lasanthaDLPDS 5 years ago
commit 8feb157bb9

@ -30,7 +30,7 @@ import {
Spin,
message,
Icon,
Card
Card, Badge
} from 'antd';
import DetailedRating from "../../detailed-rating/DetailedRating";
import {Link} from "react-router-dom";
@ -487,18 +487,14 @@ class AppDetailsDrawer extends React.Component {
)}
<Text strong={true}>Releases </Text>
{/*display add new release only if app type is enterprise*/}
<div className="releases-details">
{(app.type === "ENTERPRISE") && (
<Link to={`/publisher/apps/${app.deviceType}/${app.id}/add-release`}><Button
htmlType="button"
size="small">Add
new release</Button></Link>)}
<List
style={{paddingTop: 16}}
grid={{gutter: 16, column: 2}}
pagination={{
pageSize: 4, // number of releases per page
size: "small",
}}
dataSource={app.applicationReleases}
renderItem={release => (
<div className="app-release-cards">
@ -507,7 +503,27 @@ class AppDetailsDrawer extends React.Component {
<Card className="release-card">
<Meta
avatar={
<Avatar size="large" shape="square" src={release.iconPath}/>
<div>
{(release.currentStatus === "PUBLISHED") ? (
<Badge
title="Published"
style={{
backgroundColor: '#52c41a',
borderRadius:"50%",
color:"white"
}}
count={
<Icon
type="check-circle"/>
}>
<Avatar size="large" shape="square"
src={release.iconPath}/>
</Badge>
) : (
<Avatar size="large" shape="square"
src={release.iconPath}/>
)}
</div>
}
title={release.version}
description={
@ -529,10 +545,27 @@ class AppDetailsDrawer extends React.Component {
</div>
)}
/>
</div>
<Divider dashed={true}/>
{/*display add new release only if app type is enterprise*/}
{(app.type === "ENTERPRISE") && (
<div>
<div style={{paddingBottom: 16}}>
<Text>
Add new release for the application
</Text>
</div>
<Link to={`/publisher/apps/${app.deviceType}/${app.id}/add-release`}>
<Button
htmlType="button"
type="primary"
size="small">
Add
</Button>
</Link>
</div>)}
<Divider dashed={true}/>
<Text strong={true}>Description </Text>
{!isDescriptionEditEnabled && (

@ -17,7 +17,7 @@
*/
import React from "react";
import {Avatar, Table, Tag, Icon, message, notification, Col} from "antd";
import {Avatar, Table, Tag, Icon, message, notification, Col, Badge} from "antd";
import axios from "axios";
import pSBC from 'shade-blend-color';
import "./AppsTable.css";
@ -47,7 +47,31 @@ const columns = [
</Avatar>
);
} else {
avatar = (
const {applicationReleases} = row;
let hasPublishedRelease = false;
for (let i = 0; i < applicationReleases.length; i++) {
if (applicationReleases[i].currentStatus === "PUBLISHED") {
hasPublishedRelease = true;
break;
}
}
avatar = (hasPublishedRelease) ? (
<Badge
title="Published"
style={{ backgroundColor: '#52c41a', borderRadius:"50%", color:"white"}}
count={
<Icon
type="check-circle"/>
}>
<Avatar shape="square" size="large"
style={{
borderRadius: "28%",
border: "1px solid #ddd"
}}
src={row.applicationReleases[0].iconPath}
/>
</Badge>
) : (
<Avatar shape="square" size="large"
style={{
marginRight: 20,
@ -56,13 +80,13 @@ const columns = [
}}
src={row.applicationReleases[0].iconPath}
/>
)
);
}
return (
<div>
{avatar}
{name}
<span style={{marginLeft: 20}}>{name}</span>
</div>);
}
},

@ -24,6 +24,7 @@ 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";
const {Text} = Typography;
@ -180,19 +181,7 @@ class InstalledDevicesTable extends React.Component {
}
}).catch((error) => {
if (error.hasOwnProperty("response") && error.response.status === 401) {
//todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification["error"]({
message: "There was a problem",
duration: 0,
description:
"Error occurred while trying to load devices.",
});
}
handleApiError(error, "Something went wrong when trying to load subscription data.");
this.setState({loading: false});
});
};
@ -203,10 +192,7 @@ class InstalledDevicesTable extends React.Component {
<div>
<div style={{paddingBottom: 24}}>
<Text>
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque
laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo.
The following are the subscription details of the application in each respective device.
</Text>
</div>
<Table

@ -21,12 +21,9 @@ import {Divider, Row, Col, Typography, Button, Dropdown, notification, Menu, Ico
import "../../../App.css";
import ImgViewer from "../../apps/release/images/ImgViewer";
import StarRatings from "react-star-ratings";
import DetailedRating from "./DetailedRating";
import Reviews from "./review/Reviews";
import axios from "axios";
import AppInstallModal from "./install/AppInstallModal";
import AppUninstallModal from "./install/AppUninstallModal";
import CurrentUsersReview from "./review/CurrentUsersReview";
import {withConfigContext} from "../../../context/ConfigContext";
import {handleApiError} from "../../../js/Utils";
import ReviewContainer from "./review/ReviewContainer";
@ -207,7 +204,7 @@ class ReleaseView extends React.Component {
<Divider/>
<ReviewContainer uuid={release.uuid}/>
</TabPane>
<TabPane tab="Installed devices" key="2">
<TabPane tab="Subscription Details" key="2">
<InstalledDevicesTable uuid={release.uuid}/>
</TabPane>
</Tabs>

@ -17,12 +17,11 @@
"moment": "latest",
"prop-types": "latest",
"rc-viewer": "0.0.9",
"react-advanced-datetimerange-picker": "^1.0.8",
"react-bootstrap": "^1.0.0-beta.12",
"react-highlight-words": "^0.16.0",
"react-image-viewer-zoom": "^1.0.36",
"react-infinite-scroller": "^1.2.4",
"react-leaflet": "^2.4.0",
"react-bootstrap": "^1.0.0-beta.12",
"react-moment": "^0.9.2",
"react-router": "^5.0.1",
"react-router-config": "^5.0.1",
@ -31,6 +30,7 @@
"react-star-ratings": "^2.3.0",
"react-twemoji": "^0.2.3",
"react-virtualized": "^9.21.1",
"react-websocket": "^2.1.0",
"reqwest": "^2.0.5",
"storm-react-diagrams": "^5.2.1"
},

@ -18,9 +18,8 @@
import React from "react";
import axios from "axios";
import {Tag, message, notification, Table, Typography, Tooltip, Icon, Divider, Card, Col, Row, Select} from "antd";
import {Card, Col, Icon, message, notification, Row, Typography} 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 "../../context/ConfigContext";
@ -113,16 +112,27 @@ class DeviceTypesTable extends React.Component {
render() {
const {data, pagination, loading, selectedRows} = this.state;
const { Meta } = Card;
const itemCard = data.map((data) =>
<Col span={8} key={data.id}>
<Card hoverable title="Device Type" bordered={true}>
{data.name}
<Col span={5} key={data.id}>
<Card
size="default"
style={{ width: 200 }}
bordered={true}
actions={[
<Icon type="setting" key="setting" />,
<Icon type="edit" key="edit" />,]}
>
<Meta
avatar={<Icon type="desktop" key="device-types"/>}
title={data.name}
/>
</Card>
</Col>
);
return (
<div style={{ background: '#ECECEC', padding: '30px' }}>
<div style={{ background: '#ECECEC', padding: '20px' }}>
<Row gutter={16}>
{itemCard}
</Row>

@ -18,9 +18,8 @@
import React from "react";
import axios from "axios";
import {Tag, message, notification, Table, Typography, Tooltip, Icon, Divider, Button, Modal, Select} from "antd";
import {Icon, message, Modal, notification, Select, Table, Tag, Tooltip, Typography} 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 "../../context/ConfigContext";
@ -133,18 +132,11 @@ class DeviceTable extends React.Component {
selectedRows: [],
deviceGroups: [],
groupModalVisible: false,
selectedGroupId: []
selectedGroupId: [],
selectedRowKeys:[]
};
}
rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
this.setState({
selectedRows: selectedRows
});
}
};
componentDidMount() {
this.fetch();
}
@ -249,6 +241,9 @@ class DeviceTable extends React.Component {
).then(res => {
if (res.status === 200) {
this.fetch();
this.setState({
selectedRowKeys:[]
})
notification["success"]({
message: "Done",
duration: 4,
@ -409,11 +404,30 @@ class DeviceTable extends React.Component {
});
};
render() {
const {data, pagination, loading, selectedRows} = this.state;
onSelectChange = (selectedRowKeys, selectedRows) => {
this.setState({
selectedRowKeys,
selectedRows: selectedRows
});
};
render() {
const {data, pagination, loading, selectedRows, selectedRowKeys} = this.state;
const isSelectedSingle = this.state.selectedRows.length == 1;
let selectedText;
if(isSelectedSingle){
selectedText = "You have selected 1 device"
}else{
selectedText = "You have selected " + this.state.selectedRows.length + " devices"
}
const rowSelection = {
selectedRowKeys,
selectedRows,
onChange: this.onSelectChange,
};
let item = this.state.deviceGroups.map((data) =>
<Select.Option
value={data.id}
@ -441,8 +455,7 @@ class DeviceTable extends React.Component {
}}
loading={loading}
onChange={this.handleTableChange}
rowSelection={this.rowSelection}
scroll={{x: 1000}}
rowSelection={rowSelection}
/>
</div>
@ -453,10 +466,11 @@ class DeviceTable extends React.Component {
onOk={this.handleOk}
onCancel={this.handleCancel}
>
<p>{selectedText}</p>
<Select
mode={isSelectedSingle ? "multiple" : "default"}
showSearch
style={{width: 200}}
style={{display:"block"}}
placeholder="Select Group"
optionFilterProp="children"
onChange={this.onGroupSelectChange}

@ -18,9 +18,8 @@
import React from "react";
import axios from "axios";
import {Tag, message, notification, Table, Typography, Tooltip, Icon, Divider} from "antd";
import {Icon, message, notification, Table, Tag, Tooltip, Typography} 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 "../../context/ConfigContext";
@ -239,7 +238,6 @@ class ReportDeviceTable extends React.Component {
loading={loading}
onChange={this.handleTableChange}
rowSelection={this.rowSelection}
scroll={{x: 1000}}
/>
</div>
);

@ -18,8 +18,7 @@
import React from "react";
import moment from "moment";
import DateTimeRangeContainer from "react-advanced-datetimerange-picker";
import {Button, Select, message, notification, Tag, Tooltip, Empty} from "antd";
import {Button, Select, message, notification, Tag, Tooltip, Empty, DatePicker} from "antd";
import axios from "axios";
import {withConfigContext} from "../../../context/ConfigContext";
import GeoCustomMap from "../geo-custom-map/GeoCustomMap";
@ -29,15 +28,16 @@ class GeoDashboard extends React.Component {
constructor(props) {
super(props);
let start = moment(new Date());
let start = moment(
new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate(), 0, 0, 0, 0)
);
let end = moment(start)
.add(5, "days")
.subtract(1, "minute");
.add(1, "days")
.subtract(1, "seconds");
this.state = {
deviceData: [],
selectedDevice: '',
locationData: [],
// currentLocation: [],
loading: false,
start: start,
end: end,
@ -55,11 +55,11 @@ class GeoDashboard extends React.Component {
* @param startDate - start date
* @param endDate - end date
*/
applyCallback = (startDate, endDate) => {
applyCallback = (dates, dateStrings) => {
console.log("Apply Callback");
this.setState({
start: startDate,
end: endDate
start: dateStrings[0],
end: dateStrings[1]
});
};
@ -180,6 +180,7 @@ class GeoDashboard extends React.Component {
*/
controllerBar = () => {
const {RangePicker} = DatePicker;
let now = new Date();
let start = moment(
new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0)
@ -199,32 +200,20 @@ class GeoDashboard extends React.Component {
"2 Weeks": [moment(start).subtract(14, "days"), moment(end)],
"1 Month": [moment(start).subtract(1, "months"), moment(end)],
};
let local = {
format: "DD-MM-YYYY HH:mm",
sundayFirst: false
};
let maxDate = moment(start).add(24, "hour");
let value =
`
${this.state.start.format("DD-MM-YYYY HH:mm")} - ${this.state.end.format("DD-MM-YYYY HH:mm")}
`;
let {deviceData} = this.state;
return (
<div className="controllerDiv">
<Button style={{marginRight: 20}}>
<DateTimeRangeContainer
<RangePicker
ranges={ranges}
start={this.state.start}
end={this.state.end}
local={local}
maxDate={maxDate}
applyCallback={this.applyCallback}
>
{value}
</DateTimeRangeContainer>
</Button>
style={{marginRight: 20}}
showTime
format="YYYY-MM-DD HH:mm:ss"
defaultValue={[this.state.start, this.state.end]}
onChange={this.applyCallback}
/>
<Select
showSearch

@ -0,0 +1,175 @@
/*
* Copyright (c) 2019, 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, message, Modal, notification, Typography} from "antd";
import axios from "axios";
import {withConfigContext} from "../../context/ConfigContext";
const {Text} = Typography;
let config = null;
class AddGroup extends React.Component {
constructor(props) {
super(props);
config = this.props.context;
this.state = {
addModalVisible: false,
name:'',
description:'',
}
}
onConfirmAdGroup = () => {
const config = this.props.context;
const groupData = {
name: this.state.name,
description: this.state.description
}
//send request to the invoker
axios.post(
window.location.origin + config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
"/groups",
groupData,
{headers: {'Content-Type': 'application/json'}}
).then(res => {
if (res.status === 201) {
this.props.fetchGroups();
notification["success"]({
message: "Done",
duration: 4,
description:
"Successfully added the group.",
});
}
}).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 group.",
});
}
});
};
openAddModal = () => {
this.setState({
addModalVisible:true
})
};
handleAddOk = e => {
this.props.form.validateFields(err => {
if (!err) {
this.onConfirmAdGroup();
this.setState({
addModalVisible: false,
});
}
});
};
handleAddCancel = e => {
this.setState({
addModalVisible: false,
});
};
onChangeName = (e) => {
this.setState({
name:e.currentTarget.value
})
};
onChangeDescription = (e) => {
this.setState({
description:e.currentTarget.value
})
};
render() {
const { getFieldDecorator } = this.props.form;
return(
<div>
<div>
<Button type="primary" icon="plus" size={"default"} onClick={this.openAddModal}>
Add Group
</Button>
</div>
<div>
<Modal
title="ADD NEW GROUP"
width="40%"
visible={this.state.addModalVisible}
onOk={this.handleAddOk}
onCancel={this.handleAddCancel}
footer={[
<Button key="cancel" onClick={this.handleAddCancel}>
Cancel
</Button>,
<Button key="submit" type="primary" onClick={this.handleAddOk}>
Submit
</Button>,
]}
>
<div style={{alignItems:"center"}}>
<p>Create new device group on IoT Server.</p>
<Form
labelCol={{ span: 5 }}
wrapperCol={{ span: 18 }}
>
<Form.Item label="Name" style={{display:"block"}}>
{getFieldDecorator('name', {
rules: [
{
required: true,
message: 'Please input group name',
},
],
})(<Input onChange={this.onChangeName}/>)}
</Form.Item>
<Form.Item label="Description" style={{display:"block"}}>
{getFieldDecorator('description', {
rules: [
{
required: true,
message: 'Please input group description',
},
],
})(<Input onChange={this.onChangeDescription}/>)}
</Form.Item>
</Form>
</div>
</Modal>
</div>
</div>
)
}
}
export default withConfigContext(Form.create({name: 'add-group'})(AddGroup));

@ -0,0 +1,379 @@
/*
* Copyright (c) 2019, 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,
Divider,
Form,
Icon,
Input,
message,
Modal,
notification,
Popconfirm,
Select,
Tooltip,
Typography
} from "antd";
import axios from "axios";
import {withConfigContext} from "../../context/ConfigContext";
const {Text} = Typography;
let config = null;
class GroupActions extends React.Component {
constructor(props) {
super(props);
config = this.props.context;
this.state = {
editModalVisible: false,
shareModalVisible: false,
name:this.props.data.name,
description:this.props.data.description,
groupDataObject:{},
rolesData:[],
shareRolesData:[]
}
}
onConfirmDeleteGroup = () => {
const config = this.props.context;
//send request to the invoker
axios.delete(
window.location.origin + config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
"/groups/id/" + this.props.data.id,
{headers: {'Content-Type': 'application/json'}}
).then(res => {
if (res.status === 200) {
this.props.fetchGroups();
notification["success"]({
message: "Done",
duration: 4,
description:
"Successfully deleted the group.",
});
}
}).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 delete group.",
});
}
});
};
onConfirmUpdateGroup = (data) => {
const config = this.props.context;
//send request to the invoker
axios.put(
window.location.origin + config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
"/groups/id/" + this.props.data.id,
data
).then(res => {
if (res.status === 200) {
this.props.fetchGroups();
notification["success"]({
message: "Done",
duration: 4,
description:
"Successfully updated the group.",
});
}
}).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 update group.",
});
}
});
};
fetchUserRoles = (params = {}) => {
const config = this.props.context;
const apiUrl = window.location.origin + config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
"/roles";
//send request to the invokerss
axios.get(apiUrl).then(res => {
if (res.status === 200) {
this.setState({
rolesData: 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.",
});
}
});
};
onConfirmShareGroup = (data) => {
const config = this.props.context;
//send request to the invoker
axios.post(
window.location.origin + config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
"/groups/id/" + this.props.data.id + "/share",
data
).then(res => {
if (res.status === 200) {
this.props.fetchGroups();
notification["success"]({
message: "Done",
duration: 4,
description:
"Successfully shared the group.",
});
}
}).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 share group.",
});
}
});
}
openEditModal = () => {
this.setState({
editModalVisible:true
})
};
openShareModal = () => {
this.fetchUserRoles();
this.setState({
shareModalVisible:true
})
}
handleEditOk = e => {
this.state.groupDataObject = {
name:this.state.name,
description:this.state.description,
id:this.props.data.id,
owner:this.props.data.owner,
groupProperties:this.props.data.groupProperties
};
this.props.form.validateFields(err => {
if (!err) {
this.onConfirmUpdateGroup(this.state.groupDataObject);
this.setState({
editModalVisible: false,
});
}
});
};
handleEditCancel = e => {
this.setState({
editModalVisible: false,
});
};
handleShareOk = e => {
this.setState({
shareModalVisible: false,
});
this.onConfirmShareGroup(this.state.shareRolesData);
};
handleShareCancel = e => {
this.setState({
shareModalVisible: false,
});
};
onChangeName = (e) => {
this.setState({
name:e.currentTarget.value
})
};
onChangeDescription = (e) => {
this.setState({
description:e.currentTarget.value
})
};
handleRolesDropdownChange = (value) => {
this.setState({
shareRolesData:value
})
};
render() {
const isAdminGroups = this.props.data.id==1 || this.props.data.id==2;
const { Option } = Select;
const { getFieldDecorator } = this.props.form;
let item = this.state.rolesData.map((data) =>
<Select.Option
value={data}
key={data}>
{data}
</Select.Option>);
return(
<div>
<div style={{display:isAdminGroups ? "none" : "inline"}}>
<Tooltip placement="top" title={"Share Group"}>
<a><Icon type="share-alt" onClick={this.openShareModal}/></a>
</Tooltip>
<Divider type="vertical" />
<Tooltip placement="top" title={"Edit Group"}>
<a><Icon type="edit" onClick={this.openEditModal}/></a>
</Tooltip>
<Divider type="vertical" />
<Tooltip placement="bottom" title={"Delete Group"}>
<Popconfirm
placement="top"
title={"Are you sure?"}
onConfirm={this.onConfirmDeleteGroup}
okText="Ok"
cancelText="Cancel">
<a><Text type="danger"><Icon type="delete"/></Text></a>
</Popconfirm>
</Tooltip>
</div>
<div>
<Modal
title="Update Group"
width="40%"
visible={this.state.editModalVisible}
onOk={this.handleEditOk}
onCancel={this.handleEditCancel}
footer={[
<Button key="cancel" onClick={this.handleEditCancel}>
Cancel
</Button>,
<Button key="submit" type="primary" onClick={this.handleEditOk}>
Submit
</Button>,
]}
>
<div style={{alignItems:"center"}}>
<p>Enter new name and description for the group</p>
<Form
labelCol={{ span: 5 }}
wrapperCol={{ span: 18 }}
>
<Form.Item label="Name" style={{display:"block"}}>
{getFieldDecorator(
'name',
{
initialValue: this.props.data.name,
rules: [
{
required: true,
message: 'Please input group name',
},
],
})(<Input
onChange={this.onChangeName}/>)}
</Form.Item>
<Form.Item label="Description" style={{display:"block"}}>
{getFieldDecorator(
'description',
{
initialValue: this.props.data.description,
rules: [
{
required: true,
message: 'Please input group description',
},
],
})(<Input
onChange={this.onChangeDescription}/>)}
</Form.Item>
</Form>
</div>
</Modal>
</div>
<div>
<Modal
title="Share Group"
width="500px"
visible={this.state.shareModalVisible}
onOk={this.handleShareOk}
onCancel={this.handleShareCancel}
footer={[
<Button key="new-role" onClick={this.handleShareCancel}>
New Role
</Button>,
<Button key="new-role-selection" onClick={this.handleShareCancel}>
New Role from Selection
</Button>,
<Button key="submit" type="primary" onClick={this.handleShareOk}>
Share
</Button>,
]}
>
<p>Select user role(s)</p>
<Select
mode="multiple"
defaultValue={"admin"}
style={{ width: '100%' }}
onChange={this.handleRolesDropdownChange}>
{item}
</Select>,
</Modal>
</div>
</div>
)
}
}
export default withConfigContext(Form.create({name: 'group-actions'})(GroupActions));

@ -18,19 +18,38 @@
import React from "react";
import axios from "axios";
import {Tag, message, notification, Table, Typography, Tooltip, Icon, Divider} from "antd";
import {message, notification, Table, Typography} 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 "../../context/ConfigContext";
import GroupActions from "./GroupActions";
import AddGroup from "./AddGroup";
const {Text} = Typography;
let config = null;
let apiUrl;
const columns = [
const getTimeAgo = (time) => {
const timeAgo = new TimeAgo('en-US');
return timeAgo.format(time);
};
class GroupsTable extends React.Component {
constructor(props) {
super(props);
config = this.props.context;
TimeAgo.addLocale(en);
this.state = {
data: [],
pagination: {},
loading: false,
selectedRows: []
};
}
columns = [
{
title: 'Group Name',
dataIndex: 'name',
@ -49,26 +68,18 @@ const columns = [
key: 'description',
// render: enrolmentInfo => enrolmentInfo.ownership
// todo add filtering options
}
];
const getTimeAgo = (time) => {
const timeAgo = new TimeAgo('en-US');
return timeAgo.format(time);
};
class GroupsTable extends React.Component {
constructor(props) {
super(props);
config = this.props.context;
TimeAgo.addLocale(en);
this.state = {
data: [],
pagination: {},
loading: false,
selectedRows: []
};
}
},
{
title: 'Action',
dataIndex: 'id',
key: 'action',
render: (id, row) => (
<span>
<GroupActions data={row} fetchGroups={this.fetchGroups}/>
</span>
),
},
];
rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
@ -130,6 +141,8 @@ class GroupsTable extends React.Component {
});
};
handleTableChange = (pagination, filters, sorter) => {
const pager = {...this.state.pagination};
pager.current = pagination.current;
@ -148,10 +161,15 @@ class GroupsTable extends React.Component {
render() {
const {data, pagination, loading, selectedRows} = this.state;
return (
<div>
<div style={{background: '#f0f2f5'}}>
<AddGroup fetchGroups={this.fetchGroups} style={{marginBottom:"10px"}}/>
</div>
<div>
<Table
columns={columns}
columns={this.columns}
rowKey={record => (record.id)}
dataSource={data}
pagination={{
@ -164,9 +182,9 @@ class GroupsTable extends React.Component {
loading={loading}
onChange={this.handleTableChange}
rowSelection={this.rowSelection}
scroll={{x: 1000}}
/>
</div>
</div>
);
}
}

@ -18,7 +18,7 @@
import React from "react";
import axios from "axios";
import {Tag, message, notification, Table, Typography, Tooltip, Icon, Divider} from "antd";
import {message, notification, Table, Typography} from "antd";
import TimeAgo from 'javascript-time-ago'
// Load locale-specific relative date/time formatting rules.
@ -171,7 +171,6 @@ class PoliciesTable extends React.Component {
loading={loading}
onChange={this.handleTableChange}
rowSelection={this.rowSelection}
scroll={{x: 1000}}
/>
</div>
);

@ -18,9 +18,8 @@
import React from "react";
import axios from "axios";
import {Tag, message, notification, Table, Typography, Tooltip, Icon, Divider, Card, Col, Row, Select} from "antd";
import {Card, Col, Icon, message, notification, Row, Typography} 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 "../../context/ConfigContext";
@ -124,18 +123,27 @@ class RolesTable extends React.Component {
};
render() {
const {data, pagination, loading, selectedRows} = this.state;
const { Meta } = Card;
const itemCard = data.map((data) =>
<Col span={8} key={data}>
<Card hoverable title="Role" bordered={true}>
{data}
<Col span={5} key={data}>
<Card
size="default"
style={{ width: 200 }}
bordered={true}
actions={[
<Icon type="setting" key="setting" />,
<Icon type="edit" key="edit" />,]}
>
<Meta
avatar={<Icon type="book" key="roles"/>}
title={data}
/>
</Card>
</Col>
);
return (
<div style={{ background: '#ECECEC', padding: '30px' }}>
<div style={{ background: '#ECECEC', padding: '20px' }}>
<Row gutter={16}>
{itemCard}
</Row>

@ -18,9 +18,8 @@
import React from "react";
import axios from "axios";
import {Tag, message, notification, Table, Typography, Tooltip, Icon, Divider} from "antd";
import {Icon, message, notification, Table, Tag, Tooltip, Typography} 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 "../../context/ConfigContext";
@ -240,7 +239,6 @@ class UsersDevices extends React.Component {
loading={loading}
onChange={this.handleTableChange}
rowSelection={this.rowSelection}
scroll={{x: 1000}}
/>
</div>
);

@ -18,13 +18,11 @@
import React from "react";
import axios from "axios";
import {message, notification, Table, Typography, Panel, Collapse, Button, List, Modal, Icon} from "antd";
import {Button, Collapse, Icon, List, message, Modal, notification, Table, Tabs, Typography} 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 "../../context/ConfigContext";
import DeviceTable from "../Devices/DevicesTable";
import UsersDevices from "./UsersDevices";
const {Text} = Typography;
@ -174,9 +172,9 @@ class UsersTable extends React.Component {
};
render() {
const {data, pagination, loading, selectedRows} = this.state;
const { Panel } = Collapse;
const { TabPane } = Tabs;
const columns = [
{
title: 'User Name',
@ -231,30 +229,44 @@ class UsersTable extends React.Component {
loading={loading}
onChange={this.handleTableChange}
rowSelection={this.rowSelection}
scroll={{x: 1000}}
/>
</div>
<div>
<Modal
width="900px"
title="Info"
visible={this.state.rolesModalVisible}
onOk={this.handleOk}
onCancel={this.handleCancel}
>
<Collapse>
<Panel header="User Roles" key="1">
<Tabs size="small" defaultActiveKey="1">
<TabPane
tab={
<span>
<Icon type="book"/>
Roles
</span>
}
key="1"
>
<List
size="small"
bordered
dataSource={this.state.rolesData}
renderItem={item => <List.Item>{item}</List.Item>}
/>
</Panel>
<Panel header="Enrolled Devices" key="2">
</TabPane>
<TabPane
tab={
<span>
<Icon type="appstore"/>
Enrolled Devices
</span>
}
key="2"
>
<UsersDevices user={this.state.user}/>
</Panel>
</Collapse>
</TabPane>
</Tabs>
</Modal>
</div>
</div>

@ -74,12 +74,25 @@ class Dashboard extends React.Component {
<span>Devices</span>
</Link>
</Menu.Item>
<Menu.Item key="geo">
<Link to="/entgra/geo">
<SubMenu
key="geo"
title={
<span>
<Icon type="environment"/>
<span>Geo</span>
</span>}
>
<Menu.Item key="singleDevice">
<Link to="/entgra/geo">
<span>Single Device View</span>
</Link>
</Menu.Item>
<Menu.Item key="deviceGroup">
<Link to="#">
<span>Device Group View</span>
</Link>
</Menu.Item>
</SubMenu>
<Menu.Item key="reports">
<Link to="/entgra/reports">
<Icon type="bar-chart"/>

@ -25,6 +25,7 @@ import {
} from "antd";
import {Link} from "react-router-dom";
import GroupsTable from "../../../components/Groups/GroupsTable";
import AddGroup from "../../../components/Groups/AddGroup";
const {Paragraph} = Typography;

@ -157,7 +157,7 @@ class NormalLoginForm extends React.Component {
)}
<br/>
<a className="login-form-forgot" href="">Forgot password</a>
<Button block type="primary" htmlType="submit" className="login-form-button">
<Button loading={this.state.loading} block type="primary" htmlType="submit" className="login-form-button">
Log in
</Button>
</Form.Item>

@ -1713,16 +1713,16 @@ public interface DeviceManagementService {
@Valid OperationRequest operationRequest);
@GET
@Path("/status/count/{tenantDomain}/{type}/{status}")
@Path("/type/{type}/status/{status}/count")
@ApiOperation(
produces = MediaType.APPLICATION_JSON,
httpMethod = "GET",
value = "Get Device Count with status",
notes = "Get specified device count with status.",
notes = "Get specified device count with type and status.",
tags = "Device Management",
extensions = {
@Extension(properties = {
@ExtensionProperty(name = Constants.SCOPE, value = "perm:devices:details")
@ExtensionProperty(name = Constants.SCOPE, value = "perm:devices:view")
})
}
)
@ -1730,7 +1730,7 @@ public interface DeviceManagementService {
value = {
@ApiResponse(
code = 200,
message = "OK. \n Successfully fetched the details of the device.",
message = "OK. \n Successfully fetched the count of matching devices.",
response = int.class,
responseHeaders = {
@ResponseHeader(
@ -1764,13 +1764,6 @@ public interface DeviceManagementService {
response = ErrorResponse.class)
})
Response getDeviceCountByStatus(
@ApiParam(
name = "tenantDomain",
value = "The tenant doamin.",
required = true)
@PathParam("tenantDomain")
@Size(max = 45)
String tenantDomain,
@ApiParam(
name = "type",
value = "The device type name, such as ios, android, windows or fire-alarm.",
@ -1788,17 +1781,16 @@ public interface DeviceManagementService {
@GET
@Path("/status/ids/{tenantDomain}/{type}/{status}")
@Path("/type/{type}/status/{status}/ids")
@ApiOperation(
produces = MediaType.APPLICATION_JSON,
httpMethod = "GET",
value = "Getting Details of a Device",
notes = "Get the details of a device by specifying the device type and device identifier and optionally " +
"the owner.",
value = "Getting ids of devices with specified type and status",
notes = "Get the ids of a device by specifying the device type and status.",
tags = "Device Management",
extensions = {
@Extension(properties = {
@ExtensionProperty(name = Constants.SCOPE, value = "perm:devices:details")
@ExtensionProperty(name = Constants.SCOPE, value = "perm:devices:view")
})
}
)
@ -1840,13 +1832,6 @@ public interface DeviceManagementService {
response = ErrorResponse.class)
})
Response getDeviceIdentifiersByStatus(
@ApiParam(
name = "tenantDomain",
value = "The tenant domain.",
required = true)
@PathParam("tenantDomain")
@Size(max = 45)
String tenantDomain,
@ApiParam(
name = "type",
value = "The device type name, such as ios, android, windows or fire-alarm.",
@ -1863,7 +1848,7 @@ public interface DeviceManagementService {
String status);
@PUT
@Path("/status/update/{tenantDomain}/{type}/{status}")
@Path("/type/{type}/status/{status}")
@ApiOperation(
produces = MediaType.APPLICATION_JSON,
consumes = MediaType.APPLICATION_JSON,
@ -1915,8 +1900,6 @@ public interface DeviceManagementService {
response = ErrorResponse.class)
})
Response bulkUpdateDeviceStatus(
@ApiParam(name = "tenantDomain", value = "The tenant domain.", required = true)
@PathParam("tenantDomain") String tenantDomain,
@ApiParam(name = "type", value = "The device type, such as ios, android or windows.", required = true)
@PathParam("type") String type,
@ApiParam(name = "status", value = "The device type, such as ios, android or windows.", required = true)

@ -458,8 +458,10 @@ public interface GroupManagementService {
@Path("/id/{groupId}")
@DELETE
@Consumes(MediaType.WILDCARD)
@ApiOperation(
produces = MediaType.APPLICATION_JSON,
consumes = MediaType.WILDCARD,
httpMethod = HTTPConstants.HEADER_DELETE,
value = "Deleting a Group",
notes = "If you wish to remove an existing group, that can be done by updating the group using this API.",

@ -45,9 +45,11 @@ import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.ResponseHeader;
import org.apache.axis2.transport.http.HTTPConstants;
import org.wso2.carbon.apimgt.annotations.api.Scope;
import org.wso2.carbon.apimgt.annotations.api.Scopes;
import org.wso2.carbon.device.mgt.common.Device;
import org.wso2.carbon.device.mgt.jaxrs.beans.DeviceGroupList;
import org.wso2.carbon.device.mgt.jaxrs.beans.DeviceList;
import org.wso2.carbon.device.mgt.jaxrs.beans.ErrorResponse;
import org.wso2.carbon.device.mgt.jaxrs.util.Constants;
@ -198,6 +200,59 @@ public interface DeviceManagementAdminService {
defaultValue = "5")
@QueryParam("limit") int limit);
@Path("/count")
@GET
@ApiOperation(
produces = MediaType.APPLICATION_JSON,
httpMethod = HTTPConstants.HEADER_GET,
value = "Get the count of devices.",
notes = "Returns count of all devices enrolled with the system.",
tags = "Device Management Administrative Service",
extensions = {
@Extension(properties = {
@ExtensionProperty(name = Constants.SCOPE, value = "perm:admin:devices:view")
})
}
)
@ApiResponses(value = {
@ApiResponse(code = 200, message = "OK. \n Successfully fetched the device count.",
response = Integer.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 has been modified the last time.\n" +
"Used by caches, or in conditional requests."),
}),
@ApiResponse(
code = 304,
message = "Not Modified. \n Empty body because the client has already the latest version of " +
"the requested resource."),
@ApiResponse(
code = 404,
message = "No groups found.",
response = ErrorResponse.class),
@ApiResponse(
code = 406,
message = "Not Acceptable.\n The requested media type is not supported."),
@ApiResponse(
code = 500,
message = "Internal Server Error. \n Server error occurred while fetching the device count.",
response = ErrorResponse.class)
})
Response getDeviceCount(@ApiParam(
name = "status",
value = "status of group of which count should be retrieved")
@QueryParam("status")
String status);
@PUT
@Path("/device-owner")
@ApiOperation(

@ -33,10 +33,12 @@ import io.swagger.annotations.ResponseHeader;
import org.apache.axis2.transport.http.HTTPConstants;
import org.wso2.carbon.apimgt.annotations.api.Scope;
import org.wso2.carbon.apimgt.annotations.api.Scopes;
import org.wso2.carbon.device.mgt.common.group.mgt.DeviceGroup;
import org.wso2.carbon.device.mgt.jaxrs.beans.DeviceGroupList;
import org.wso2.carbon.device.mgt.jaxrs.beans.ErrorResponse;
import org.wso2.carbon.device.mgt.jaxrs.util.Constants;
import javax.validation.Valid;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@ -75,6 +77,12 @@ import javax.ws.rs.core.Response;
description = "",
key = "perm:admin-groups:count",
permissions = {"/device-mgt/admin/groups/view"}
),
@Scope(
name = "Add groups",
description = "",
key = "perm:admin-groups:add",
permissions = {"/device-mgt/admin/groups/add"}
)
}
)
@ -166,7 +174,7 @@ public interface GroupManagementAdminService {
)
@ApiResponses(value = {
@ApiResponse(code = 200, message = "OK. \n Successfully fetched the device group count.",
response = DeviceGroupList.class,
response = Integer.class,
responseHeaders = {
@ResponseHeader(
name = "Content-Type",
@ -202,4 +210,71 @@ public interface GroupManagementAdminService {
@QueryParam("status")
String status);
@POST
@ApiOperation(
consumes = MediaType.APPLICATION_JSON,
httpMethod = HTTPConstants.HEADER_POST,
value = "Adding a New Device Group",
notes = "Add device group with the current user as the owner.",
tags = "Device Group Management",
extensions = {
@Extension(properties = {
@ExtensionProperty(name = Constants.SCOPE, value = "perm:admin-groups:add")
})
}
)
@ApiResponses(
value = {
@ApiResponse(
code = 201,
message = "Created. \n Device group has successfully been created",
responseHeaders = {
@ResponseHeader(
name = "Content-Location",
description = "The URL of the added group."),
@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 has been modified the last time" +
".\n" + "Used by caches, or in conditional requests.")
}
),
@ApiResponse(
code = 303,
message = "See Other. \n Source can be retrieved from the URL specified at the Location " +
"header.",
responseHeaders = {
@ResponseHeader(
name = "Content-Location",
description = "The Source URL of the document.")}),
@ApiResponse(
code = 400,
message = "Bad Request. \n Invalid request or validation error.",
response = ErrorResponse.class),
@ApiResponse(
code = 401,
message = "Unauthorized. \n Current logged in user is not authorized to add device groups.",
response = ErrorResponse.class),
@ApiResponse(
code = 415,
message = "Unsupported media type. \n The entity of the request was in a not supported " +
"format."),
@ApiResponse(
code = 500,
message = "Internal Server Error. \n " +
"Server error occurred while adding a new device group.",
response = ErrorResponse.class)
})
Response createGroup(@ApiParam(
name = "group",
value = "Define the group object with data.",
required = true)
@Valid DeviceGroup group);
}

@ -973,66 +973,48 @@ public class DeviceManagementServiceImpl implements DeviceManagementService {
@GET
@Override
@Path("/status/count/{tenantDomain}/{type}/{status}")
public Response getDeviceCountByStatus(@PathParam("tenantDomain") String tenantDomain, @PathParam("type") String type, @PathParam("status") String status) {
@Path("/type/{type}/status/{status}/count")
public Response getDeviceCountByStatus(@PathParam("type") String type, @PathParam("status") String status) {
int deviceCount;
try {
int tenantId = DeviceMgtAPIUtils.getRealmService().getTenantManager().getTenantId(tenantDomain);
deviceCount = DeviceMgtAPIUtils.getDeviceManagementService().getDeviceCountOfTypeByStatus(tenantId, type, status);
deviceCount = DeviceMgtAPIUtils.getDeviceManagementService().getDeviceCountOfTypeByStatus(type, status);
return Response.status(Response.Status.OK).entity(deviceCount).build();
} catch (DeviceManagementException e) {
String errorMessage = "Error while retrieving device count.";
log.error(errorMessage, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(
new ErrorResponse.ErrorResponseBuilder().setMessage(errorMessage).build()).build();
} catch (UserStoreException e) {
String errorMessage = "Error resolving tenant Domain";
log.error(errorMessage, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(
new ErrorResponse.ErrorResponseBuilder().setMessage(errorMessage).build()).build();
}
}
@GET
@Override
@Path("/status/ids/{tenantDomain}/{type}/{status}")
public Response getDeviceIdentifiersByStatus(@PathParam("tenantDomain") String tenantDomain, @PathParam("type") String type, @PathParam("status") String status) {
@Path("/type/{type}/status/{status}/ids")
public Response getDeviceIdentifiersByStatus(@PathParam("type") String type, @PathParam("status") String status) {
List<String> deviceIds;
try {
int tenantId = DeviceMgtAPIUtils.getRealmService().getTenantManager().getTenantId(tenantDomain);
deviceIds = DeviceMgtAPIUtils.getDeviceManagementService().getDeviceIdentifiersByStatus(tenantId, type, status);
deviceIds = DeviceMgtAPIUtils.getDeviceManagementService().getDeviceIdentifiersByStatus(type, status);
return Response.status(Response.Status.OK).entity(deviceIds.toArray(new String[0])).build();
} catch (DeviceManagementException e) {
String errorMessage = "Error while obtaining list of devices";
log.error(errorMessage, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(
new ErrorResponse.ErrorResponseBuilder().setMessage(errorMessage).build()).build();
} catch (UserStoreException e) {
String errorMessage = "Error resolving tenant Domain";
log.error(errorMessage, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(
new ErrorResponse.ErrorResponseBuilder().setMessage(errorMessage).build()).build();
}
}
@PUT
@Override
@Path("/status/update/{tenantDomain}/{type}/{status}")
public Response bulkUpdateDeviceStatus(@PathParam("tenantDomain") String tenantDomain, @PathParam("type") String type,
@PathParam("status") String status, @Valid List<String> deviceList) {
@Path("/type/{type}/status/{status}")
public Response bulkUpdateDeviceStatus(@PathParam("type") String type, @PathParam("status") String status,
@Valid List<String> deviceList) {
try {
int tenantId = DeviceMgtAPIUtils.getRealmService().getTenantManager().getTenantId(tenantDomain);
DeviceMgtAPIUtils.getDeviceManagementService().bulkUpdateDeviceStatus(tenantId, type, deviceList, status);
DeviceMgtAPIUtils.getDeviceManagementService().bulkUpdateDeviceStatus(type, deviceList, status);
} catch (DeviceManagementException e) {
String errorMessage = "Error while updating device status in bulk.";
log.error(errorMessage, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(
new ErrorResponse.ErrorResponseBuilder().setMessage(errorMessage).build()).build();
} catch (UserStoreException e) {
String errorMessage = "Error resolving tenant Domain";
log.error(errorMessage, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(
new ErrorResponse.ErrorResponseBuilder().setMessage(errorMessage).build()).build();
}
return Response.status(Response.Status.OK).build();
}

@ -40,10 +40,10 @@ import org.wso2.carbon.base.MultitenantConstants;
import org.wso2.carbon.context.CarbonContext;
import org.wso2.carbon.context.PrivilegedCarbonContext;
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.PaginationRequest;
import org.wso2.carbon.device.mgt.common.exceptions.DeviceManagementException;
import org.wso2.carbon.device.mgt.common.exceptions.InvalidDeviceException;
import org.wso2.carbon.device.mgt.common.PaginationRequest;
import org.wso2.carbon.device.mgt.common.exceptions.UserNotFoundException;
import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService;
import org.wso2.carbon.device.mgt.jaxrs.beans.DeviceList;
@ -53,7 +53,13 @@ import org.wso2.carbon.device.mgt.jaxrs.service.impl.util.RequestValidationUtil;
import org.wso2.carbon.device.mgt.jaxrs.util.DeviceMgtAPIUtils;
import javax.validation.constraints.Size;
import javax.ws.rs.*;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;
@ -107,6 +113,26 @@ public class DeviceManagementAdminServiceImpl implements DeviceManagementAdminSe
}
}
@Override
@Path("/count")
@GET
public Response getDeviceCount(@QueryParam("status") String status) {
int deviceCount;
try {
if (status == null) {
deviceCount = DeviceMgtAPIUtils.getDeviceManagementService().getDeviceCount();
} else {
deviceCount = DeviceMgtAPIUtils.getDeviceManagementService().getDeviceCount(EnrolmentInfo.Status.valueOf(status));
}
} catch (DeviceManagementException e) {
String msg = "Error occurred while fetching device count.";
log.error(msg, e);
return Response.serverError().entity(
new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build();
}
return Response.status(Response.Status.OK).entity(deviceCount).build();
}
@PUT
@Override
@Path("/device-owner")

@ -20,9 +20,11 @@ package org.wso2.carbon.device.mgt.jaxrs.service.impl.admin;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.solr.common.StringUtils;
import org.wso2.carbon.device.mgt.common.GroupPaginationRequest;
import org.wso2.carbon.device.mgt.common.PaginationResult;
import org.wso2.carbon.device.mgt.common.group.mgt.DeviceGroup;
import org.wso2.carbon.device.mgt.common.group.mgt.DeviceGroupConstants;
import org.wso2.carbon.device.mgt.common.group.mgt.GroupAlreadyExistException;
import org.wso2.carbon.device.mgt.common.group.mgt.GroupManagementException;
import org.wso2.carbon.device.mgt.jaxrs.beans.DeviceGroupList;
import org.wso2.carbon.device.mgt.jaxrs.service.api.admin.GroupManagementAdminService;
@ -36,6 +38,10 @@ public class GroupManagementAdminServiceImpl implements GroupManagementAdminServ
private static final Log log = LogFactory.getLog(GroupManagementAdminServiceImpl.class);
private static final String DEFAULT_ADMIN_ROLE = "admin";
private static final String[] DEFAULT_ADMIN_PERMISSIONS = {"/permission/device-mgt/admin/groups",
"/permission/device-mgt/user/groups"};
@Override
public Response getGroups(String name, String owner, int offset, int limit, String status) {
try {
@ -84,4 +90,24 @@ public class GroupManagementAdminServiceImpl implements GroupManagementAdminServ
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build();
}
}
@Override
public Response createGroup(DeviceGroup group) {
if (group == null) {
return Response.status(Response.Status.BAD_REQUEST).build();
}
group.setStatus(DeviceGroupConstants.GroupStatus.ACTIVE);
try {
DeviceMgtAPIUtils.getGroupManagementProviderService().createGroup(group, DEFAULT_ADMIN_ROLE, DEFAULT_ADMIN_PERMISSIONS);
return Response.status(Response.Status.CREATED).build();
} catch (GroupManagementException e) {
String msg = "Error occurred while adding new group.";
log.error(msg, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build();
} catch (GroupAlreadyExistException e) {
String msg = "Group already exists with name " + group.getName() + ".";
log.warn(msg);
return Response.status(Response.Status.CONFLICT).entity(msg).build();
}
}
}

@ -56,45 +56,29 @@ public class DeviceLocation implements Serializable {
@ApiModelProperty(name = "updatedTime", value = "Update time of the device.", required = true)
private Date updatedTime;
@ApiModelProperty(name = "altitude", value = "Device altitude.", required = true)
private Double altitude;
private double altitude;
@ApiModelProperty(name = "speed", value = "Device speed.", required = true)
private Float speed;
private float speed;
@ApiModelProperty(name = "bearing", value = "Device bearing.", required = true)
private Float bearing;
private float bearing;
@ApiModelProperty(name = "distance", value = "Device distance.", required = true)
private Double distance;
private double distance;
public Double getDistance() {
return distance;
}
public double getAltitude() { return altitude; }
public void setDistance(Double distance) {
this.distance = distance;
}
public void setAltitude(double altitude) { this.altitude = altitude; }
public Double getAltitude() {
return altitude;
}
public float getSpeed() { return speed; }
public Float getSpeed() {
return speed;
}
public void setSpeed(float speed) { this.speed = speed; }
public void setSpeed(Float speed) {
this.speed = speed;
}
public float getBearing() { return bearing; }
public Float getBearing() {
return bearing;
}
public void setBearing(float bearing) { this.bearing = bearing; }
public void setBearing(Float bearing) {
this.bearing = bearing;
}
public double getDistance() { return distance; }
public void setAltitude(Double altitude) {
this.altitude = altitude;
}
public void setDistance(double distance) { this.distance = distance; }
public int getDeviceId() {
return deviceId;

@ -752,11 +752,11 @@ public interface DeviceManagementProviderService {
List<GeoCluster> findGeoClusters(String deviceType, GeoCoordinate southWest, GeoCoordinate northEast,
int geohashLength) throws DeviceManagementException;
int getDeviceCountOfTypeByStatus(int tenantId, String deviceType, String deviceStatus) throws DeviceManagementException;
int getDeviceCountOfTypeByStatus(String deviceType, String deviceStatus) throws DeviceManagementException;
List<String> getDeviceIdentifiersByStatus(int tenantId, String deviceType, String deviceStatus) throws DeviceManagementException;
List<String> getDeviceIdentifiersByStatus(String deviceType, String deviceStatus) throws DeviceManagementException;
boolean bulkUpdateDeviceStatus(int tenantId, String deviceType, List<String> deviceList, String status) throws DeviceManagementException;
boolean bulkUpdateDeviceStatus(String deviceType, List<String> deviceList, String status) throws DeviceManagementException;
boolean updateEnrollment(String owner, List<String> deviceIdentifiers)
throws DeviceManagementException, UserNotFoundException, InvalidDeviceException;

@ -3125,10 +3125,10 @@ public class DeviceManagementProviderServiceImpl implements DeviceManagementProv
}
@Override
public int getDeviceCountOfTypeByStatus(int tenantId, String deviceType, String deviceStatus) throws DeviceManagementException {
public int getDeviceCountOfTypeByStatus(String deviceType, String deviceStatus) throws DeviceManagementException {
try {
DeviceManagementDAOFactory.openConnection();
return deviceDAO.getDeviceCount(deviceType, deviceStatus, tenantId);
return deviceDAO.getDeviceCount(deviceType, deviceStatus, getTenantId());
} catch (DeviceManagementDAOException e) {
String msg = "Error occurred in while retrieving device count by status for deviceType :" +deviceType + " status : " + deviceStatus;
log.error(msg, e);
@ -3143,11 +3143,11 @@ public class DeviceManagementProviderServiceImpl implements DeviceManagementProv
}
@Override
public List<String> getDeviceIdentifiersByStatus(int tenantId, String deviceType, String deviceStatus) throws DeviceManagementException {
public List<String> getDeviceIdentifiersByStatus(String deviceType, String deviceStatus) throws DeviceManagementException {
List<String> deviceIds;
try {
DeviceManagementDAOFactory.openConnection();
deviceIds = deviceDAO.getDeviceIdentifiers(deviceType, deviceStatus, tenantId);
deviceIds = deviceDAO.getDeviceIdentifiers(deviceType, deviceStatus, getTenantId());
} catch (DeviceManagementDAOException e) {
String msg = "Error occurred in while retrieving devices by status for deviceType :" +deviceType + " status : " + deviceStatus;
log.error(msg, e);
@ -3163,20 +3163,19 @@ public class DeviceManagementProviderServiceImpl implements DeviceManagementProv
}
@Override
public boolean bulkUpdateDeviceStatus(int tenantId, String deviceType,
List<String> deviceList, String status)
public boolean bulkUpdateDeviceStatus(String deviceType, List<String> deviceList, String status)
throws DeviceManagementException {
boolean success;
try {
DeviceManagementDAOFactory.openConnection();
success = deviceDAO.setEnrolmentStatusInBulk(deviceType, status, tenantId, deviceList);
DeviceManagementDAOFactory.beginTransaction();
success = deviceDAO.setEnrolmentStatusInBulk(deviceType, status, getTenantId(), deviceList);
DeviceManagementDAOFactory.commitTransaction();
} catch (DeviceManagementDAOException e) {
String msg = "Error occurred in while updating status of devices :" + deviceType + " status : " + deviceList
.toString();
DeviceManagementDAOFactory.rollbackTransaction();
String msg = "Error occurred in while updating status of devices :" + deviceType + " status : " + status;
log.error(msg, e);
throw new DeviceManagementException(msg, e);
} catch (SQLException e) {
} catch (TransactionManagementException e) {
String msg = "Error occurred while opening a connection to the data source";
log.error(msg, e);
throw new DeviceManagementException(msg, e);

@ -118,7 +118,7 @@
{{/if}}
{{#if iosPluginFlag}}
<li>
<a href="{{@app.context}}/dep/devices"><i class="fw fw-ios"></i>
<a href="{{@app.context}}/dep/devices"><i class="fw fw-apple"></i>
DEP Configurations
</a>
</li>

@ -155,8 +155,8 @@ public class PolicyManagerUtil {
PolicyAdministratorPoint pap = new PolicyAdministratorPointImpl();
try {
Policy correctivePolicy = pap.getPolicy(correctiveAction.getPolicyId());
if (correctivePolicy == null || PolicyManagementConstants.CORRECTIVE_POLICY_TYPE
.equalsIgnoreCase(correctivePolicy.getPolicyType())) {
if (correctivePolicy == null || !PolicyManagementConstants.CORRECTIVE_POLICY_TYPE
.equalsIgnoreCase(correctivePolicy.getPolicyType() )) {
String msg = "No corrective policy was found for the policy " + policy.getPolicyName() +
" and policy ID " + policy.getId();
log.error(msg);

@ -4,7 +4,7 @@
CREATE TABLE IF NOT EXISTS AP_APP(
ID INTEGER NOT NULL AUTO_INCREMENT,
NAME VARCHAR(45) NOT NULL,
DESCRIPTION CLOB NULL,
DESCRIPTION VARCHAR(200) NOT NULL,
TYPE VARCHAR(200) NOT NULL,
TENANT_ID INTEGER NOT NULL,
STATUS VARCHAR(45) NOT NULL DEFAULT 'ACTIVE',
@ -20,7 +20,7 @@ CREATE TABLE IF NOT EXISTS AP_APP(
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS AP_APP_RELEASE(
ID INTEGER NOT NULL AUTO_INCREMENT,
DESCRIPTION CLOB NOT NULL,
DESCRIPTION VARCHAR(200) NOT NULL,
VERSION VARCHAR(70) NOT NULL,
TENANT_ID INTEGER NOT NULL,
UUID VARCHAR(200) NOT NULL,
@ -35,7 +35,7 @@ CREATE TABLE IF NOT EXISTS AP_APP_RELEASE(
SC_3_LOCATION VARCHAR(100) NULL DEFAULT NULL,
APP_HASH_VALUE VARCHAR(1000) NOT NULL,
SHARED_WITH_ALL_TENANTS BOOLEAN NOT NULL DEFAULT FALSE,
APP_META_INFO CLOB NULL DEFAULT NULL,
APP_META_INFO VARCHAR(150) NULL DEFAULT NULL,
SUPPORTED_OS_VERSIONS VARCHAR(45) NOT NULL,
RATING DOUBLE NULL DEFAULT NULL,
CURRENT_STATE VARCHAR(45) NOT NULL,
@ -57,8 +57,8 @@ CREATE TABLE IF NOT EXISTS AP_APP_REVIEW(
COMMENT TEXT NOT NULL,
ROOT_PARENT_ID INTEGER NOT NULL,
IMMEDIATE_PARENT_ID INTEGER NOT NULL,
CREATED_AT TIMESTAMP NOT NULL,
MODIFIED_AT TIMESTAMP NOT NULL,
CREATED_AT TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
MODIFIED_AT TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
RATING INTEGER NULL,
USERNAME VARCHAR(45) NOT NULL,
ACTIVE_REVIEW BOOLEAN NOT NULL DEFAULT TRUE,
@ -267,14 +267,11 @@ CREATE TABLE IF NOT EXISTS AP_SCHEDULED_SUBSCRIPTION(
ID INTEGER NOT NULL AUTO_INCREMENT,
TASK_NAME VARCHAR(100) NOT NULL,
APPLICATION_UUID VARCHAR(36) NOT NULL,
SUBSCRIBER_LIST LONGVARCHAR NOT NULL,
SUBSCRIBER_LIST TEXT NOT NULL,
STATUS VARCHAR(15) NOT NULL,
SCHEDULED_AT TIMESTAMP NOT NULL,
SCHEDULED_AT TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
SCHEDULED_BY VARCHAR(100) NOT NULL,
SCHEDULED_TIMESTAMP TIMESTAMP NOT NULL,
SCHEDULED_TIMESTAMP TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
DELETED BOOLEAN,
PRIMARY KEY (ID),
CONSTRAINT fk_AP_SCHEDULED_SUBSCRIPTION_AP_APP_RELEASE
FOREIGN KEY (APPLICATION_UUID)
REFERENCES AP_APP_RELEASE (UUID) ON DELETE NO ACTION ON UPDATE NO ACTION
PRIMARY KEY (ID)
);
Loading…
Cancel
Save