From ad8eb23aff1879c3c20a589305c73eaed8e4b17c Mon Sep 17 00:00:00 2001 From: Kaveesha Mihirangi Date: Fri, 31 Jan 2020 11:36:00 +0000 Subject: [PATCH] Generate Policy configuration UI from JSON --- .../react-app/package.json | 1 + .../src/components/Devices/AddDevice.js | 18 + .../src/components/Devices/EnrollAgent.js | 18 + .../src/components/Policies/AddPolicy.js | 140 +- .../components/Policies/ConfigureProfile.js | 649 ++++- .../src/components/Policies/SelectPlatform.js | 57 +- .../src/components/Policies/configuration.js | 2174 ----------------- .../react-app/src/components/Roles/AddRole.js | 18 + .../src/components/Roles/RoleAction.js | 18 + .../react-app/src/components/Users/AddUser.js | 18 + .../src/components/Users/UserActions.js | 18 + .../src/pages/Dashboard/Policies/policies.css | 49 +- .../api/DeviceTypeManagementService.java | 67 + .../impl/DeviceTypeManagementServiceImpl.java | 42 + .../device/mgt/common/DeviceManager.java | 8 + .../policy/mgt/PolicyMonitoringManager.java | 3 +- .../type/mgt/DeviceTypeMetaDefinition.java | 11 +- .../mgt/common/ui/policy/mgt/Policy.java | 110 + .../mgt/PolicyConfigurationManager.java | 33 + .../DeviceManagementProviderService.java | 24 +- .../DeviceManagementProviderServiceImpl.java | 38 +- .../device/mgt/core/TestDeviceManager.java | 16 +- .../type/template/DeviceTypeManager.java | 27 +- .../HTTPDeviceTypeManagerService.java | 32 +- .../device/type/template/config/Buttons.java | 44 + .../device/type/template/config/Column.java | 68 + .../type/template/config/DataPanel.java | 102 + .../config/DeviceTypeConfiguration.java | 22 + .../device/type/template/config/Option.java | 46 + .../type/template/config/OptionalData.java | 209 ++ .../type/template/config/PanelItem.java | 82 + .../device/type/template/config/Policy.java | 88 + .../config/PolicyUIConfigurations.java | 76 + .../device/type/template/config/SubPanel.java | 49 + .../type/template/config/ValidationRules.java | 38 + .../mgt/ConfigurationBasedPolicyManager.java | 51 + ...rviceAndDeviceTypeGeneratorServceTest.java | 1 + .../mgt/core/mock/TypeXDeviceManager.java | 12 +- 38 files changed, 2089 insertions(+), 2388 deletions(-) delete mode 100644 components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Policies/configuration.js create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/ui/policy/mgt/Policy.java create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/ui/policy/mgt/PolicyConfigurationManager.java create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.extensions/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/template/config/Buttons.java create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.extensions/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/template/config/Column.java create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.extensions/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/template/config/DataPanel.java create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.extensions/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/template/config/Option.java create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.extensions/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/template/config/OptionalData.java create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.extensions/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/template/config/PanelItem.java create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.extensions/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/template/config/Policy.java create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.extensions/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/template/config/PolicyUIConfigurations.java create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.extensions/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/template/config/SubPanel.java create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.extensions/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/template/config/ValidationRules.java create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.extensions/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/template/policy/mgt/ConfigurationBasedPolicyManager.java diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/package.json b/components/device-mgt/io.entgra.device.mgt.ui/react-app/package.json index 3aacd5da9b1..6a7a9d69ef9 100644 --- a/components/device-mgt/io.entgra.device.mgt.ui/react-app/package.json +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/package.json @@ -21,6 +21,7 @@ "rc-viewer": "0.0.9", "react-bootstrap": "^1.0.0-beta.12", "react-highlight-words": "^0.16.0", + "react-icons": "^3.8.0", "react-image-viewer-zoom": "^1.0.36", "react-infinite-scroller": "^1.2.4", "react-leaflet": "^2.4.0", diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Devices/AddDevice.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Devices/AddDevice.js index 350e2db4788..a8a407d0bca 100644 --- a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Devices/AddDevice.js +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Devices/AddDevice.js @@ -1,3 +1,21 @@ +/* + * 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'; diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Devices/EnrollAgent.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Devices/EnrollAgent.js index 356be766121..5707d9ccb63 100644 --- a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Devices/EnrollAgent.js +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Devices/EnrollAgent.js @@ -1,3 +1,21 @@ +/* + * 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, Divider, message, notification } from 'antd'; import TimeAgo from 'javascript-time-ago/modules/JavascriptTimeAgo'; diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Policies/AddPolicy.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Policies/AddPolicy.js index ecbd403ffa3..f1b0a8e83b1 100644 --- a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Policies/AddPolicy.js +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Policies/AddPolicy.js @@ -1,8 +1,36 @@ +/* + * Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved. + * + * Entgra (pvt) Ltd. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + import React from 'react'; -import { Button, Form, Row, Col, Card, Steps } from 'antd'; +import { + Button, + Form, + Row, + Col, + Card, + Steps, + message, + notification, +} from 'antd'; import { withConfigContext } from '../../context/ConfigContext'; import SelectPlatform from './SelectPlatform'; import ConfigureProfile from './ConfigureProfile'; +import axios from 'axios'; const { Step } = Steps; class AddPolicy extends React.Component { @@ -10,34 +38,67 @@ class AddPolicy extends React.Component { super(props); this.config = this.props.context; this.state = { - isAddDeviceModalVisible: false, - current: 0, + currentStepIndex: 0, + isLoading: false, + policyUIConfigurationsList: [], }; } - onClickType = () => { - this.setState({ - current: 1, - }); + getPolicyConfigJson = type => { + this.setState({ isLoading: true }); + + let apiUrl = + window.location.origin + + this.config.serverConfig.invoker.uri + + this.config.serverConfig.invoker.deviceMgt + + '/device-types/' + + type + + '/ui-policy-configurations'; + // send request to the invokers + axios + .get(apiUrl) + .then(res => { + if (res.status === 200) { + this.setState({ + isLoading: false, + policyUIConfigurationsList: JSON.parse(res.data.data), + currentStepIndex: 1, + }); + } + }) + .catch(error => { + if (error.hasOwnProperty('response') && error.response.status === 401) { + // todo display a popop with error + message.error('You are not logged in'); + window.location.href = window.location.origin + '/entgra/login'; + } else { + notification.error({ + message: 'There was a problem', + duration: 0, + description: 'Error occurred while trying to load Policy details.', + }); + } + this.setState({ isLoading: false }); + }); }; - next() { - const current = this.state.current + 1; - this.setState({ current }); - } + onHandleNext = () => { + const currentStepIndex = this.state.currentStepIndex + 1; + this.setState({ currentStepIndex }); + }; - prev() { - const current = this.state.current - 1; - this.setState({ current }); - } + onHandlePrev = () => { + const currentStepIndex = this.state.currentStepIndex - 1; + this.setState({ currentStepIndex }); + }; render() { - const { current } = this.state; + const { currentStepIndex, policyUIConfigurationsList } = this.state; return (
- + @@ -48,31 +109,50 @@ class AddPolicy extends React.Component { -
- +
+
-
- +
+
-
-
-
-
+
+
+
+
- {current > 0 && ( - )} - {current < 5 && current > 0 && ( - )} - {current === 5 && } + {currentStepIndex === 5 && }
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Policies/ConfigureProfile.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Policies/ConfigureProfile.js index 6241dc01362..608824f7172 100644 --- a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Policies/ConfigureProfile.js +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Policies/ConfigureProfile.js @@ -1,3 +1,21 @@ +/* + * Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved. + * + * Entgra (pvt) Ltd. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + import React from 'react'; import { Tabs, @@ -7,67 +25,343 @@ import { Input, Typography, Form, + Collapse, Checkbox, Select, Tooltip, Icon, + Table, Alert, Upload, + Popconfirm, Button, Radio, } from 'antd'; import { withConfigContext } from '../../context/ConfigContext'; import '../../pages/Dashboard/Policies/policies.css'; -import jsonResponse from './configuration'; -const { Title, Paragraph } = Typography; +import moment from 'moment'; +const { Text, Title, Paragraph } = Typography; const { TabPane } = Tabs; const { Option } = Select; const { TextArea } = Input; -const policyConfigurationsList = jsonResponse.PolicyConfigurations; - class ConfigureProfile extends React.Component { constructor(props) { super(props); this.config = this.props.context; - this.policies = policyConfigurationsList.androidPolicy.Policy; this.state = { + loading: false, isDisplayMain: 'none', - activeKeys: [], + activePanelKeys: [], + activeSubPanelKeys: [], + count: 0, + dataArray: [], + customInputDataArray: [], + inputTableDataSources: {}, + addPolicyForms: null, }; } componentDidMount() {} - onChange = e => { - console.log(`checked = ${e.target.id}`); + // convert time from 24h format to 12h format + timeConverter = time => { + time = time + .toString() + .match(/^([01]\d|2[0-3])(:)([0-5]\d)(:[0-5]\d)?$/) || [time]; + if (time.length > 1) { + time = time.slice(1); + time[5] = +time[0] < 12 ? ' AM' : ' PM'; + time[0] = +time[0] % 12 || 12; + } + return time.join(''); }; - onChecked = (e, i) => { - if (e) { - this.setState({ - isDisplayMain: 'block', + // get Option value from start Time, end Time and time difference between 2 values + getOptionForTimeSelectors = (startTimeValue, endTimeValue, timeIncrement) => { + let timeOptions = []; + let time = new Date( + moment() + .startOf('day') + .format('YYYY/MM/DD'), + ); + let tempValue = startTimeValue; + time.setMinutes(time.getMinutes() + tempValue); + let startOption = ( + + ); + timeOptions.push(startOption); + + while (tempValue !== endTimeValue) { + time = new Date( + moment() + .startOf('day') + .format('YYYY/MM/DD'), + ); + tempValue += timeIncrement; + if (tempValue > 1440) { + tempValue = 0; + continue; + } + time.setMinutes(time.getMinutes() + tempValue); + let option = ( + + ); + timeOptions.push(option); + } + return timeOptions; + }; + + // handle items which handle from radio buttons + handleRadioPanel = (e, subPanel) => { + { + subPanel.map((panel, i) => { + if (panel.value === e.target.value) { + document.getElementById(panel.value).style.display = 'block'; + } else { + document.getElementById(panel.value).style.display = 'none'; + } }); - } else { - this.setState({ - isDisplayMain: 'none', + } + }; + + // handle items which handle from select options + handleSelectedPanel = (e, subPanel) => { + { + subPanel.map((panel, i) => { + if (panel.id === e) { + document.getElementById(panel.id).style.display = 'block'; + } else { + document.getElementById(panel.id).style.display = 'none'; + } }); } }; - onClickSwitch = e => {}; + // handle items which handle from checkbox + handleSubPanel = e => { + if (e.target.checked) { + let joined = this.state.activeSubPanelKeys.concat(e.target.id); + this.setState({ activeSubPanelKeys: joined }); + } else { + let index = this.state.activeSubPanelKeys.indexOf(e.target.id); + if (index !== -1) { + this.state.activeSubPanelKeys.splice(index, 1); + let removed = this.state.activeSubPanelKeys; + this.setState({ activeSubPanelKeys: removed }); + } + } + }; + // handle Switch on off button + handleMainPanel = (e, ref) => { + if (e) { + let joined = this.state.activePanelKeys.concat(ref); + this.setState({ activePanelKeys: joined }); + } else { + let index = this.state.activePanelKeys.indexOf(ref); + if (index !== -1) { + this.state.activePanelKeys.splice(index, 1); + let removed = this.state.activePanelKeys; + this.setState({ activePanelKeys: removed }); + } + } + }; + + handleCustomInputTable = event => { + const { count, customInputDataArray } = this.state; + + const newData = [ + { + key: count, + CERT_NAME: `${event.file.name}`, + }, + ]; + this.setState({ + customInputDataArray: [...customInputDataArray, newData], + count: count + 1, + }); + }; + + handleAdd = array => { + const { count, inputTableDataSources } = this.state; + const newData = [ + { + key: count, + }, + ]; + inputTableDataSources[array].push(newData); + Object.defineProperty(inputTableDataSources, array, { + value: inputTableDataSources[array], + }); + this.setState({ + inputTableDataSources, + count: count + 1, + }); + }; + + getColumns = ({ getFieldDecorator }, arr) => { + const columnArray = []; + const actionColumn = [ + { + title: '', + dataIndex: 'operation', + render: (name, row) => ( + + + + + + + + + + ), + }, + ]; + Object.values(arr).map((columnData, c) => { + if (columnData.type === 'input') { + const column = { + title: `${columnData.name}`, + dataIndex: `${columnData.key}`, + key: `${columnData.key}`, + render: (name, row, i) => ( + + {getFieldDecorator(`${columnData.key}${i}`, {})( + , + )} + + ), + }; + columnArray.push(column); + } else if (columnData.type === 'upload') { + const column = { + title: `${columnData.name}`, + dataIndex: `${columnData.key}`, + key: `${columnData.key}`, + render: (name, row, i) => ( + + {getFieldDecorator(`${columnData.key}${i}`, {})( + + + , + )} + + ), + }; + columnArray.push(column); + } else if (columnData.type === 'select') { + const column = { + title: `${columnData.name}`, + dataIndex: `${columnData.key}`, + key: `${columnData.key}`, + render: (name, row, i) => ( + + {getFieldDecorator(`${columnData.key}${i}`, { + initialValue: columnData.others.initialDataIndex, + })( + , + )} + + ), + }; + columnArray.push(column); + } + }); + const columns = columnArray.concat(actionColumn); + return columns; + }; + + // generate form items getPanelItems = panel => { const { getFieldDecorator } = this.props.form; return panel.map((item, k) => { - switch (item._type) { + switch (item.type) { case 'select': + if (item.optional.hasOwnProperty('subPanel')) { + return ( +
+ + {item.label}  + + + + + } + style={{ display: 'block' }} + > + {getFieldDecorator(`${item.id}`, { + initialValue: `${item.optional.option[0].name}`, + })( + , + )} + +
+ {item.optional.subPanel.map((panel, i) => { + return ( +
+ {this.getPanelItems(panel.panelItem)} +
+ ); + })} +
+
+ ); + } return ( - {item.Label}  + {item.label}  @@ -75,24 +369,56 @@ class ConfigureProfile extends React.Component { } style={{ display: 'block' }} > - {getFieldDecorator(`${item._id}`, { - initialValue: `${item.Optional.Option[0]}`, + {getFieldDecorator(`${item.id}`, { + initialValue: `${item.optional.option[0].name}`, })( , )} ); + case 'timeSelector': + return ( + + {item.label}  + + + + + } + style={{ display: 'block' }} + > + {getFieldDecorator(`${item.id}`, { + // valuePropName: 'option', + initialValue: item.optional.initialDataIndex, + })( + , + )} + + ); case 'input': return ( - {item.Label}  + {item.label}  @@ -100,29 +426,71 @@ class ConfigureProfile extends React.Component { } style={{ display: 'block' }} > - {getFieldDecorator(`${item._id}`, { + {getFieldDecorator(`${item.id}`, { rules: [ { - pattern: new RegExp(`${item.Optional.rules.regex}`), - message: `${item.Optional.rules.validationMsg}`, + pattern: new RegExp(`${item.optional.rules.regex}`), + message: `${item.optional.rules.validationMsg}`, }, ], - })()} + })()} ); case 'checkbox': + if (item.optional.hasOwnProperty('subPanel')) { + return ( +
+ + + {getFieldDecorator(`${item.id}`, { + valuePropName: 'checked', + initialValue: item.optional.ischecked, + })( + + + {item.label}  + + + + + , + )} + + } + > +
+
+ {item.optional.subPanel.map((panel, i) => { + return ( +
+ {this.getPanelItems(panel.panelItem)} +
+ ); + })} +
+
+
+
+
+ ); + } return ( - {getFieldDecorator(`${item._id}`, { + {getFieldDecorator(`${item.id}`, { valuePropName: 'checked', - initialValue: `${item.Optional.checked}`, + initialValue: item.optional.ischecked, })( - + - {item.Label}  + {item.label}  @@ -131,13 +499,14 @@ class ConfigureProfile extends React.Component { )} ); + case 'textArea': return ( - {item.Label}  + {item.label}  @@ -145,56 +514,81 @@ class ConfigureProfile extends React.Component { } style={{ display: 'block' }} > - {getFieldDecorator(`${item._id}`, {})( + {getFieldDecorator(`${item.id}`, {})(