Add scheduled install/uninstall feature to APPM store ui

feature/appm-store/pbac
Jayasanka 5 years ago
parent 2e690892d5
commit ff875c3330

@ -42,7 +42,7 @@ class ReleaseView extends React.Component {
} }
} }
appOperation = (type, payload, operation) => { appOperation = (type, payload, operation, timestamp=null) => {
const config = this.props.context; const config = this.props.context;
const release = this.props.app.applicationReleases[0]; const release = this.props.app.applicationReleases[0];
const {uuid} = release; const {uuid} = release;
@ -50,7 +50,11 @@ class ReleaseView extends React.Component {
this.setState({ this.setState({
loading: true, loading: true,
}); });
const url = window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/subscription/" + uuid + "/" + type + "/" + operation; let url = window.location.origin+ config.serverConfig.invoker.uri +
config.serverConfig.invoker.store + "/subscription/" + uuid + "/" + type + "/" + operation;
if(timestamp!= null){
url += `?timestamp=${timestamp}`;
}
axios.post( axios.post(
url, url,
payload, payload,
@ -61,7 +65,8 @@ class ReleaseView extends React.Component {
if (res.status === 200) { if (res.status === 200) {
this.setState({ this.setState({
loading: false, loading: false,
appInstallModalVisible: false appInstallModalVisible: false,
appUnInstallModalVisible: false,
}); });
notification["success"]({ notification["success"]({
message: 'Done!', message: 'Done!',

@ -18,13 +18,14 @@
import React from "react"; import React from "react";
import axios from "axios"; import axios from "axios";
import {Button, message, notification, Table, Typography} from "antd"; import {Button, message, DatePicker, Table, Typography} from "antd";
import TimeAgo from 'javascript-time-ago' import TimeAgo from 'javascript-time-ago'
// Load locale-specific relative date/time formatting rules. // Load locale-specific relative date/time formatting rules.
import en from 'javascript-time-ago/locale/en' import en from 'javascript-time-ago/locale/en'
import {withConfigContext} from "../../../../context/ConfigContext"; import {withConfigContext} from "../../../../context/ConfigContext";
import {handleApiError} from "../../../../js/Utils"; import {handleApiError} from "../../../../js/Utils";
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
const {Text} = Typography; const {Text} = Typography;
const columns = [ const columns = [
@ -109,7 +110,9 @@ class DeviceInstall extends React.Component {
data: [], data: [],
pagination: {}, pagination: {},
loading: false, loading: false,
selectedRows: [] selectedRows: [],
scheduledTime: null,
isScheduledInstallVisible: false
}; };
} }
@ -147,7 +150,7 @@ class DeviceInstall extends React.Component {
if (deviceType !== 'ANY') { if (deviceType !== 'ANY') {
extraParams.type = deviceType; extraParams.type = deviceType;
} }
// note: encode with '%26' not '&' // note: encode with '%26' not '&'
const encodedExtraParams = Object.keys(extraParams).map(key => key + '=' + extraParams[key]).join('&'); const encodedExtraParams = Object.keys(extraParams).map(key => key + '=' + extraParams[key]).join('&');
@ -163,11 +166,10 @@ class DeviceInstall extends React.Component {
data: res.data.data.devices, data: res.data.data.devices,
pagination, pagination,
}); });
} }
}).catch((error) => { }).catch((error) => {
handleApiError(error,"Error occurred while trying to load devices."); handleApiError(error, "Error occurred while trying to load devices.");
this.setState({loading: false}); this.setState({loading: false});
}); });
}; };
@ -187,7 +189,7 @@ class DeviceInstall extends React.Component {
}); });
}; };
install = () => { install = (timestamp=null) => {
const {selectedRows} = this.state; const {selectedRows} = this.state;
const payload = []; const payload = [];
selectedRows.map(device => { selectedRows.map(device => {
@ -196,11 +198,11 @@ class DeviceInstall extends React.Component {
type: device.type type: device.type
}); });
}); });
this.props.onInstall("devices", payload, "install"); this.props.onInstall("devices", payload, "install",timestamp);
}; };
render() { render() {
const {data, pagination, loading, selectedRows} = this.state; const {data, pagination, loading, selectedRows, scheduledTime,isScheduledInstallVisible} = this.state;
return ( return (
<div> <div>
<Text> <Text>
@ -224,12 +226,7 @@ class DeviceInstall extends React.Component {
rowSelection={this.rowSelection} rowSelection={this.rowSelection}
scroll={{x: 1000}} scroll={{x: 1000}}
/> />
<div style={{paddingTop: 10, textAlign: "right"}}> <InstallModalFooter type="Install" operation={this.install} disabled={selectedRows.length === 0}/>
<Button disabled={selectedRows.length === 0} htmlType="button" type="primary"
onClick={this.install}>
Install
</Button>
</div>
</div> </div>
); );
} }

@ -18,13 +18,14 @@
import React from "react"; import React from "react";
import axios from "axios"; import axios from "axios";
import {Button,Table, Typography} from "antd"; import {Button, Select, Table, Typography} from "antd";
import TimeAgo from 'javascript-time-ago' import TimeAgo from 'javascript-time-ago'
// Load locale-specific relative date/time formatting rules. // Load locale-specific relative date/time formatting rules.
import en from 'javascript-time-ago/locale/en' import en from 'javascript-time-ago/locale/en'
import {withConfigContext} from "../../../../context/ConfigContext"; import {withConfigContext} from "../../../../context/ConfigContext";
import {handleApiError} from "../../../../js/Utils"; import {handleApiError} from "../../../../js/Utils";
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
const {Text} = Typography; const {Text} = Typography;
const columns = [ const columns = [
@ -115,8 +116,8 @@ class DeviceUninstall extends React.Component {
rowSelection = { rowSelection = {
onChange: (selectedRowKeys, selectedRows) => { onChange: (selectedRowKeys, selectedRows) => {
this.setState({ this.setState({
selectedRows: selectedRows selectedRows: selectedRows
}) })
}, },
getCheckboxProps: record => ({ getCheckboxProps: record => ({
disabled: record.name === 'Disabled User', // Column configuration not to be checked disabled: record.name === 'Disabled User', // Column configuration not to be checked
@ -151,7 +152,7 @@ class DeviceUninstall extends React.Component {
const uuid = this.props.uuid; const uuid = this.props.uuid;
axios.get( axios.get(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/subscription/" + uuid + "/"+ window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/subscription/" + uuid + "/" +
"/devices?" + encodedExtraParams, "/devices?" + encodedExtraParams,
).then(res => { ).then(res => {
if (res.status === 200) { if (res.status === 200) {
@ -163,7 +164,7 @@ class DeviceUninstall extends React.Component {
}); });
} }
}).catch((error) => { }).catch((error) => {
handleApiError(error,"Error occurred while trying to load devices."); handleApiError(error, "Error occurred while trying to load devices.");
this.setState({loading: false}); this.setState({loading: false});
}); });
}; };
@ -172,59 +173,54 @@ class DeviceUninstall extends React.Component {
const pager = {...this.state.pagination}; const pager = {...this.state.pagination};
pager.current = pagination.current; pager.current = pagination.current;
this.setState({ this.setState({
pagination: pager, pagination: pager,
}); });
this.fetch({ this.fetch({
results: pagination.pageSize, results: pagination.pageSize,
page: pagination.current, page: pagination.current,
sortField: sorter.field, sortField: sorter.field,
sortOrder: sorter.order, sortOrder: sorter.order,
...filters, ...filters,
}); });
}; };
uninstall = () => { uninstall = (timestamp = null) => {
const {selectedRows} = this.state; const {selectedRows} = this.state;
const payload = []; const payload = [];
selectedRows.map(device => { selectedRows.map(device => {
payload.push({ payload.push({
id: device.deviceIdentifier, id: device.deviceIdentifier,
type: device.type type: device.type
}); });
}); });
this.props.onUninstall("devices", payload, "uninstall"); this.props.onUninstall("devices", payload, "uninstall", null);
}; };
render() { render() {
const {data, pagination, loading, selectedRows} = this.state; const {data, pagination, loading, selectedRows} = this.state;
return ( return (
<div> <div>
<Text> <Text>
Start uninstalling the application for devices by selecting the corresponding devices. Start uninstalling the application for devices by selecting the corresponding devices.
Select uninstall to automatically start uninstalling the application for the respective devices. Select uninstall to automatically start uninstalling the application for the respective devices.
</Text> </Text>
<Table <Table
style={{paddingTop: 20}} style={{paddingTop: 20}}
columns={columns} columns={columns}
rowKey={record => record.deviceIdentifier} rowKey={record => record.deviceIdentifier}
dataSource={data} dataSource={data}
pagination={{ pagination={{
...pagination, ...pagination,
size: "small", size: "small",
showTotal: (total, range) => `showing ${range[0]}-${range[1]} of ${total} devices` showTotal: (total, range) => `showing ${range[0]}-${range[1]} of ${total} devices`
}} }}
loading={loading} loading={loading}
onChange={this.handleTableChange} onChange={this.handleTableChange}
rowSelection={this.rowSelection} rowSelection={this.rowSelection}
scroll={{x: 1000}} scroll={{x: 1000}}
/> />
<div style={{paddingTop: 10, textAlign: "right"}}> <InstallModalFooter type="Uninstall" operation={this.uninstall} disabled={selectedRows.length === 0}/>
<Button disabled={selectedRows.length === 0} htmlType="button" type="primary" </div>
onClick={this.uninstall}>
Uninstall
</Button>
</div>
</div>
); );
} }
} }

@ -22,6 +22,7 @@ import debounce from 'lodash.debounce';
import axios from "axios"; import axios from "axios";
import {withConfigContext} from "../../../../context/ConfigContext"; import {withConfigContext} from "../../../../context/ConfigContext";
import {handleApiError} from "../../../../js/Utils"; import {handleApiError} from "../../../../js/Utils";
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
const {Text} = Typography; const {Text} = Typography;
const {Option} = Select; const {Option} = Select;
@ -112,9 +113,7 @@ class GroupInstall extends React.Component {
<Option key={d.value}>{d.text}</Option> <Option key={d.value}>{d.text}</Option>
))} ))}
</Select> </Select>
<div style={{paddingTop:10, textAlign:"right"}}> <InstallModalFooter type="Install" operation={this.install} disabled={value.length===0}/>
<Button disabled={value.length===0} htmlType="button" type="primary" onClick={this.install}>Install</Button>
</div>
</div> </div>
); );
} }

@ -22,6 +22,7 @@ import debounce from 'lodash.debounce';
import axios from "axios"; import axios from "axios";
import {withConfigContext} from "../../../../context/ConfigContext"; import {withConfigContext} from "../../../../context/ConfigContext";
import {handleApiError} from "../../../../js/Utils"; import {handleApiError} from "../../../../js/Utils";
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
const {Text} = Typography; const {Text} = Typography;
const {Option} = Select; const {Option} = Select;
@ -75,19 +76,19 @@ class GroupUninstall extends React.Component {
handleChange = value => { handleChange = value => {
this.setState({ this.setState({
value, value,
data: [], data: [],
fetching: false, fetching: false,
}); });
}; };
install = () =>{ uninstall = (timestamp=null) =>{
const {value} = this.state; const {value} = this.state;
const data = []; const data = [];
value.map(val=>{ value.map(val=>{
data.push(val.key); data.push(val.key);
}); });
this.props.onInstall("group", data, "uninstall"); this.props.onUninstall("group", data, "uninstall",null);
}; };
render() { render() {
@ -108,15 +109,12 @@ class GroupUninstall extends React.Component {
filterOption={false} filterOption={false}
onSearch={this.fetchUser} onSearch={this.fetchUser}
onChange={this.handleChange} onChange={this.handleChange}
style={{width: '100%'}} style={{width: '100%'}}>
>
{data.map(d => ( {data.map(d => (
<Option key={d.value}>{d.text}</Option> <Option key={d.value}>{d.text}</Option>
))} ))}
</Select> </Select>
<div style={{paddingTop:10, textAlign:"right"}}> <InstallModalFooter type="Uninstall" operation={this.uninstall} disabled={value.length===0}/>
<Button disabled={value.length===0} htmlType="button" type="primary" onClick={this.install}>Install</Button>
</div>
</div> </div>
); );
} }

@ -22,6 +22,7 @@ import debounce from 'lodash.debounce';
import axios from "axios"; import axios from "axios";
import {withConfigContext} from "../../../../context/ConfigContext"; import {withConfigContext} from "../../../../context/ConfigContext";
import {handleApiError} from "../../../../js/Utils"; import {handleApiError} from "../../../../js/Utils";
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
const {Text} = Typography; const {Text} = Typography;
const {Option} = Select; const {Option} = Select;
@ -79,13 +80,13 @@ class RoleInstall extends React.Component {
}); });
}; };
install = () =>{ install = (timestamp=null) =>{
const {value} = this.state; const {value} = this.state;
const data = []; const data = [];
value.map(val=>{ value.map(val=>{
data.push(val.key); data.push(val.key);
}); });
this.props.onInstall("role", data, "install"); this.props.onInstall("role", data, "install", timestamp);
}; };
render() { render() {
@ -112,9 +113,7 @@ class RoleInstall extends React.Component {
<Option key={d.value}>{d.text}</Option> <Option key={d.value}>{d.text}</Option>
))} ))}
</Select> </Select>
<div style={{paddingTop:10, textAlign:"right"}}> <InstallModalFooter type="Install" operation={this.install} disabled={value.length===0}/>
<Button disabled={value.length===0} htmlType="button" type="primary" onClick={this.install}>Install</Button>
</div>
</div> </div>
); );
} }

@ -22,6 +22,7 @@ import debounce from 'lodash.debounce';
import axios from "axios"; import axios from "axios";
import {withConfigContext} from "../../../../context/ConfigContext"; import {withConfigContext} from "../../../../context/ConfigContext";
import {handleApiError} from "../../../../js/Utils"; import {handleApiError} from "../../../../js/Utils";
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
const {Text} = Typography; const {Text} = Typography;
const {Option} = Select; const {Option} = Select;
@ -80,17 +81,16 @@ class RoleUninstall extends React.Component {
}); });
}; };
install = () =>{ uninstall = (timestamp=null) =>{
const {value} = this.state; const {value} = this.state;
const data = []; const data = [];
value.map(val=>{ value.map(val=>{
data.push(val.key); data.push(val.key);
}); });
this.props.onInstall("role", data, "uninstall"); this.props.onUninstall("role", data, "uninstall",null);
}; };
render() { render() {
const {fetching, data, value} = this.state; const {fetching, data, value} = this.state;
return ( return (
@ -113,9 +113,7 @@ class RoleUninstall extends React.Component {
<Option key={d.value}>{d.text}</Option> <Option key={d.value}>{d.text}</Option>
))} ))}
</Select> </Select>
<div style={{paddingTop:10, textAlign:"right"}}> <InstallModalFooter type="Uninstall" operation={this.uninstall} disabled={value.length===0}/>
<Button disabled={value.length===0} htmlType="button" type="primary" onClick={this.install}>Install</Button>
</div>
</div> </div>
); );
} }

@ -22,6 +22,7 @@ import debounce from 'lodash.debounce';
import axios from "axios"; import axios from "axios";
import {withConfigContext} from "../../../../context/ConfigContext"; import {withConfigContext} from "../../../../context/ConfigContext";
import {handleApiError} from "../../../../js/Utils"; import {handleApiError} from "../../../../js/Utils";
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
const {Text} = Typography; const {Text} = Typography;
const {Option} = Select; const {Option} = Select;
@ -81,13 +82,13 @@ class UserInstall extends React.Component {
}); });
}; };
install = () => { install = (timestamp=null) => {
const {value} = this.state; const {value} = this.state;
const data = []; const data = [];
value.map(val => { value.map(val => {
data.push(val.key); data.push(val.key);
}); });
this.props.onInstall("user", data, "install"); this.props.onInstall("user", data, "install",timestamp);
}; };
render() { render() {
@ -112,9 +113,7 @@ class UserInstall extends React.Component {
<Option key={d.value}>{d.text}</Option> <Option key={d.value}>{d.text}</Option>
))} ))}
</Select> </Select>
<div style={{paddingTop: 10, textAlign: "right"}}> <InstallModalFooter type="Install" operation={this.install} disabled={value.length===0}/>
<Button disabled={value.length===0} htmlType="button" type="primary" onClick={this.install}>Install</Button>
</div>
</div> </div>
); );
} }

@ -22,6 +22,7 @@ import debounce from 'lodash.debounce';
import axios from "axios"; import axios from "axios";
import {withConfigContext} from "../../../../context/ConfigContext"; import {withConfigContext} from "../../../../context/ConfigContext";
import {handleApiError} from "../../../../js/Utils"; import {handleApiError} from "../../../../js/Utils";
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
const {Text} = Typography; const {Text} = Typography;
const {Option} = Select; const {Option} = Select;
@ -49,9 +50,8 @@ class UserUninstall extends React.Component {
const uuid = this.props.uuid; const uuid = this.props.uuid;
axios.get( axios.get(
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.store+ "/subscription/" + uuid + "/"+ window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/subscription/" + uuid + "/" +
"/USER?", "/USER?",
).then(res => { ).then(res => {
if (res.status === 200) { if (res.status === 200) {
if (fetchId !== this.lastFetchId) { if (fetchId !== this.lastFetchId) {
@ -67,54 +67,53 @@ class UserUninstall extends React.Component {
} }
}).catch((error) => { }).catch((error) => {
handleApiError(error,"Error occurred while trying to load users."); handleApiError(error, "Error occurred while trying to load users.");
this.setState({fetching: false}); this.setState({fetching: false});
}); });
}; };
handleChange = value => { handleChange = value => {
this.setState({ this.setState({
value, value,
data: [], data: [],
fetching: false, fetching: false,
}); });
}; };
uninstall = () => { uninstall = (timestamp=null) => {
const {value} = this.state; const {value} = this.state;
const data = []; const data = [];
value.map(val => { value.map(val => {
data.push(val.key); data.push(val.key);
}); });
this.props.onUninstall("user", data, "uninstall"); this.props.onUninstall("user", data, "uninstall",null);
}; };
render() { render() {
const {fetching, data, value} = this.state; const {fetching, data, value} = this.state;
return ( return (
<div> <div>
<Text>Start uninstalling the application for one or more users by entering the corresponding user name. Select uninstall to automatically start uninstalling the application for the respective user/users. </Text> <Text>Start uninstalling the application for one or more users by entering the corresponding user name.
<p>Select users</p> Select uninstall to automatically start uninstalling the application for the respective
<Select user/users. </Text>
mode="multiple" <p>Select users</p>
labelInValue <Select
value={value} mode="multiple"
placeholder="Enter the username" labelInValue
notFoundContent={fetching ? <Spin size="small"/> : null} value={value}
filterOption={false} placeholder="Enter the username"
onSearch={this.fetchUser} notFoundContent={fetching ? <Spin size="small"/> : null}
onChange={this.handleChange} filterOption={false}
style={{width: '100%'}} onSearch={this.fetchUser}
> onChange={this.handleChange}
{data.map(d => ( style={{width: '100%'}}>
<Option key={d.value}>{d.text}</Option> {data.map(d => (
))} <Option key={d.value}>{d.text}</Option>
</Select> ))}
<div style={{paddingTop: 10, textAlign: "right"}}> </Select>
<Button disabled={value.length===0} htmlType="button" type="primary" onClick={this.uninstall}>Uninstall</Button> <InstallModalFooter type="Uninstall" operation={this.uninstall} disabled={value.length === 0}/>
</div> </div>
</div>
); );
} }
} }

@ -0,0 +1,94 @@
/*
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from "react";
import {Button, DatePicker} from "antd";
class InstallModalFooter extends React.Component{
constructor(props) {
super(props);
this.state={
scheduledTime: null,
isScheduledInstallVisible: false
}
}
onDateTimeChange = (value, dateString) => {
this.setState({
scheduledTime: dateString
});
};
showScheduledInstall = ()=>{
this.setState({
isScheduledInstallVisible: true
})
};
hideScheduledInstall = ()=>{
this.setState({
isScheduledInstallVisible: false
})
};
render() {
const {scheduledTime,isScheduledInstallVisible} =this.state;
const {disabled, type} = this.props;
return (
<div>
<div style={{
textAlign: "right",
display: (!isScheduledInstallVisible)?'block':'none'
}}>
<Button style={{margin: 5}} disabled={disabled} htmlType="button" type="primary"
onClick={this.props.operation}>
{type}
</Button>
<Button style={{margin: 5}} disabled={disabled} htmlType="button"
onClick={this.showScheduledInstall}>
Scheduled {type}
</Button>
</div>
<div style={{
textAlign: "right",
display: (isScheduledInstallVisible)?'block':'none'
}}>
<DatePicker showTime
placeholder="Select Time"
format="YYYY-MM-DDTHH:mm"
onChange={this.onDateTimeChange}/>
<Button disabled={scheduledTime == null}
style={{margin: 5}}
htmlType="button"
type="primary"
onClick={()=>{
this.props.operation(scheduledTime);
}}>
Schedule
</Button>
<Button style={{margin: 5}} htmlType="button"
onClick={this.hideScheduledInstall}>
Cancel
</Button>
</div>
</div>
);
}
}
export default InstallModalFooter;

@ -25,7 +25,7 @@ export const handleApiError = (error, message) => {
} else { } else {
notification["error"]({ notification["error"]({
message: "There was a problem", message: "There was a problem",
duration: 10, duration: 2,
description: message, description: message,
}); });
} }

Loading…
Cancel
Save