mirror of
https://repository.entgra.net/community/device-mgt-core.git
synced 2025-10-06 02:01:45 +00:00
Add app delete and retire functionality to APPM UI
This commit is contained in:
parent
c953c41872
commit
2d0277de3a
@ -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>
|
||||||
<Button
|
{this.props.isEnabled && (
|
||||||
size="small"
|
<Button
|
||||||
type="primary"
|
type="link"
|
||||||
icon="setting"
|
className="btn-view-more"
|
||||||
onClick={this.showModal}
|
onClick={this.showModal}
|
||||||
>
|
>
|
||||||
Manage
|
<SettingOutlined /> Managed Configurations
|
||||||
</Button>
|
</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>
|
||||||
|
</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}
|
||||||
>
|
>
|
||||||
<Spin spinning={this.state.loading}>
|
<div>
|
||||||
<div id="manage-config-iframe-container"></div>
|
<Popover
|
||||||
</Spin>
|
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"s policies. Managed configurations,
|
||||||
|
previously previously known as application restrictions, allow
|
||||||
|
the organization"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}>
|
||||||
|
<div id="manage-config-iframe-container"></div>
|
||||||
|
</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's policies. Managed
|
|
||||||
configurations, previously known as application
|
|
||||||
restrictions, allow the organization'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
|
||||||
|
|||||||
@ -35,3 +35,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.app-details-drawer {
|
||||||
|
text-align: right
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-view-more {
|
||||||
|
color: rgba(0, 0, 0, 0.65) !important;
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user