Skip to content

Commit

Permalink
feat: bulk backup action
Browse files Browse the repository at this point in the history
Signed-off-by: Yi-Ya Chen <[email protected]>
  • Loading branch information
houhoucoop authored and a110605 committed Dec 23, 2024
1 parent ddfa9dd commit 21c4758
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 114 deletions.
2 changes: 1 addition & 1 deletion src/models/backingImage.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ export default {
for (const bi of payload) {
const url = bi.actions.backupBackingImageCreate
if (url) {
yield call(execAction, url)
yield call(execAction, url, bi)
}
}
}
Expand Down
276 changes: 165 additions & 111 deletions src/routes/backingImage/BackingImageBulkActions.js
Original file line number Diff line number Diff line change
@@ -1,139 +1,193 @@
import React from 'react'
import React, { useState } from 'react'
import PropTypes from 'prop-types'
import { Button, Modal } from 'antd'
import { Form, Button, Modal, Select, Icon } from 'antd'
import { hasReadyBackingDisk, diskStatusColorMap } from '../../utils/status'
import styles from './BackingImageBulkActions.less'

const confirm = Modal.confirm
function BulkActions({
selectedRows,
backupProps,
deleteBackingImages,
downloadSelectedBackingImages,
backupSelectedBackingImages,
backupTargets = [],
form = {},
}) {
const [isBackupModalVisible, setIsBackupModalVisible] = useState(false)


function bulkActions({ selectedRows, backupProps, deleteBackingImages, downloadSelectedBackingImages, backupSelectedBackingImages }) {
const { getFieldDecorator, getFieldValue } = form
const { backupTargetAvailable } = backupProps
const readyColor = diskStatusColorMap.ready
const readyTextStyle = {
display: 'inline-block',
width: 'max-content',
padding: '0 4px',
color: '#27AE5F',
border: `1px solid ${readyColor.color}`,
backgroundColor: readyColor.bg,
textTransform: 'capitalize',
}
const getImageLabel = selectedRows.length === 1 ? 'image' : 'images'
const readyImages = selectedRows.filter((row) => hasReadyBackingDisk(row))

const handleDelete = () => {
const title = (
<>
<p>
Are you sure you want to delete the following {selectedRows.length} backing {getImageLabel}?
</p>
<ul>
{selectedRows.map((item) => (
<li key={item.name}>{item.name}</li>
))}
</ul>
</>
)
Modal.confirm({
width: 'fit-content',
okText: 'Delete',
okType: 'danger',
title,
content: null,
onOk: () => deleteBackingImages(selectedRows),
})
}

const handleDownload = () => {
const downloadableImages = selectedRows.filter((row) => hasReadyBackingDisk(row))

const title = (
<>
<p>
The following backing {getImageLabel} with <strong style={readyTextStyle}>Ready</strong> status disk will be downloaded.
</p>
<ul>
{downloadableImages.map((item) => (
<li key={item.name}>{item.name}</li>
))}
</ul>
<p>
Note: You need to allow <strong>Automatic Downloads</strong> in browser settings to download multiple files at once.
</p>
</>
)
Modal.confirm({
width: 'fit-content',
okText: 'Download',
title,
content: null,
onOk: () => downloadSelectedBackingImages(downloadableImages),
})
}

const handleBackup = () => {
setIsBackupModalVisible(true)
}

const handleBackupOk = () => {
const backupTarget = backupTargets.find((bkTarget) => bkTarget.name === getFieldValue('backupTargetName'))
const backupImages = readyImages.map((backingImage) => ({
...backingImage,
backingImageName: backingImage.name,
backupTargetName: backupTarget.name,
backupTargetURL: backupTarget.backupTargetURL,
}))

backupSelectedBackingImages(backupImages)
setIsBackupModalVisible(false)
}

const handleClick = (action) => {
const count = selectedRows.length
switch (action) {
case 'delete':
confirm({
width: 'fit-content',
okText: 'Delete',
okType: 'danger',
title: (<>
<p>Are you sure to delete below {count} backing {count === 1 ? 'image' : 'images' } ?</p>
<ul>
{selectedRows.map(item => <li>{item.name}</li>)},
</ul>
</>),
onOk() {
deleteBackingImages(selectedRows)
},
})
break
case 'download': {
const downloadableImages = selectedRows.filter(row => hasReadyBackingDisk(row))
const readyColor = diskStatusColorMap.ready
const readyTextStyle = {
display: 'inline-block',
width: 'max-content',
padding: '0 4px',
marginRight: '5px',
color: '#27AE5F',
border: `1px solid ${readyColor.color}`,
backgroundColor: readyColor.bg,
textTransform: 'capitalize',
}
confirm({
okText: 'Download',
width: 'fit-content',
title: (<>
<p>Below backing {count === 1 ? 'image' : 'images' } with <strong style={readyTextStyle}>Ready</strong> status disk will be downloaded</p>
<ul>
{downloadableImages.map(item => <li>{item.name}</li>)}
</ul>
<p>Note. You need allow <strong>Automatic Downloads</strong> permission<br />in browser settings to download multiple files at once.</p>
</>),
onOk() {
downloadSelectedBackingImages(downloadableImages)
},
})
break
}
case 'backup': {
const backupImages = selectedRows.filter(row => hasReadyBackingDisk(row))
const readyColor = diskStatusColorMap.ready
const readyTextStyle = {
display: 'inline-block',
width: 'max-content',
padding: '0 4px',
marginRight: '5px',
color: '#27AE5F',
border: `1px solid ${readyColor.color}`,
backgroundColor: readyColor.bg,
textTransform: 'capitalize',
}
confirm({
okText: 'Backup',
width: 'fit-content',
title: (<>
<p>Are you sure to backup below <strong style={readyTextStyle}>Ready</strong> status backing {count === 1 ? 'image' : 'images'} ?</p>
<ul>
{backupImages.map(item => <li key={item.name}>{item.name}</li>)}
</ul>
</>),
onOk() {
backupSelectedBackingImages(backupImages)
},
})
break
}
default:
}
const handleBackupCancel = () => {
setIsBackupModalVisible(false)
}

const allActions = [
{
key: 'delete',
name: 'Delete',
disabled() { return selectedRows.length === 0 }
disabled: () => selectedRows.length === 0,
onClick: handleDelete,
},
{
key: 'download',
name: 'Download',
disabled() {
return selectedRows.length === 0
|| selectedRows.every(row => !hasReadyBackingDisk(row))
|| selectedRows.some(row => row.dataEngine === 'v2')
}
disabled: () => selectedRows.length === 0
|| readyImages.length === 0
|| selectedRows.some((row) => row.dataEngine === 'v2'),
onClick: handleDownload,
},
{
key: 'backup',
name: 'Back Up',
disabled() {
return selectedRows.length === 0
|| backupTargetAvailable === false
|| selectedRows.every(row => !hasReadyBackingDisk(row))
}
name: 'Backup',
disabled: () => selectedRows.length === 0
|| readyImages.length === 0
|| !backupTargetAvailable,
onClick: handleBackup,
},
]

return (
<div style={{ display: 'flex' }}>
{ allActions.map(item => {
return (
<div key={item.key} style={{ marginRight: '10px' }}>
<Button size="large" type="primary" disabled={item.disabled()} onClick={() => handleClick(item.key)}>{ item.name }</Button>
</div>
)
}) }
</div>
<>
<div style={{ display: 'flex' }}>
{allActions.map(({ key, name, disabled, onClick }) => (
<Button
key={key}
size="large"
type="primary"
disabled={disabled()}
onClick={onClick}
className={styles.bulkActionBtns}
>
{name}
</Button>
))}
</div>
<Modal
className={styles.backupModal}
closable={false}
width="fit-content"
okText="Backup"
visible={isBackupModalVisible}
onOk={handleBackupOk}
onCancel={handleBackupCancel}
>
<Icon className={styles.questionCircleIcon} type="question-circle" />
<Form className={styles.backupForm}>
<p>
Are you sure you want to backup the following <strong style={readyTextStyle}>Ready</strong> status backing {readyImages.length === 1 ? 'image' : 'images'}?
</p>
<ul>{readyImages.map((item) => <li key={item.name}>{item.name}</li>)}</ul>
<Form.Item label="Backup Target">
{getFieldDecorator('backupTargetName', {
initialValue: backupTargets.find((bk) => bk.name === 'default')?.name || '',
})(
<Select>
{backupTargets.map((bkTarget) => (
<Select.Option
key={bkTarget.name}
disabled={!bkTarget.available}
value={bkTarget.name}
>
{bkTarget.name}
</Select.Option>
))}
</Select>
)}
</Form.Item>
</Form>
</Modal>
</>
)
}

bulkActions.propTypes = {
selectedRows: PropTypes.array,
deleteBackingImages: PropTypes.func,
downloadSelectedBackingImages: PropTypes.func,
backupSelectedBackingImages: PropTypes.func,
backupProps: PropTypes.object,
BulkActions.propTypes = {
selectedRows: PropTypes.array.isRequired,
deleteBackingImages: PropTypes.func.isRequired,
downloadSelectedBackingImages: PropTypes.func.isRequired,
backupSelectedBackingImages: PropTypes.func.isRequired,
backupProps: PropTypes.object.isRequired,
backupTargets: PropTypes.array.isRequired,
form: PropTypes.object.isRequired,
}

export default bulkActions
export default Form.create()(BulkActions)
41 changes: 41 additions & 0 deletions src/routes/backingImage/BackingImageBulkActions.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
.bulkActionBtns {
margin-right: 10px;
}

.backupModal {
display: flex;

.questionCircleIcon {
margin-right: 16px;
font-size: 22px;
color: #faad14;
}

.backupForm {
color: #000000d9;
font-weight: 500;
font-size: 17px;
line-height: 1.4;
}

:global {
.ant-modal-body {
display: flex;
padding: 32px 32px 0;

.ant-form-item {
display: flex;
margin-bottom: 0;
}

.ant-col.ant-form-item-control-wrapper {
flex: 1;
}
}

.ant-modal-footer {
padding: 24px 32px;
border-top: 0;
}
}
}
6 changes: 4 additions & 2 deletions src/routes/backingImage/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ class BackingImage extends React.Component {

const backingImages = filterBackingImage(data, biSearchField, biSearchValue)
const volumeNameOptions = volumeData.map((volume) => volume.name)
const backupTargets = getBackupTargets(backupTarget)

const backingImageListProps = {
dataSource: backingImages,
Expand Down Expand Up @@ -179,7 +180,7 @@ class BackingImage extends React.Component {

const createBackupBackingImageModalProps = {
backingImage: selectedBackingImage,
backupTargets: getBackupTargets(backupTarget),
backupTargets,
visible: backupBackingImageModalVisible,
onOk(url, payload) {
dispatch({
Expand Down Expand Up @@ -327,6 +328,7 @@ class BackingImage extends React.Component {
const backingImageBulkActionsProps = {
selectedRows,
backupProps: this.props.backup,
backupTargets,
deleteBackingImages(record) {
dispatch({
type: 'backingImage/bulkDelete',
Expand All @@ -344,7 +346,7 @@ class BackingImage extends React.Component {
type: 'backingImage/bulkBackup',
payload: record,
})
},
}
}

const minCopiesCountProps = {
Expand Down

0 comments on commit 21c4758

Please sign in to comment.