Merge branch 'application-mgt-new' into 'application-mgt-new'

Group create/edit/delete/share functions

See merge request entgra/carbon-device-mgt!352
feature/appm-store/pbac
Dharmakeerthi Lasantha 5 years ago
commit 1907d57b4e

@ -18,9 +18,8 @@
import React from "react";
import axios from "axios";
import {message, notification, Typography, Icon, Card, Col, Row} 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";

@ -18,9 +18,8 @@
import React from "react";
import axios from "axios";
import {Tag, message, notification, Table, Typography, Tooltip, Icon, 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";

@ -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";

@ -0,0 +1,176 @@
/*
* 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="500px"
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: 12 }}
style={{alignItems:"center"}}
>
<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,377 @@
/*
* 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="500px"
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: 12 }}
style={{alignItems:"center"}}
>
<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="cancel" onClick={this.handleShareCancel}>
Cancel
</Button>,
<Button key="submit" type="primary" onClick={this.handleShareOk}>
Submit
</Button>,
]}
>
<Select
placeholder={"Select user role(s)"}
mode="multiple"
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 { message, notification, Table, Typography} 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', marginBottom:"5px"}}>
<AddGroup fetchGroups={this.fetchGroups}/>
</div>
<div>
<Table
columns={columns}
columns={this.columns}
rowKey={record => (record.id)}
dataSource={data}
pagination={{
@ -166,6 +184,7 @@ class GroupsTable extends React.Component {
rowSelection={this.rowSelection}
/>
</div>
</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,7 +123,6 @@ class RolesTable extends React.Component {
};
render() {
const {data, pagination, loading, selectedRows} = this.state;
const { Meta } = Card;
const itemCard = data.map((data) =>

@ -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";

@ -18,13 +18,11 @@
import React from "react";
import axios from "axios";
import {message, notification, Table, Typography, Panel, Collapse, Button, List, Modal, Icon, Tabs} 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;

@ -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;

@ -55,7 +55,7 @@ class Policies extends React.Component {
</div>
</PageHeader>
<div style={{background: '#f0f2f5', padding: 24, minHeight: 720}}>
<p>sghdfugjhskf</p>
</div>
</div>
);

@ -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.",

Loading…
Cancel
Save