Merge branch 'feature/appm-publisher/delete-app' into 'master'

Add app delete and retire functionality to APPM UI

Closes product-iots#495

See merge request entgra/carbon-device-mgt!523
merge-requests/532/head
Dharmakeerthi Lasantha 5 years ago
commit f2dfed1c6c

@ -11,6 +11,7 @@
"license": "Apache License 2.0", "license": "Apache License 2.0",
"dependencies": { "dependencies": {
"@ant-design/dark-theme": "^0.2.2", "@ant-design/dark-theme": "^0.2.2",
"@ant-design/icons": "^4.0.6",
"@babel/polyfill": "^7.6.0", "@babel/polyfill": "^7.6.0",
"acorn": "^6.2.0", "acorn": "^6.2.0",
"antd": "^3.23.6", "antd": "^3.23.6",
@ -25,6 +26,7 @@
"fetch": "^1.1.0", "fetch": "^1.1.0",
"imagemin": "^6.1.0", "imagemin": "^6.1.0",
"keymirror": "^0.1.1", "keymirror": "^0.1.1",
"lodash.debounce": "latest",
"rc-tween-one": "^2.4.1", "rc-tween-one": "^2.4.1",
"react-d3-graph": "^2.1.0", "react-d3-graph": "^2.1.0",
"react-highlight-words": "^0.16.0", "react-highlight-words": "^0.16.0",
@ -39,8 +41,7 @@
"redux-thunk": "^2.3.0", "redux-thunk": "^2.3.0",
"shade-blend-color": "^1.0.0", "shade-blend-color": "^1.0.0",
"storm-react-diagrams": "^5.2.1", "storm-react-diagrams": "^5.2.1",
"typescript": "^3.6.4", "typescript": "^3.6.4"
"lodash.debounce": "latest"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.5.0", "@babel/core": "^7.5.0",

@ -0,0 +1,102 @@
/*
* 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 { DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
import { Button, Modal, notification, Tooltip } from 'antd';
import axios from 'axios';
import { handleApiError } from '../../../../../../../../../../../services/utils/errorHandler';
import { withConfigContext } from '../../../../../../../../../../../components/ConfigContext';
import { withRouter } from 'react-router-dom';
import '../../styles.css';
const { confirm } = Modal;
class DeleteApp extends React.Component {
showModal = () => {
confirm({
title: 'Are you sure you want to delete this app?',
icon: <ExclamationCircleOutlined style={{ color: 'red' }} />,
content:
'You are trying to delete the entire application, by performing this operation all ' +
'app data, app release data, and all release artifacts will be deleted ' +
'permanently. Further, please note, this process cannot be undone.',
okText: 'Yes',
okType: 'danger',
cancelText: 'No',
onOk: this.deleteRelease,
});
};
deleteRelease = () => {
const config = this.props.context;
const apiUrl =
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/admin/applications/' +
this.props.id;
axios
.delete(apiUrl)
.then(res => {
if (res.status === 200) {
notification.success({
message: 'Successfully deleted the app',
});
this.props.history.push('/publisher');
}
})
.catch(error => {
handleApiError(
error,
'Something Went wrong when trying to delete the app, Please contact the administrator',
);
this.setState({
loading: false,
});
});
};
render() {
return (
<div className="delete-app">
{this.props.isDeletableApp && (
<Button
type="link"
className="btn-view-more"
onClick={this.showModal}
>
<DeleteOutlined /> Delete
</Button>
)}
{!this.props.isDeletableApp && (
<Tooltip
placement="leftTop"
title="All application releases should be in a deletable state."
>
<Button type="link" disabled={true} className="btn-view-more">
<DeleteOutlined /> Delete
</Button>
</Tooltip>
)}
</div>
);
}
}
export default withConfigContext(withRouter(DeleteApp));

@ -17,10 +17,12 @@
*/ */
import React from 'react'; import React from 'react';
import { Button, Modal, notification, Spin } from 'antd'; import { Button, Modal, notification, Popover, Spin, Tooltip } from 'antd';
import axios from 'axios'; import axios from 'axios';
import { withConfigContext } from '../../../../../../../../../../../components/ConfigContext'; import { withConfigContext } from '../../../../../../../../../../../components/ConfigContext';
import { handleApiError } from '../../../../../../../../../../../services/utils/errorHandler'; import { handleApiError } from '../../../../../../../../../../../services/utils/errorHandler';
import { SettingOutlined, QuestionCircleOutlined } from '@ant-design/icons';
import '../../styles.css';
class ManagedConfigurationsIframe extends React.Component { class ManagedConfigurationsIframe extends React.Component {
constructor(props) { constructor(props) {
@ -29,6 +31,7 @@ class ManagedConfigurationsIframe extends React.Component {
this.state = { this.state = {
visible: false, visible: false,
loading: false, loading: false,
isHintVisible: false,
}; };
} }
@ -201,26 +204,69 @@ class ManagedConfigurationsIframe extends React.Component {
}); });
}; };
handleHintVisibleChange = visible => {
this.setState({ isHintVisible: visible });
};
render() { render() {
return ( return (
<div> <div>
{this.props.isEnabled && (
<Button <Button
size="small" type="link"
type="primary" className="btn-view-more"
icon="setting"
onClick={this.showModal} onClick={this.showModal}
> >
Manage <SettingOutlined /> Managed Configurations
</Button>
)}
{!this.props.isEnabled && (
<Tooltip
placement="leftTop"
title="Managed configurations are available only with android enterprise apps."
>
<Button type="link" className="btn-view-more" disabled={true}>
<SettingOutlined /> Managed Configurations
</Button> </Button>
</Tooltip>
)}
<Modal <Modal
visible={this.state.visible} visible={this.state.visible}
onOk={this.handleOk} onOk={this.handleOk}
onCancel={this.handleCancel} onCancel={this.handleCancel}
footer={null} footer={null}
> >
<div>
<Popover
title={null}
trigger="click"
visible={this.state.isHintVisible}
content={
<p>
If you are developing apps for the enterprise market, you may
need to satisfy particular requirements set by a
organization&quot;s policies. Managed configurations,
previously previously known as application restrictions, allow
the organization&quot;s IT admin to remotely specify settings
for apps. This capability is particularly useful for
organization-approved apps deployed to a work profile.
</p>
}
onVisibleChange={this.handleHintVisibleChange}
overlayStyle={{ width: 300 }}
>
<Button
size="large"
type="link"
style={{ marginTop: -56, fontSize: '1.2em' }}
>
<QuestionCircleOutlined />
</Button>
</Popover>
<Spin spinning={this.state.loading}> <Spin spinning={this.state.loading}>
<div id="manage-config-iframe-container"></div> <div id="manage-config-iframe-container"></div>
</Spin> </Spin>
</div>
</Modal> </Modal>
</div> </div>
); );

@ -0,0 +1,106 @@
/*
* 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 {
EyeInvisibleOutlined,
ExclamationCircleOutlined,
} from '@ant-design/icons';
import { Button, Modal, notification, Tooltip } from 'antd';
import axios from 'axios';
import { handleApiError } from '../../../../../../../../../../../services/utils/errorHandler';
import { withConfigContext } from '../../../../../../../../../../../components/ConfigContext';
import { withRouter } from 'react-router-dom';
import '../../styles.css';
const { confirm } = Modal;
class RetireApp extends React.Component {
showModal = () => {
confirm({
title: 'Are you sure you want to retire this app?',
icon: <ExclamationCircleOutlined style={{ color: 'red' }} />,
content:
'You are trying to retire the entire application, by performing this operation, ' +
'you will not see the app data or app release data on either publisher or store. ' +
'Further, please note, this process cannot be undone.',
okText: 'Yes',
okType: 'danger',
cancelText: 'No',
onOk: this.hideApp,
});
};
hideApp = () => {
const config = this.props.context;
const apiUrl =
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/admin/applications/retire/' +
this.props.id;
axios
.put(apiUrl)
.then(res => {
if (res.status === 200) {
notification.success({
message: 'Successfully hided the app',
});
this.props.history.push('/publisher');
}
})
.catch(error => {
console.log(error);
handleApiError(
error,
'Something Went wrong when trying to reitre the app, Please contact the administrator',
);
this.setState({
loading: false,
});
});
};
render() {
return (
<div>
{this.props.isHideableApp && (
<Button
type="link"
onClick={this.showModal}
className="btn-view-more"
>
<EyeInvisibleOutlined /> Retire
</Button>
)}
{!this.props.isHideableApp && (
<Tooltip
placement="leftTop"
title="All releases should be in retired state"
>
<Button type="link" disabled={true} className="btn-view-more">
<EyeInvisibleOutlined /> Retire
</Button>
</Tooltip>
)}
</div>
);
}
}
export default withConfigContext(withRouter(RetireApp));

@ -33,6 +33,8 @@ import {
Card, Card,
Badge, Badge,
Tooltip, Tooltip,
Dropdown,
Menu,
} from 'antd'; } from 'antd';
import DetailedRating from '../../../../DetailedRating'; import DetailedRating from '../../../../DetailedRating';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
@ -46,6 +48,9 @@ import ManagedConfigurationsIframe from './components/ManagedConfigurationsIfram
import { handleApiError } from '../../../../../../../../../services/utils/errorHandler'; import { handleApiError } from '../../../../../../../../../services/utils/errorHandler';
import Authorized from '../../../../../../../../../components/Authorized/Authorized'; import Authorized from '../../../../../../../../../components/Authorized/Authorized';
import { isAuthorized } from '../../../../../../../../../services/utils/authorizationHandler'; import { isAuthorized } from '../../../../../../../../../services/utils/authorizationHandler';
import { MoreOutlined } from '@ant-design/icons';
import DeleteApp from './components/DeleteApp';
import RetireApp from './components/RetireApp';
const { Meta } = Card; const { Meta } = Card;
const { Text, Title } = Typography; const { Text, Title } = Typography;
@ -485,6 +490,7 @@ class AppDetailsDrawer extends React.Component {
if (app == null) { if (app == null) {
return null; return null;
} }
const { id } = this.props.app;
let avatar = null; let avatar = null;
@ -527,6 +533,36 @@ class AppDetailsDrawer extends React.Component {
visible={visible} visible={visible}
> >
<Spin spinning={loading} delay={500}> <Spin spinning={loading} delay={500}>
<div className="app-details-drawer">
<Dropdown
trigger={['click']}
overlay={
<Menu>
<Menu.Item key="0">
<DeleteApp id={id} isDeletableApp={app.isDeletableApp} />
</Menu.Item>
<Menu.Item key="1">
<RetireApp id={id} isHideableApp={app.isHideableApp} />
</Menu.Item>
{config.androidEnterpriseToken !== null &&
isAuthorized(
config.user,
'/permission/admin/device-mgt/enterprise/user/modify',
) && (
<Menu.Item key="2">
<ManagedConfigurationsIframe
isEnabled={app.isAndroidEnterpriseApp}
style={{ paddingTop: 16 }}
packageName={app.packageName}
/>
</Menu.Item>
)}
</Menu>
}
>
<MoreOutlined style={{ fontSize: 34 }} />
</Dropdown>
</div>
<div style={{ textAlign: 'center' }}> <div style={{ textAlign: 'center' }}>
{avatar} {avatar}
<Authorized <Authorized
@ -540,38 +576,6 @@ class AppDetailsDrawer extends React.Component {
/> />
</div> </div>
<Divider /> <Divider />
{/* display manage config button only if the app is public android app*/}
{app.isAndroidEnterpriseApp &&
config.androidEnterpriseToken !== null && (
<Authorized
permission="/permission/admin/device-mgt/enterprise/user/modify"
yes={
<div>
<div>
<Text strong={true}>Set up managed configurations</Text>
</div>
<div style={{ paddingTop: 16 }}>
<Text>
If you are developing apps for the enterprise market,
you may need to satisfy particular requirements set by
a organization&apos;s policies. Managed
configurations, previously known as application
restrictions, allow the organization&apos;s IT admin
to remotely specify settings for apps. This capability
is particularly useful for organization-approved apps
deployed to a work profile.
</Text>
</div>
<br />
<ManagedConfigurationsIframe
style={{ paddingTop: 16 }}
packageName={app.packageName}
/>
<Divider dashed={true} />
</div>
}
/>
)}
<Text strong={true}>Releases </Text> <Text strong={true}>Releases </Text>
<div className="releases-details"> <div className="releases-details">
<List <List

Loading…
Cancel
Save