+ );
+ }
+}
+
+export default App;
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/App.test.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/App.test.js
new file mode 100644
index 0000000000..0b509e08c1
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/App.test.js
@@ -0,0 +1,27 @@
+/*
+ * 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 ReactDOM from 'react-dom';
+import App from './App';
+
+it('renders without crashing', () => {
+ const div = document.createElement('div');
+ ReactDOM.render(, div);
+ ReactDOM.unmountComponentAtNode(div);
+});
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/RouteWithSubRoutes.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/RouteWithSubRoutes.js
new file mode 100644
index 0000000000..f57ec331b0
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/RouteWithSubRoutes.js
@@ -0,0 +1,37 @@
+/*
+ * 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 {Route} from 'react-router-dom';
+class RouteWithSubRoutes extends React.Component{
+ props;
+ constructor(props){
+ super(props);
+ this.props = props;
+ }
+ render() {
+ return(
+ (
+
+ )}/>
+ );
+ }
+
+}
+
+export default RouteWithSubRoutes;
\ No newline at end of file
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/AppCard.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/AppCard.js
new file mode 100644
index 0000000000..d18aa40bbf
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/AppCard.js
@@ -0,0 +1,78 @@
+/*
+ * 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 {Card, Typography, Col, Row} from 'antd';
+import React from "react";
+import {Link} from "react-router-dom";
+import "../../App.css";
+import StarRatings from 'react-star-ratings';
+
+const {Meta} = Card;
+const {Text} = Typography;
+
+class AppCard extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.handleReleasesClick = this.handleReleasesClick.bind(this);
+ }
+
+ handleReleasesClick() {
+ this.props.openReleasesModal(this.props.app);
+ }
+
+ render() {
+ const app = this.props.app;
+ const release = this.props.app.applicationReleases[0];
+
+ const description = (
+
+
+
+
+
+ {/**/}
+
+
+ {app.name}
+ {app.deviceType}
+
+
+
+
+
+ );
+
+ return (
+
+
+
+ );
+ }
+}
+
+export default AppCard;
\ No newline at end of file
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/AppList.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/AppList.js
new file mode 100644
index 0000000000..e6e459e3dc
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/AppList.js
@@ -0,0 +1,120 @@
+/*
+ * 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 AppCard from "./AppCard";
+import {Col, message, notification, Row, Result, Skeleton} from "antd";
+import axios from "axios";
+import {withConfigContext} from "../../context/ConfigContext";
+
+class AppList extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ apps: [],
+ loading: true
+ }
+ }
+
+ componentDidMount() {
+ const {deviceType} = this.props;
+ this.props.changeSelectedMenuItem(deviceType);
+ this.fetchData(deviceType);
+ }
+
+
+ componentDidUpdate(prevProps, prevState) {
+ if (prevProps.deviceType !== this.props.deviceType) {
+ const {deviceType} = this.props;
+ this.props.changeSelectedMenuItem(deviceType);
+ this.fetchData(deviceType);
+ }
+ }
+
+ fetchData = (deviceType) => {
+ const config = this.props.context;
+ const payload = {};
+ if (deviceType === "web-clip") {
+ payload.appType = "WEB_CLIP";
+ } else {
+ payload.deviceType = deviceType;
+ }
+ this.setState({
+ loading: true
+ });
+ //send request to the invoker
+ axios.post(
+ window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/applications/",
+ payload,
+ ).then(res => {
+ if (res.status === 200) {
+ //todo remove this property check after backend improvement
+ let apps = (res.data.data.hasOwnProperty("applications")) ? res.data.data.applications : [];
+ this.setState({
+ apps: apps,
+ loading: false
+ })
+ }
+
+ }).catch((error) => {
+ console.log(error.response);
+ if (error.hasOwnProperty("response") && error.response.status === 401) {
+ //todo display a popup with error
+ message.error('You are not logged in');
+ window.location.href = window.location.origin+ '/store/login';
+ } else {
+ notification["error"]({
+ message: "There was a problem",
+ duration: 0,
+ description:
+ "Error occurred while trying to load apps.",
+ });
+ }
+
+ this.setState({loading: false});
+ });
+ };
+
+ render() {
+ const {apps,loading} = this.state;
+
+ return (
+
+
+ {apps.length === 0 && (
+ Back Home}
+ />
+ )}
+ {apps.map(app => (
+
+
+
+ ))}
+
+
+ );
+ }
+}
+
+export default withConfigContext(AppList);
\ No newline at end of file
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/DetailedRating.css b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/DetailedRating.css
new file mode 100644
index 0000000000..28a761eb7b
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/DetailedRating.css
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+
+.d-rating .numeric-data{
+ box-sizing: border-box;
+ display: inline-block;
+ padding: 20px 0 20px 0;
+ vertical-align: top;
+ text-align: center;
+ width: 30%;
+}
+
+.d-rating .bar-containers{
+ box-sizing: border-box;
+ display: inline-block;
+ padding: 20px 20px 20px 30px;
+ vertical-align: top;
+ width: 70%;
+}
+
+.d-rating .bar-containers .bar-container{
+ color: #737373;
+ font-weight: 400;
+ height: 20px;
+ margin-bottom: 4px;
+ position: relative;
+ width: 100%;
+}
+
+.d-rating .bar-containers .bar-container .number{
+ font-size: 11px;
+ left: -16px;
+ letter-spacing: 1px;
+ position: absolute;
+}
+
+.d-rating .bar-containers .bar-container .bar{
+ transition: width .25s ease;
+ display: inline-block;
+ height: 100%;
+ opacity: .8;
+ border-radius: 5px;
+}
+
+.bar-container .rate-5{
+ background: #57bb8a;
+}
+
+.bar-container .rate-4{
+ background: #9ace6a;
+}
+
+.bar-container .rate-3{
+ background: #ffcf02;
+}
+
+.bar-container .rate-2{
+ background: #ff9f02;
+}
+
+.bar-container .rate-1{
+ background: #ff6f31;
+}
+
+.d-rating .numeric-data .rate{
+ color: #333;
+ font-size: 64px;
+ font-weight: 100;
+ line-height: 64px;
+ padding-bottom: 6px;
+}
+
+.d-rating .numeric-data .people-count{
+ padding-top: 6px;
+}
\ No newline at end of file
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/DetailedRating.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/DetailedRating.js
new file mode 100644
index 0000000000..0536e966cf
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/DetailedRating.js
@@ -0,0 +1,140 @@
+/*
+ * 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 {Row, Typography, Icon} from "antd";
+import StarRatings from "react-star-ratings";
+import "./DetailedRating.css";
+import axios from "axios";
+import {withConfigContext} from "../../../context/ConfigContext";
+
+const { Text } = Typography;
+
+
+class DetailedRating extends React.Component{
+
+ constructor(props){
+ super(props);
+ this.state={
+ detailedRating: null
+ }
+ }
+
+ componentDidMount() {
+ const {type,uuid} = this.props;
+ this.getData(type,uuid);
+ }
+
+ componentDidUpdate(prevProps, prevState) {
+ if (prevProps.uuid !== this.props.uuid) {
+ const {type,uuid} = this.props;
+ this.getData(type,uuid);
+ }
+ }
+
+
+ getData = (type, uuid)=>{
+ const config = this.props.context;
+
+ return axios.get(
+ window.location.origin+ config.serverConfig.invoker.uri +config.serverConfig.invoker.store+"/reviews/"+uuid+"/"+type+"-rating",
+ ).then(res => {
+ if (res.status === 200) {
+ let detailedRating = res.data.data;
+ this.setState({
+ detailedRating
+ })
+ }
+
+ }).catch(function (error) {
+ if (error.response.status === 401) {
+ window.location.href = window.location.origin+'/store/login';
+ }
+ });
+ };
+
+ render() {
+ const detailedRating = this.state.detailedRating;
+
+ if(detailedRating ==null){
+ return null;
+ }
+
+ const totalCount = detailedRating.noOfUsers;
+ const ratingVariety = detailedRating.ratingVariety;
+
+ const ratingArray = [];
+
+ for (let [key, value] of Object.entries(ratingVariety)) {
+ ratingArray.push(value);
+ }
+
+ const maximumRating = Math.max(...ratingArray);
+
+ const ratingBarPercentages = [0,0,0,0,0];
+
+ if(maximumRating>0){
+ for(let i = 0; i<5; i++){
+ ratingBarPercentages[i] = (ratingVariety[(i+1).toString()])/maximumRating*100;
+ }
+ }
+
+ return (
+
+
+
{detailedRating.ratingValue.toFixed(1)}
+
+
+ {totalCount} total
+
+
+
+ 5
+
+
+
+ 4
+
+
+
+ 3
+
+
+
+ 2
+
+
+
+ 1
+
+
+
+
+ );
+ }
+}
+
+
+export default withConfigContext(DetailedRating);
\ No newline at end of file
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/ReleaseView.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/ReleaseView.js
new file mode 100644
index 0000000000..6d55b45361
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/ReleaseView.js
@@ -0,0 +1,171 @@
+/*
+ * 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 {Divider, Row, Col, Typography, Button, Rate, notification} from "antd";
+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 CurrentUsersReview from "./review/CurrentUsersReview";
+import {withConfigContext} from "../../../context/ConfigContext";
+
+const {Title, Text, Paragraph} = Typography;
+
+class ReleaseView extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ loading: false,
+ appInstallModalVisible: false
+ }
+ }
+
+ installApp = (type, payload) => {
+ const config = this.props.context;
+ const release = this.props.app.applicationReleases[0];
+ const {uuid} = release;
+
+ this.setState({
+ loading: true,
+ });
+ const url = window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/subscription/" + uuid + "/" + type + "/install";
+ axios.post(
+ url,
+ payload,
+ {
+ headers: {'X-Platform': config.serverConfig.platform}
+ }
+ ).then(res => {
+ if (res.status === 200) {
+ this.setState({
+ loading: false,
+ appInstallModalVisible: false
+ });
+ notification["success"]({
+ message: 'Done!',
+ description:
+ 'App installed triggered.',
+ });
+ } else {
+ this.setState({
+ loading: false
+ });
+ notification["error"]({
+ message: "There was a problem",
+ duration: 0,
+ description:
+ "Error occurred while installing app",
+ });
+ }
+
+ }).catch((error) => {
+ if (error.response.status === 401) {
+ window.location.href = window.location.origin+ '/store/login';
+ } else {
+ this.setState({
+ loading: false,
+ visible: false
+ });
+ notification["error"]({
+ message: "There was a problem",
+ duration: 0,
+ description:
+ "Error occurred while installing the app.",
+ });
+ }
+ });
+ };
+
+ showAppInstallModal = () => {
+ this.setState({
+ appInstallModalVisible: true
+ });
+ };
+
+ closeAppInstallModal = () => {
+ this.setState({
+ appInstallModalVisible: false
+ });
+ };
+
+ render() {
+ const {app,deviceType} = this.props;
+ const release = app.applicationReleases[0];
+ return (
+
+ );
+ }
+}
+
+export default withConfigContext(ReleaseView);
\ No newline at end of file
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/images/ImgViewer.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/images/ImgViewer.js
new file mode 100644
index 0000000000..17a136aac6
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/images/ImgViewer.js
@@ -0,0 +1,63 @@
+/*
+ * 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, {Component} from 'react';
+import RcViewer from 'rc-viewer';
+import {Col} from "antd";
+
+class ImgViewer extends Component {
+ render() {
+ const options = {
+ title: false,
+ toolbar: {
+ zoomIn: 0,
+ zoomOut: 0,
+ oneToOne: 0,
+ reset: 0,
+ prev: 1,
+ play: {
+ show: 0
+ },
+ next: 1,
+ rotateLeft: 0,
+ rotateRight: 0,
+ flipHorizontal: 0,
+ flipVertical: 0
+ },
+ rotatable: false,
+ transition: false,
+ movable : false
+ };
+ return (
+
+ );
+
+ }
+}
+
+export default ImgViewer;
\ No newline at end of file
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/install/AppInstallModal.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/install/AppInstallModal.js
new file mode 100644
index 0000000000..1b4d9c8c2e
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/install/AppInstallModal.js
@@ -0,0 +1,62 @@
+/*
+ * 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 {Modal, Tabs} from "antd";
+import UserInstall from "./UserInstall";
+import GroupInstall from "./GroupInstall";
+import RoleInstall from "./RoleInstall";
+import DeviceInstall from "./DeviceInstall";
+
+const { TabPane } = Tabs;
+
+class AppInstallModal extends React.Component{
+ state={
+ data:[]
+ };
+ render() {
+ const {deviceType} = this.props;
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default AppInstallModal;
\ No newline at end of file
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/install/DeviceInstall.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/install/DeviceInstall.js
new file mode 100644
index 0000000000..34dfefd362
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/install/DeviceInstall.js
@@ -0,0 +1,248 @@
+/*
+ * 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 axios from "axios";
+import {Button, 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";
+
+const {Text} = Typography;
+const columns = [
+ {
+ title: 'Device',
+ dataIndex: 'name',
+ fixed: 'left',
+ width: 100,
+ },
+ {
+ title: 'Modal',
+ dataIndex: 'deviceInfo',
+ key: 'modal',
+ render: deviceInfo => `${deviceInfo.vendor} ${deviceInfo.deviceModel}`
+ // todo add filtering options
+ },
+ {
+ title: 'Owner',
+ dataIndex: 'enrolmentInfo',
+ key: 'owner',
+ render: enrolmentInfo => enrolmentInfo.owner
+ // todo add filtering options
+ },
+ {
+ title: 'Last Updated',
+ dataIndex: 'enrolmentInfo',
+ key: 'dateOfLastUpdate',
+ render: (data) => {
+ return (getTimeAgo(data.dateOfLastUpdate));
+ }
+ // todo add filtering options
+ },
+ {
+ title: 'Status',
+ dataIndex: 'enrolmentInfo',
+ key: 'status',
+ render: enrolmentInfo => enrolmentInfo.status
+ // todo add filtering options
+ },
+ {
+ title: 'Ownership',
+ dataIndex: 'enrolmentInfo',
+ key: 'ownership',
+ render: enrolmentInfo => enrolmentInfo.ownership
+ // todo add filtering options
+ },
+ {
+ title: 'OS Version',
+ dataIndex: 'deviceInfo',
+ key: 'osVersion',
+ render: deviceInfo => deviceInfo.osVersion
+ // todo add filtering options
+ },
+ {
+ title: 'IMEI',
+ dataIndex: 'properties',
+ key: 'imei',
+ render: properties => {
+ let imei = "not-found";
+ for (let i = 0; i < properties.length; i++) {
+ if (properties[i].name === "IMEI") {
+ imei = properties[i].value;
+ }
+ }
+ return imei;
+ }
+ // todo add filtering options
+ },
+];
+
+const getTimeAgo = (time) => {
+ const timeAgo = new TimeAgo('en-US');
+ return timeAgo.format(time);
+};
+
+
+class DeviceInstall extends React.Component {
+ constructor(props) {
+ super(props);
+ TimeAgo.addLocale(en);
+ this.state = {
+ data: [],
+ pagination: {},
+ loading: false,
+ selectedRows: []
+ };
+ }
+
+ rowSelection = {
+ onChange: (selectedRowKeys, selectedRows) => {
+ // console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
+ this.setState({
+ selectedRows: selectedRows
+ })
+ },
+ getCheckboxProps: record => ({
+ disabled: record.name === 'Disabled User', // Column configuration not to be checked
+ name: record.name,
+ }),
+ };
+
+ componentDidMount() {
+ this.fetch();
+ }
+
+ //fetch data from api
+ fetch = (params = {}) => {
+ const config = this.props.context;
+ this.setState({loading: true});
+ const {deviceType} = this.props;
+ // get current page
+ const currentPage = (params.hasOwnProperty("page")) ? params.page : 1;
+
+ const extraParams = {
+ offset: 10 * (currentPage - 1), //calculate the offset
+ limit: 10,
+ status: "ACTIVE",
+ requireDeviceInfo: true,
+ type: deviceType
+ };
+
+ // note: encode with '%26' not '&'
+ const encodedExtraParams = Object.keys(extraParams).map(key => key + '=' + extraParams[key]).join('&');
+
+ //send request to the invoker
+ axios.get(
+ window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.deviceMgt +
+ "/devices?" + encodedExtraParams,
+ ).then(res => {
+ if (res.status === 200) {
+ const pagination = {...this.state.pagination};
+ this.setState({
+ loading: false,
+ data: res.data.data.devices,
+ pagination,
+ });
+
+ }
+
+ }).catch((error) => {
+ console.log(error);
+ if (error.hasOwnProperty("status") && error.response.status === 401) {
+ //todo display a popop with error
+ message.error('You are not logged in');
+ window.location.href = window.location.origin + '/store/login';
+ } else {
+ notification["error"]({
+ message: "There was a problem",
+ duration: 0,
+ description:
+ "Error occurred while trying to load devices.",
+ });
+ }
+
+ this.setState({loading: false});
+ });
+ };
+
+ handleTableChange = (pagination, filters, sorter) => {
+ const pager = {...this.state.pagination};
+ pager.current = pagination.current;
+ this.setState({
+ pagination: pager,
+ });
+ this.fetch({
+ results: pagination.pageSize,
+ page: pagination.current,
+ sortField: sorter.field,
+ sortOrder: sorter.order,
+ ...filters,
+ });
+ };
+
+ install = () => {
+ const {selectedRows} = this.state;
+ const payload = [];
+ selectedRows.map(device => {
+ payload.push({
+ id: device.deviceIdentifier,
+ type: device.type
+ });
+ });
+ this.props.onInstall("devices", payload);
+ };
+
+ render() {
+ const {data, pagination, loading, selectedRows} = this.state;
+ return (
+
+
+ Start installing the application for one or more users by entering the corresponding user name.
+ Select install to automatically start downloading the application for the respective user/users.
+
+
+
+ );
+ }
+}
+
+export default withConfigContext(DeviceInstall);
\ No newline at end of file
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/install/GroupInstall.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/install/GroupInstall.js
new file mode 100644
index 0000000000..a8af7482c9
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/install/GroupInstall.js
@@ -0,0 +1,133 @@
+/*
+ * 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 {Typography, Select, Spin, message, notification, Button} from "antd";
+import debounce from 'lodash.debounce';
+import axios from "axios";
+import {withConfigContext} from "../../../../context/ConfigContext";
+
+const {Text} = Typography;
+const {Option} = Select;
+
+
+class GroupInstall extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.lastFetchId = 0;
+ this.fetchUser = debounce(this.fetchUser, 800);
+ }
+
+ state = {
+ data: [],
+ value: [],
+ fetching: false,
+ };
+
+ fetchUser = value => {
+ this.lastFetchId += 1;
+ const fetchId = this.lastFetchId;
+ const config = this.props.context;
+ this.setState({data: [], fetching: true});
+
+ axios.post(
+ window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.deviceMgt+"/groups?name=" + value,
+
+ ).then(res => {
+ if (res.status === 200) {
+ if (fetchId !== this.lastFetchId) {
+ // for fetch callback order
+ return;
+ }
+
+ const data = res.data.data.deviceGroups.map(group => ({
+ text: group.name,
+ value: group.name,
+ }));
+
+ this.setState({data, fetching: false});
+ }
+
+ }).catch((error) => { console.log(error);
+ if (error.hasOwnProperty("status") && error.response.status === 401) {
+ message.error('You are not logged in');
+ window.location.href = window.location.origin+'/store/login';
+ } else {
+ notification["error"]({
+ message: "There was a problem",
+ duration: 0,
+ description:
+ "Error occurred while trying to load groups.",
+ });
+ }
+
+ this.setState({fetching: false});
+ });
+ };
+
+ handleChange = value => {
+ this.setState({
+ value,
+ data: [],
+ fetching: false,
+ });
+ };
+
+ install = () =>{
+ const {value} = this.state;
+ const data = [];
+ value.map(val=>{
+ data.push(val.key);
+ });
+ this.props.onInstall("group",data);
+ };
+
+ render() {
+
+ const {fetching, data, value} = this.state;
+
+ return (
+
+ Start installing the application for one or more groups by entering the corresponding group name. Select install to automatically start downloading the application for the respective device group/ groups.
+
+
+ : null}
+ filterOption={false}
+ onSearch={this.fetchUser}
+ onChange={this.handleChange}
+ style={{width: '100%'}}
+ >
+ {data.map(d => (
+
+ ))}
+
+
+
+
+
+ );
+ }
+}
+
+export default withConfigContext(GroupInstall);
\ No newline at end of file
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/install/RoleInstall.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/install/RoleInstall.js
new file mode 100644
index 0000000000..877fd3a4cb
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/install/RoleInstall.js
@@ -0,0 +1,133 @@
+/*
+ * 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 {Typography, Select, Spin, message, notification, Button} from "antd";
+import debounce from 'lodash.debounce';
+import axios from "axios";
+import {withConfigContext} from "../../../../context/ConfigContext";
+
+const {Text} = Typography;
+const {Option} = Select;
+
+
+class RoleInstall extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.lastFetchId = 0;
+ this.fetchUser = debounce(this.fetchUser, 800);
+ }
+
+ state = {
+ data: [],
+ value: [],
+ fetching: false,
+ };
+
+ fetchUser = value => {
+ const config = this.props.context;
+ this.lastFetchId += 1;
+ const fetchId = this.lastFetchId;
+ this.setState({data: [], fetching: true});
+
+ axios.get(
+ window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.deviceMgt+"/roles?filter=" + value,
+
+ ).then(res => {
+ if (res.status === 200) {
+ if (fetchId !== this.lastFetchId) {
+ // for fetch callback order
+ return;
+ }
+
+ const data = res.data.data.roles.map(role => ({
+ text: role,
+ value: role,
+ }));
+
+ this.setState({data, fetching: false});
+ }
+
+ }).catch((error) => { console.log(error);
+ if (error.hasOwnProperty("status") && error.response.status === 401) {
+ message.error('You are not logged in');
+ window.location.href = window.location.origin+'/store/login';
+ } else {
+ notification["error"]({
+ message: "There was a problem",
+ duration: 0,
+ description:
+ "Error occurred while trying to load roles.",
+ });
+ }
+
+ this.setState({fetching: false});
+ });
+ };
+
+ handleChange = value => {
+ this.setState({
+ value,
+ data: [],
+ fetching: false,
+ });
+ };
+
+ install = () =>{
+ const {value} = this.state;
+ const data = [];
+ value.map(val=>{
+ data.push(val.key);
+ });
+ this.props.onInstall("role",data);
+ };
+
+ render() {
+
+ const {fetching, data, value} = this.state;
+
+ return (
+
+ Start installing the application for one or more roles by entering the corresponding role name. Select install to automatically start downloading the application for the respective user role/roles.
+
+
+ : null}
+ filterOption={false}
+ onSearch={this.fetchUser}
+ onChange={this.handleChange}
+ style={{width: '100%'}}
+ >
+ {data.map(d => (
+
+ ))}
+
+
+
+
+
+ );
+ }
+}
+
+export default withConfigContext(RoleInstall);
\ No newline at end of file
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/install/UserInstall.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/install/UserInstall.js
new file mode 100644
index 0000000000..8e592d52cf
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/install/UserInstall.js
@@ -0,0 +1,133 @@
+/*
+ * 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 {Typography, Select, Spin, message, notification, Button} from "antd";
+import debounce from 'lodash.debounce';
+import axios from "axios";
+import {withConfigContext} from "../../../../context/ConfigContext";
+
+const {Text} = Typography;
+const {Option} = Select;
+
+
+class UserInstall extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.lastFetchId = 0;
+ this.fetchUser = debounce(this.fetchUser, 800);
+ }
+
+ state = {
+ data: [],
+ value: [],
+ fetching: false,
+ };
+
+ fetchUser = value => {
+ const config = this.props.context;
+ this.lastFetchId += 1;
+ const fetchId = this.lastFetchId;
+ this.setState({data: [], fetching: true});
+
+
+ //send request to the invoker
+ axios.get(
+ window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.deviceMgt+"/users/search?username=" + value,
+
+ ).then(res => {
+ if (res.status === 200) {
+ if (fetchId !== this.lastFetchId) {
+ // for fetch callback order
+ return;
+ }
+
+ const data = res.data.data.users.map(user => ({
+ text: user.username,
+ value: user.username,
+ }));
+
+ this.setState({data, fetching: false});
+ }
+
+ }).catch((error) => {
+ if (error.response.hasOwnProperty(status) && error.response.status === 401) {
+ message.error('You are not logged in');
+ window.location.href = window.location.origin+ '/store/login';
+ } else {
+ notification["error"]({
+ message: "There was a problem",
+ duration: 0,
+ description:
+ "Error occurred while trying to load users.",
+ });
+ }
+
+ this.setState({fetching: false});
+ });
+ };
+
+ handleChange = value => {
+ this.setState({
+ value,
+ data: [],
+ fetching: false,
+ });
+ };
+
+ install = () => {
+ const {value} = this.state;
+ const data = [];
+ value.map(val => {
+ data.push(val.key);
+ });
+ this.props.onInstall("user", data);
+ };
+
+ render() {
+ const {fetching, data, value} = this.state;
+
+ return (
+
+ Start installing the application for one or more users by entering the corresponding user name. Select install to automatically start downloading the application for the respective user/users.
+
+ );
+ }
+}
+
+export default withConfigContext(SingleReview);
\ No newline at end of file
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/review/singleReview/editReview/EditReview.css b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/review/singleReview/editReview/EditReview.css
new file mode 100644
index 0000000000..ec9dd385a5
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/review/singleReview/editReview/EditReview.css
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+.edit-button {
+ color: #1890ff;
+ text-decoration: none;
+ outline: none;
+ cursor: pointer;
+ font-weight: normal;
+ font-size: 0.8em;
+ padding-left: 5px;
+}
\ No newline at end of file
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/review/singleReview/editReview/EditReview.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/review/singleReview/editReview/EditReview.js
new file mode 100644
index 0000000000..f18bac5079
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/apps/release/review/singleReview/editReview/EditReview.js
@@ -0,0 +1,191 @@
+/*
+ * 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 {Drawer, Button, Icon, Row, Col, Typography, Divider, Input, Spin, notification} from 'antd';
+import StarRatings from "react-star-ratings";
+import axios from "axios";
+import "./EditReview.css";
+import {withConfigContext} from "../../../../../../context/ConfigContext";
+
+const {Title} = Typography;
+const {TextArea} = Input;
+
+class EditReview extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ visible: false,
+ content: '',
+ rating: 0,
+ loading: false
+ };
+ }
+
+ componentDidMount() {
+ const {content,rating,id} = this.props.review;
+ console.log(this.props.review);
+ this.setState({
+ content,
+ rating
+ });
+ }
+
+ showDrawer = () => {
+ this.setState({
+ visible: true,
+ loading: false
+ });
+ };
+
+ onClose = () => {
+ this.setState({
+ visible: false,
+
+ });
+ };
+
+ changeRating = (newRating, name) => {
+ this.setState({
+ rating: newRating
+ });
+ };
+
+ onChange = (e) => {
+ this.setState({content: e.target.value})
+ };
+
+ onSubmit = () => {
+ const config = this.props.context;
+ const {content, rating} = this.state;
+ const {id} = this.props.review;
+ const {uuid} = this.props;
+ this.setState({
+ loading: true
+ });
+
+ const payload = {
+ content: content,
+ rating: rating
+ };
+
+ axios.put(
+ window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/reviews/" + uuid+"/"+id,
+ payload,
+ ).then(res => {
+ if (res.status === 200) {
+ this.setState({
+ loading: false,
+ visible: false
+ });
+ notification["success"]({
+ message: 'Done!',
+ description:
+ 'Your review has been update successfully.',
+ });
+
+ this.props.updateCallback(res.data.data);
+ } else {
+ this.setState({
+ loading: false,
+ visible: false
+ });
+ notification["error"]({
+ message: "There was a problem",
+ duration: 0,
+ description:
+ "We are unable to update your review right now.",
+ });
+ }
+
+ }).catch((error) => {
+ console.log(error);
+ if (error.hasOwnProperty("response") && error.response.status === 401) {
+ window.location.href = window.location.origin+ '/store/login';
+ } else {
+ this.setState({
+ loading: false,
+ visible: false
+ });
+ notification["error"]({
+ message: "There was a problem",
+ duration: 0,
+ description:
+ "We are unable to add your review right now.",
+ });
+ }
+ });
+
+
+ };
+
+ render() {
+ return (
+
+ edit
+
+
+
+
+
+ Edit review
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default withConfigContext(EditReview);
\ No newline at end of file
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/context/ConfigContext.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/context/ConfigContext.js
new file mode 100644
index 0000000000..ea680a4cc5
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/context/ConfigContext.js
@@ -0,0 +1,34 @@
+/*
+ * 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";
+
+const ConfigContext = React.createContext();
+
+export const withConfigContext = Component => {
+ return props => (
+
+ {context => {
+ return ;
+ }}
+
+ );
+};
+
+export default ConfigContext;
+
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/index.css b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/index.css
new file mode 100644
index 0000000000..74dcd95a69
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/index.css
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+.App {
+ padding: 20px;
+}
+
+.ant-layout-header{
+ padding: 0;
+ height: auto;
+ box-shadow: 0 2px 8px #f0f1f2;
+}
+
+.steps-content {
+ margin-top: 16px;
+ border: 1px dashed #e9e9e9;
+ border-radius: 6px;
+ background-color: #fafafa;
+ min-height: 200px;
+ text-align: center;
+ padding-top: 80px;
+}
+
+.steps-action {
+ margin-top: 24px;
+}
+
+.ant-input-affix-wrapper .ant-input{
+ min-height: 0;
+}
\ No newline at end of file
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/index.html b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/index.html
new file mode 100644
index 0000000000..8f254050f2
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/index.html
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+ Entgra App Store
+
+
+
\ No newline at end of file
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/index.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/index.js
new file mode 100644
index 0000000000..480ffce5af
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/index.js
@@ -0,0 +1,62 @@
+/*
+ * 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 ReactDOM from 'react-dom';
+import * as serviceWorker from './serviceWorker';
+import App from "./App";
+import Login from "./pages/Login";
+import Dashboard from "./pages/dashboard/Dashboard";
+import Apps from "./pages/dashboard/apps/Apps";
+import Release from "./pages/dashboard/apps/release/Release";
+import './index.css';
+
+const routes = [
+ {
+ path: '/store/login',
+ exact: true,
+ component: Login
+ },
+ {
+ path: '/store',
+ exact: false,
+ component: Dashboard,
+ routes: [
+ {
+ path: '/store/:deviceType',
+ component: Apps,
+ exact: true
+ },
+ {
+ path: '/store/:deviceType/apps/:uuid',
+ exact: true,
+ component: Release
+ }
+ ]
+ }
+];
+
+
+ReactDOM.render(
+ ,
+ document.getElementById('root'));
+
+// If you want your app e and load faster, you can change
+// unregister() to register() below. Note this comes with some pitfalls.
+// Learn more about service workers: https://bit.ly/CRA-PWA
+serviceWorker.unregister();
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/logo.svg b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/logo.svg
new file mode 100644
index 0000000000..6b60c1042f
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/logo.svg
@@ -0,0 +1,7 @@
+
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/Login.css b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/Login.css
new file mode 100644
index 0000000000..debb603a35
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/Login.css
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+@-moz-keyframes spin {
+ 0% {
+ -moz-transform: rotate(0deg) scale(1.0);
+ }
+ 100% {
+ -moz-transform: rotate(360deg) scale(0.1);
+ }
+}
+
+@-webkit-keyframes spin {
+ 0% {
+ -webkit-transform: rotate(0deg) scale(1.0);
+ }
+ 100% {
+ -webkit-transform: rotate(360deg) scale(0.1);
+ }
+}
+
+@keyframes spin {
+ 0% {
+ -webkit-transform: rotate(0deg) scale(1.0);
+ }
+ 100% {
+ -webkit-transform: rotate(360deg) scale(0.1);
+ transform: rotate(360deg) scale(0.1);
+ }
+}
+
+.background {
+ position: absolute;
+ height: 100%;
+ width: 100%;
+ z-index: 0;
+ background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg');
+ background-repeat: no-repeat;
+ background-position: center 110px;
+ background-size: 100%;
+ animation: spin 200s infinite linear;
+}
+
+.content {
+ position: relative;
+ z-index: 1;
+}
\ No newline at end of file
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/Login.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/Login.js
new file mode 100644
index 0000000000..5aed47a3b4
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/Login.js
@@ -0,0 +1,171 @@
+/*
+ * 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 {Typography, Row, Col, Form, Icon, Input, Button, Checkbox} from 'antd';
+import './Login.css';
+import axios from 'axios';
+import {withConfigContext} from "../context/ConfigContext";
+
+const {Title} = Typography;
+const {Text} = Typography;
+
+class Login extends React.Component {
+ render() {
+ const config = this.props.context;
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Login
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+class NormalLoginForm extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ inValid: false,
+ loading: false
+ };
+ }
+
+ handleSubmit = (e) => {
+ const thisForm = this;
+ const config = this.props.context;
+ console.log(config);
+
+ e.preventDefault();
+ this.props.form.validateFields((err, values) => {
+ thisForm.setState({
+ inValid: false
+ });
+ if (!err) {
+ thisForm.setState({
+ loading: true
+ });
+ const parameters = {
+ username: values.username,
+ password: values.password,
+ platform: "store"
+ };
+
+ const request = Object.keys(parameters).map(key => key + '=' + parameters[key]).join('&');
+
+ axios.post(window.location.origin+ config.serverConfig.loginUri, request
+ ).then(res => {
+ if (res.status === 200) {
+ window.location = window.location.origin+ "/store";
+ }
+ }).catch(function (error) {
+ if (error.response.status === 400) {
+ thisForm.setState({
+ inValid: true,
+ loading: false
+ });
+ }
+ });
+ }
+
+ });
+ };
+
+ render() {
+ const {getFieldDecorator} = this.props.form;
+ let errorMsg = "";
+ if (this.state.inValid) {
+ errorMsg = Invalid Login Details;
+ }
+ let loading = "";
+ if (this.state.loading) {
+ loading = Loading..;
+ }
+ return (
+
+ {getFieldDecorator('username', {
+ rules: [{required: true, message: 'Please input your username!'}],
+ })(
+ }
+ placeholder="Username"/>
+ )}
+
+
+ {getFieldDecorator('password', {
+ rules: [{required: true, message: 'Please input your Password!'}],
+ })(
+ } type="password"
+ placeholder="Password"/>
+ )}
+
+ {loading}
+ {errorMsg}
+
+ {getFieldDecorator('remember', {
+ valuePropName: 'checked',
+ initialValue: true,
+ })(
+ Remember me
+ )}
+
+ Forgot password
+
+
+
+ );
+ }
+}
+
+const WrappedNormalLoginForm = withConfigContext(Form.create({name: 'normal_login'})(NormalLoginForm));
+
+export default withConfigContext(Login);
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/Dashboard.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/Dashboard.js
new file mode 100644
index 0000000000..c7b8c47575
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/Dashboard.js
@@ -0,0 +1,157 @@
+/*
+ * 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 {Layout, Menu, Icon} from 'antd';
+
+const {Header, Content, Footer} = Layout;
+import {Link} from "react-router-dom";
+import RouteWithSubRoutes from "../../components/RouteWithSubRoutes";
+import {Switch} from 'react-router';
+import axios from "axios";
+import "../../App.css";
+import {withConfigContext} from "../../context/ConfigContext";
+import Logout from "./logout/Logout";
+
+const {SubMenu} = Menu;
+
+class Dashboard extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ routes: props.routes,
+ selectedKeys: [],
+ deviceTypes: []
+ };
+ this.logo = this.props.context.theme.logo;
+ }
+
+ componentDidMount() {
+ this.getDeviceTypes();
+ }
+
+ getDeviceTypes = () => {
+ const config = this.props.context;
+ axios.get(
+ window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.deviceMgt + "/device-types"
+ ).then(res => {
+ if (res.status === 200) {
+ const deviceTypes = JSON.parse(res.data.data);
+ this.setState({
+ deviceTypes,
+ loading: false,
+ });
+ }
+
+ }).catch((error) => {
+ if (error.hasOwnProperty("response") && error.response.status === 401) {
+ window.location.href = window.location.origin + '/store/login';
+ } else {
+ notification["error"]({
+ message: "There was a problem",
+ duration: 0,
+ description:
+ "Error occurred while trying to load device types.",
+ });
+ }
+ this.setState({
+ loading: false
+ });
+ });
+ };
+
+ changeSelectedMenuItem = (key) => {
+ this.setState({
+ selectedKeys: [key]
+ })
+ };
+
+ render() {
+ const config = this.props.context;
+ const {selectedKeys, deviceTypes} = this.state;
+
+ return (
+
Support for a single or bulk upload.
+ Strictly prohibit from uploading company data or other band
+ files
+
+
+
+
+
Icon
+
+
+
+
Banner
+
+
+
+
+
+
+
+
Screenshots
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default AddNewApp;
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/add-new-app/IconImg.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/add-new-app/IconImg.js
new file mode 100644
index 0000000000..00937843d2
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/add-new-app/IconImg.js
@@ -0,0 +1,84 @@
+/*
+ * 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 { Upload, Icon, message } from 'antd';
+
+function getBase64(img, callback) {
+ const reader = new FileReader();
+ reader.addEventListener('load', () => callback(reader.result));
+ reader.readAsDataURL(img);
+}
+
+function beforeUpload(file) {
+ const isJPG = file.type === 'image/jpeg';
+ if (!isJPG) {
+ message.error('You can only upload JPG file!');
+ }
+ const isLt2M = file.size / 1024 / 1024 < 2;
+ if (!isLt2M) {
+ message.error('Image must smaller than 2MB!');
+ }
+ return isJPG && isLt2M;
+}
+
+
+class IconImage extends React.Component {
+ state = {
+ loading: false,
+ };
+
+ handleChange = (info) => {
+ if (info.file.status === 'uploading') {
+ this.setState({ loading: true });
+ return;
+ }
+ if (info.file.status === 'done') {
+ // Get this url from response in real world.
+ getBase64(info.file.originFileObj, imageUrl => this.setState({
+ imageUrl,
+ loading: false,
+ }));
+ }
+ }
+
+ render() {
+ const uploadButton = (
+
+
+
Upload
+
+ );
+ const imageUrl = this.state.imageUrl;
+ return (
+
+ {imageUrl ? : uploadButton}
+
+ );
+ }
+}
+
+export default IconImage;
\ No newline at end of file
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/add-new-app/Step1.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/add-new-app/Step1.js
new file mode 100644
index 0000000000..ebe00b9122
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/add-new-app/Step1.js
@@ -0,0 +1,170 @@
+/*
+ * 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 {Form, Input, Button, Select, Divider, Tag, Tooltip, Icon, Checkbox, Row, Col} from "antd";
+import styles from './Style.less';
+
+const { Option } = Select;
+const { TextArea } = Input;
+const InputGroup = Input.Group;
+
+const formItemLayout = {
+ labelCol: {
+ span: 8,
+ },
+ wrapperCol: {
+ span: 16,
+ },
+};
+
+class EditableTagGroup extends React.Component {
+ state = {
+ tags: [],
+ inputVisible: false,
+ inputValue: '',
+ };
+
+ handleClose = (removedTag) => {
+ const tags = this.state.tags.filter(tag => tag !== removedTag);
+ // console.log(tags);
+ this.setState({ tags });
+ }
+
+ showInput = () => {
+ this.setState({ inputVisible: true }, () => this.input.focus());
+ }
+
+ handleInputChange = (e) => {
+ this.setState({ inputValue: e.target.value });
+ }
+
+ handleInputConfirm = () => {
+ const { inputValue } = this.state;
+ let { tags } = this.state;
+ if (inputValue && tags.indexOf(inputValue) === -1) {
+ tags = [...tags, inputValue];
+ }
+ // console.log(tags);
+ this.setState({
+ tags,
+ inputVisible: false,
+ inputValue: '',
+ });
+ }
+
+ saveInputRef = input => this.input = input
+
+ render() {
+ const { tags, inputVisible, inputValue } = this.state;
+ return (
+
+ );
+ }
+}
+
+export default Step1;
\ No newline at end of file
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/add-new-app/Step2.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/add-new-app/Step2.js
new file mode 100644
index 0000000000..180da49c66
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/add-new-app/Step2.js
@@ -0,0 +1,29 @@
+/*
+ * 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"
+
+class Step2 extends React.Component {
+ render() {
+ return (
+
tttoooeeee
+ );
+ }
+}
+
+export default Step2;
\ No newline at end of file
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/add-new-app/Step3.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/add-new-app/Step3.js
new file mode 100644
index 0000000000..0fe9610f9c
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/add-new-app/Step3.js
@@ -0,0 +1,29 @@
+/*
+ * 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"
+
+class Step3 extends React.Component {
+ render() {
+ return (
+
tttoooeeee
+ );
+ }
+}
+
+export default Step3;
\ No newline at end of file
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/add-new-app/Style.less b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/add-new-app/Style.less
new file mode 100644
index 0000000000..823ca73809
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/add-new-app/Style.less
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+.stepForm {
+ max-width: 500px;
+ margin: 40px auto 0;
+}
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/add-new-app/UploadScreenshots.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/add-new-app/UploadScreenshots.js
new file mode 100644
index 0000000000..b5405e8748
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/add-new-app/UploadScreenshots.js
@@ -0,0 +1,67 @@
+/*
+ * 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 { Upload, Icon, Modal} from 'antd';
+
+
+class UploadScreenshots extends React.Component {
+ state = {
+ previewVisible: false,
+ previewImage: '',
+ fileList: [],
+ };
+
+ handleCancel = () => this.setState({ previewVisible: false });
+
+ handlePreview = (file) => {
+ this.setState({
+ previewImage: file.url || file.thumbUrl,
+ previewVisible: true,
+ });
+ };
+
+ handleChange = ({ fileList }) => this.setState({ fileList });
+
+ render() {
+ const { previewVisible, previewImage, fileList } = this.state;
+ const uploadButton = (
+
+ );
+ }
+}
+export default UploadScreenshots;
\ No newline at end of file
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/add-new-app/components/AddTagModal.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/add-new-app/components/AddTagModal.js
new file mode 100644
index 0000000000..1088ce4d8c
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/add-new-app/components/AddTagModal.js
@@ -0,0 +1,67 @@
+/*
+ * 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 { Upload, Icon, Modal} from 'antd';
+
+
+class AddTagModal extends React.Component {
+ state = {
+ previewVisible: false,
+ previewImage: '',
+ fileList: [],
+ };
+
+ handleCancel = () => this.setState({ previewVisible: false });
+
+ handlePreview = (file) => {
+ this.setState({
+ previewImage: file.url || file.thumbUrl,
+ previewVisible: true,
+ });
+ };
+
+ handleChange = ({ fileList }) => this.setState({ fileList });
+
+ render() {
+ const { previewVisible, previewImage, fileList } = this.state;
+ const uploadButton = (
+
+ );
+ }
+}
+export default AddTagModal;
\ No newline at end of file
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/apps/Apps.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/apps/Apps.js
new file mode 100644
index 0000000000..3d79af938e
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/apps/Apps.js
@@ -0,0 +1,45 @@
+/*
+ * 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 AppList from "../../../components/apps/AppList";
+
+class Apps extends React.Component {
+ routes;
+ constructor(props) {
+ super(props);
+ this.routes = props.routes;
+
+ }
+
+
+ render() {
+ const {deviceType} = this.props.match.params;
+ return (
+
+
+ {deviceType!==null && }
+
+
+
+
+ );
+ }
+}
+
+export default Apps;
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/apps/release/Release.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/apps/release/Release.js
new file mode 100644
index 0000000000..b1eb744b22
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/apps/release/Release.js
@@ -0,0 +1,131 @@
+/*
+ * 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 '../../../../App.css';
+import {Skeleton, Typography, Row, Col, Card, message, notification, Breadcrumb, Icon} from "antd";
+import ReleaseView from "../../../../components/apps/release/ReleaseView";
+import axios from "axios";
+import {withConfigContext} from "../../../../context/ConfigContext";
+import {Link} from "react-router-dom";
+
+const {Title} = Typography;
+
+class Release extends React.Component {
+ routes;
+
+ constructor(props) {
+ super(props);
+ this.routes = props.routes;
+ this.state = {
+ loading: true,
+ app: null,
+ uuid: null
+ };
+ }
+
+ componentDidMount() {
+ const {uuid, deviceType} = this.props.match.params;
+ this.fetchData(uuid);
+ this.props.changeSelectedMenuItem(deviceType);
+ }
+
+ componentDidUpdate(prevProps, prevState, snapshot) {
+ if (prevState.uuid !== this.state.uuid) {
+ const {uuid, deviceType} = this.props.match.params;
+ this.fetchData(uuid);
+ this.props.changeSelectedMenuItem(deviceType);
+ }
+ }
+
+ fetchData = (uuid) => {
+ const config = this.props.context;
+
+ //send request to the invoker
+ axios.get(
+ window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/applications/" + uuid,
+ ).then(res => {
+ if (res.status === 200) {
+ let app = res.data.data;
+
+ this.setState({
+ app: app,
+ loading: false,
+ uuid: uuid
+ })
+ }
+
+ }).catch((error) => {
+ console.log(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 + '/store/login';
+ } else {
+ notification["error"]({
+ message: "There was a problem",
+ duration: 0,
+ description:
+ "Error occurred while trying to load releases.",
+ });
+ }
+
+ this.setState({loading: false});
+ });
+ };
+
+ render() {
+ const {app, loading} = this.state;
+ const {deviceType} = this.props.match.params;
+
+ let content = No Releases Found;
+ let appName = "loading...";
+
+ if (app != null && app.applicationReleases.length !== 0) {
+ content = ;
+ appName = app.name;
+ }
+
+ return (
+
+ );
+ }
+}
+
+
+export default withConfigContext(Release);
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/logout/Logout.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/logout/Logout.js
new file mode 100644
index 0000000000..35068cc581
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/dashboard/logout/Logout.js
@@ -0,0 +1,80 @@
+/*
+ * 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 {notification, Menu, Icon} from 'antd';
+import axios from 'axios';
+import {withConfigContext} from "../../../context/ConfigContext";
+
+/*
+This class for call the logout api by sending request
+ */
+class Logout extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ inValid: false,
+ loading: false
+ };
+ }
+ /*
+ This function call the logout api when the request is success
+ */
+ handleSubmit = () => {
+
+ const thisForm = this;
+ const config = this.props.context;
+
+ thisForm.setState({
+ inValid: false
+ });
+
+ axios.post(window.location.origin + config.serverConfig.logoutUri
+ ).then(res => {
+ //if the api call status is correct then user will logout and then it goes to login page
+ if (res.status === 200) {
+ window.location = window.location.origin + "/store/login";
+ }
+ }).catch(function (error) {
+
+ if (error.hasOwnProperty("response") && error.response.status === 400) {
+ thisForm.setState({
+ inValid: true
+ });
+ } else {
+ notification["error"]({
+ message: "There was a problem",
+ duration: 0,
+ description:
+ "Error occurred while trying to logout.",
+ });
+ }
+ });
+ };
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+export default withConfigContext(Logout);
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/serviceWorker.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/serviceWorker.js
new file mode 100644
index 0000000000..249177c0b0
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/serviceWorker.js
@@ -0,0 +1,153 @@
+/*
+ * 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.
+ */
+
+// This optional code is used to register a service worker.
+// register() is not called by default.
+
+// This lets the app load faster on subsequent visits in production, and gives
+// it offline capabilities. However, it also means that developers (and users)
+// will only see deployed updates on subsequent visits to a page, after all the
+// existing tabs open on the page have been closed, since previously cached
+// resources are updated in the background.
+
+// To learn more about the benefits of this model and instructions on how to
+// opt-in, read https://bit.ly/CRA-PWA
+
+const isLocalhost = Boolean(
+ window.location.hostname === 'localhost' ||
+ // [::1] is the IPv6 localhost address.
+ window.location.hostname === '[::1]' ||
+ // 127.0.0.1/8 is considered localhost for IPv4.
+ window.location.hostname.match(
+ /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
+ )
+);
+
+export function register(config) {
+ if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
+ // The URL constructor is available in all browsers that support SW.
+ const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
+ if (publicUrl.origin !== window.location.origin) {
+ // Our service worker won't work if PUBLIC_URL is on a different origin
+ // from what our page is served on. This might happen if a CDN is used to
+ // serve assets; see https://github.com/facebook/create-react-app/issues/2374
+ return;
+ }
+
+ window.addEventListener('load', () => {
+ const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
+
+ if (isLocalhost) {
+ // This is running on localhost. Let's check if a service worker still exists or not.
+ checkValidServiceWorker(swUrl, config);
+
+ // Add some additional logging to localhost, pointing developers to the
+ // service worker/PWA documentation.
+ navigator.serviceWorker.ready.then(() => {
+ console.log(
+ 'This web app is being served cache-first by a service ' +
+ 'worker. To learn more, visit https://bit.ly/CRA-PWA'
+ );
+ });
+ } else {
+ // Is not localhost. Just register service worker
+ registerValidSW(swUrl, config);
+ }
+ });
+ }
+}
+
+function registerValidSW(swUrl, config) {
+ navigator.serviceWorker
+ .register(swUrl)
+ .then(registration => {
+ registration.onupdatefound = () => {
+ const installingWorker = registration.installing;
+ if (installingWorker == null) {
+ return;
+ }
+ installingWorker.onstatechange = () => {
+ if (installingWorker.state === 'installed') {
+ if (navigator.serviceWorker.controller) {
+ // At this point, the updated precached content has been fetched,
+ // but the previous service worker will still serve the older
+ // content until all client tabs are closed.
+ console.log(
+ 'New content is available and will be used when all ' +
+ 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
+ );
+
+ // Execute callback
+ if (config && config.onUpdate) {
+ config.onUpdate(registration);
+ }
+ } else {
+ // At this point, everything has been precached.
+ // It's the perfect time to display a
+ // "Content is cached for offline use." message.
+ console.log('Content is cached for offline use.');
+
+ // Execute callback
+ if (config && config.onSuccess) {
+ config.onSuccess(registration);
+ }
+ }
+ }
+ };
+ };
+ })
+ .catch(error => {
+ console.error('Error during service worker registration:', error);
+ });
+}
+
+function checkValidServiceWorker(swUrl, config) {
+ // Check if the service worker can be found. If it can't reload the page.
+ fetch(swUrl)
+ .then(response => {
+ // Ensure service worker exists, and that we really are getting a JS file.
+ const contentType = response.headers.get('content-type');
+ if (
+ response.status === 404 ||
+ (contentType != null && contentType.indexOf('javascript') === -1)
+ ) {
+ // No service worker found. Probably a different app. Reload the page.
+ navigator.serviceWorker.ready.then(registration => {
+ registration.unregister().then(() => {
+ window.location.reload();
+ });
+ });
+ } else {
+ // Service worker found. Proceed as normal.
+ registerValidSW(swUrl, config);
+ }
+ })
+ .catch(() => {
+ console.log(
+ 'No internet connection found. App is running in offline mode.'
+ );
+ });
+}
+
+export function unregister() {
+ if ('serviceWorker' in navigator) {
+ navigator.serviceWorker.ready.then(registration => {
+ registration.unregister();
+ });
+ }
+}
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/webpack.config.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/webpack.config.js
new file mode 100644
index 0000000000..2f3874ea21
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/webpack.config.js
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
+ *
+ * WSO2 Inc. 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.
+ */
+var path = require('path');
+const HtmlWebPackPlugin = require("html-webpack-plugin");
+const MiniCssExtractPlugin = require("mini-css-extract-plugin");
+const configurations = require("./public/conf/config.json");
+
+const config = {
+ devtool: "source-map",
+ output: {
+ publicPath: '/store/'
+ },
+ watch: false,
+ resolve: {
+ alias: {
+ AppData: path.resolve(__dirname, 'source/src/app/common/'),
+ AppComponents: path.resolve(__dirname, 'source/src/app/components/')
+ },
+ extensions: ['.jsx', '.js', '.ttf', '.woff', '.woff2', '.svg']
+ },
+ module: {
+ rules: [
+ {
+ test: /\.(js|jsx)$/,
+ exclude: /node_modules/,
+ use: [
+ {
+ loader: 'babel-loader'
+ }
+ ]
+ },
+ {
+ test: /\.html$/,
+ use: [
+ {
+ loader: "html-loader",
+ options: { minimize: true }
+ }
+ ]
+ },
+ {
+ test: /\.css$/,
+ use: [MiniCssExtractPlugin.loader, "css-loader"]
+ },
+ {
+ test: /\.scss$/,
+ use: [
+ MiniCssExtractPlugin.loader,
+ "css-loader",
+ "postcss-loader",
+ "sass-loader"
+ ]
+ },
+ {
+ test: /\.scss$/,
+ use: [ 'style-loader', 'scss-loader' ]
+ },
+ {
+ test: /\.less$/,
+ use: [
+ {
+ loader: "style-loader"
+ },
+ {
+ loader: "css-loader",
+ },
+ {
+ loader: "less-loader",
+ options: {
+ modifyVars: {
+ 'primary-color': configurations.theme.primaryColor,
+ 'link-color': configurations.theme.primaryColor,
+ },
+ javascriptEnabled: true,
+ },
+ }
+ ]
+ },
+ {
+ test: /\.(woff|woff2|eot|ttf|svg)$/,
+ loader: 'url-loader?limit=100000',
+ },
+ {
+ test: /\.(png|jpe?g)/i,
+ use: [
+ {
+ loader: "url-loader",
+ options: {
+ name: "./img/[name].[ext]",
+ limit: 10000
+ }
+ },
+ {
+ loader: "img-loader"
+ }
+ ]
+ }
+ ]
+ },
+ plugins: [
+ new HtmlWebPackPlugin({
+ template: "./src/index.html",
+ filename: "./index.html"
+ }),
+ new MiniCssExtractPlugin({
+ filename: "[name].css",
+ chunkFilename: "[id].css"
+ })
+ ],
+ externals: {
+ 'Config': JSON.stringify(require('./public/conf/config.json'))
+ }
+};
+
+if (process.env.NODE_ENV === "development") {
+ config.watch = true;
+}
+
+module.exports = config;
diff --git a/components/device-mgt/io.entgra.device.mgt.ui/src/main/webapp/WEB-INF/web.xml b/components/device-mgt/io.entgra.device.mgt.ui/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000000..bd9f023aa9
--- /dev/null
+++ b/components/device-mgt/io.entgra.device.mgt.ui/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,25 @@
+
+
+
+ Store-Webapp
+
+ 404
+ /index.html
+
+
diff --git a/components/device-mgt/pom.xml b/components/device-mgt/pom.xml
index e73c1138bb..c17c4ab520 100644
--- a/components/device-mgt/pom.xml
+++ b/components/device-mgt/pom.xml
@@ -41,6 +41,7 @@
org.wso2.carbon.device.mgt.analytics.data.publisherorg.wso2.carbon.device.mgt.url.printerorg.wso2.carbon.device.mgt.analytics.wsproxy
+ io.entgra.device.mgt.ui
diff --git a/features/device-mgt/org.wso2.carbon.device.mgt.ui.feature/src/main/resources/p2.inf b/features/device-mgt/org.wso2.carbon.device.mgt.ui.feature/src/main/resources/p2.inf
index 89445081ba..f2fa5b0aaa 100644
--- a/features/device-mgt/org.wso2.carbon.device.mgt.ui.feature/src/main/resources/p2.inf
+++ b/features/device-mgt/org.wso2.carbon.device.mgt.ui.feature/src/main/resources/p2.inf
@@ -3,4 +3,5 @@ org.eclipse.equinox.p2.touchpoint.natives.mkdir(path:${installFolder}/../../depl
org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/org.wso2.carbon.device.mgt.ui_${feature.version}/jaggeryapps/devicemgt-cdmf,target:${installFolder}/../../deployment/server/jaggeryapps/devicemgt-cdmf,overwrite:true);\
org.eclipse.equinox.p2.touchpoint.natives.mkdir(path:${installFolder}/../../deployment/server/jaggeryapps/uuf-template-app);\
org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/org.wso2.carbon.device.mgt.ui_${feature.version}/jaggeryapps/uuf-template-app,target:${installFolder}/../../deployment/server/jaggeryapps/uuf-template-app,overwrite:true);\
-org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/org.wso2.carbon.device.mgt.ui_${feature.version}/jaggery-modules/utils/,target:${installFolder}/../../modules/utils,overwrite:true);\
\ No newline at end of file
+org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/org.wso2.carbon.device.mgt.ui_${feature.version}/jaggery-modules/utils/,target:${installFolder}/../../modules/utils,overwrite:true);\
+org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/org.wso2.carbon.device.mgt.ui_${feature.version}/webapps/store.war,target:${installFolder}/../../deployment/server/webapps/store.war,overwrite:true);\