Skip to content

Commit

Permalink
Allow user to choose backup target when backup backing image
Browse files Browse the repository at this point in the history
Signed-off-by: andy.lee <andy.lee@suse.com>
  • Loading branch information
a110605 committed Jun 18, 2024
1 parent da9a084 commit f8eb8ab
Show file tree
Hide file tree
Showing 14 changed files with 212 additions and 26 deletions.
1 change: 1 addition & 0 deletions src/assets/images/read-only.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 11 additions & 1 deletion src/models/backingImage.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { create, deleteBackingImage, query, deleteDisksOnBackingImage, uploadChunk, download, bulkDownload } from '../services/backingImage'
import { create, deleteBackingImage, query, execAction, deleteDisksOnBackingImage, uploadChunk, download, bulkDownload } from '../services/backingImage'
import { message, notification } from 'antd'
import { delay } from 'dva/saga'
import { wsChanges, updateState } from '../utils/websocket'
Expand Down Expand Up @@ -89,6 +89,16 @@ export default {
payload.sourceType === 'upload' && notification.destroy()
}
},
*createBackingImageBackup({
url,
payload,
}, { call, put }) {
const resp = yield call(execAction, url, payload)
if (resp && resp.status === 200) {
message.success(`Successfully backup backing image ${payload.backingImageName}`, 5)
}
yield put({ type: 'query' })
},
*delete({
payload,
}, { call, put }) {
Expand Down
18 changes: 16 additions & 2 deletions src/routes/backingImage/BackingImageActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import PropTypes from 'prop-types'
import { Modal } from 'antd'
import { DropOption } from '../../components'
import { hasReadyBackingDisk } from '../../utils/status'

const confirm = Modal.confirm

function actions({ selected, deleteBackingImage, downloadBackingImage }) {
function actions({ selected, deleteBackingImage, downloadBackingImage, openBackupBackingImageModal }) {
const handleMenuClick = (event, record) => {
event.domEvent?.stopPropagation?.()
switch (event.key) {
Expand All @@ -20,15 +21,27 @@ function actions({ selected, deleteBackingImage, downloadBackingImage }) {
case 'download':
downloadBackingImage(record)
break
case 'backup': {
openBackupBackingImageModal(record)
// const data = {
// ...record,
// // backupTargetName: 'default',
// // backupTargetURL: 'nfs://longhorn-test-nfs-svc.default:/opt/backupstore',
// // backingImageName: record.name,
// }
// backupBackingImage(data)
break
}
default:
}
}

const disableDownloadAction = !hasReadyBackingDisk(selected)

const availableActions = [
{ key: 'delete', name: 'Delete' },
{ key: 'download', name: 'Download', disabled: disableDownloadAction, tooltip: disableDownloadAction ? 'Missing disk with ready state' : '' },
{ key: 'backup', name: 'Backup' },
{ key: 'delete', name: 'Delete' },
]

return (
Expand All @@ -42,6 +55,7 @@ actions.propTypes = {
selected: PropTypes.object,
deleteBackingImage: PropTypes.func,
downloadBackingImage: PropTypes.func,
openBackupBackingImageModal: PropTypes.func,
}

export default actions
4 changes: 3 additions & 1 deletion src/routes/backingImage/BackingImageList.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import BackingImageActions from './BackingImageActions'
import { pagination } from '../../utils/page'
import { formatMib } from '../../utils/formatter'

function list({ loading, dataSource, deleteBackingImage, showDiskStateMapDetail, rowSelection, downloadBackingImage, height }) {
function list({ loading, dataSource, openBackupBackingImageModal, deleteBackingImage, showDiskStateMapDetail, rowSelection, downloadBackingImage, height }) {
const backingImageActionsProps = {
deleteBackingImage,
downloadBackingImage,
openBackupBackingImageModal,
}
const state = (record) => {
if (record.deletionTimestamp) {
Expand Down Expand Up @@ -107,6 +108,7 @@ list.propTypes = {
dataSource: PropTypes.array,
deleteBackingImage: PropTypes.func,
showDiskStateMapDetail: PropTypes.func,
openBackupBackingImageModal: PropTypes.func,
rowSelection: PropTypes.object,
height: PropTypes.number,
}
Expand Down
80 changes: 80 additions & 0 deletions src/routes/backingImage/CreateBackupBackingImageModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Form, Select, Icon } from 'antd'
import { ModalBlur } from '../../components'

const FormItem = Form.Item
const Option = Select.Option

const formItemLayout = {
labelCol: {
span: 6,
},
wrapperCol: {
span: 15,
},
}

const modal = ({
backingImage,
availBackupTargets,
visible,
onCancel,
onOk,
form: {
getFieldDecorator,
getFieldValue,
},
}) => {
function handleOk() {
const backupTarget = availBackupTargets.find(bkTarget => bkTarget.name === getFieldValue('backupTargetName'))
if (backupTarget) {
const url = backingImage.actions?.backupBackingImageCreate
const payload = {
...backingImage,
backingImageName: backingImage.name,
backupTargetName: backupTarget.name,
backupTargetURL: backupTarget.backupTargetURL,
}
onOk(url, payload)
}
}

const modalOpts = {
title: 'Create Backup Backing Image',
visible,
onCancel,
onOk: handleOk,
}

return (
<ModalBlur {...modalOpts}>
<p type="warning">
<Icon style={{ marginRight: '10px' }} type="exclamation-circle" />Choose a backup target to backup <strong>{backingImage.name}</strong> backing image
</p>
<div style={{ display: 'flex' }}>
<FormItem label="Backup Target" style={{ width: '100%' }} {...formItemLayout}>
{getFieldDecorator('backupTargetName', {
initialValue: availBackupTargets.length > 0 ? availBackupTargets[0].name : '',
})(
<Select style={{ width: '100%' }}>
{availBackupTargets.map(bkTarget => <Option key={bkTarget.name} value={bkTarget.name}>{bkTarget.name}</Option>)}
</Select>
)}
</FormItem>
</div>
</ModalBlur>
)
}

modal.propTypes = {
backingImage: PropTypes.object,
availBackupTargets: PropTypes.array,
form: PropTypes.object.isRequired,
visible: PropTypes.bool,
onCancel: PropTypes.func,
item: PropTypes.object,
onOk: PropTypes.func,
}

export default Form.create()(modal)
49 changes: 46 additions & 3 deletions src/routes/backingImage/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import { Row, Col, Button, Progress, notification } from 'antd'
import CreateBackingImage from './CreateBackingImage'
import BackingImageList from './BackingImageList'
import DiskStateMapDetail from './DiskStateMapDetail'
import CreateBackupBackingImageModal from './CreateBackupBackingImageModal'
import { Filter } from '../../components/index'
import BackingImageBulkActions from './BackingImageBulkActions'
import queryString from 'query-string'
import { getAvailBackupTargets } from '../../utils/backupTarget'
import style from './BackingImage.less'
import C from '../../utils/constants'

Expand All @@ -18,6 +20,8 @@ class BackingImage extends React.Component {
this.state = {
height: 300,
message: null,
backupBackingImageModalVisible: false,
selectedBackingImage: {},
}
}

Expand All @@ -37,6 +41,22 @@ class BackingImage extends React.Component {
})
}

handleBackupBackingImageModalOpen = (record) => {
this.setState({
...this.state,
backupBackingImageModalVisible: true,
selectedBackingImage: record,
})
}

handleBackupBackingImageModalClose = () => {
this.setState({
...this.state,
backupBackingImageModalVisible: false,
selectedBackingImage: {},
})
}

uploadFile = (file, record) => {
let totalSize = file.size
this.props.dispatch({
Expand Down Expand Up @@ -65,8 +85,9 @@ class BackingImage extends React.Component {
}

render() {
const { dispatch, loading, location } = this.props
const { uploadFile } = this
const { dispatch, loading, location, backupTarget } = this.props
const { uploadFile, handleBackupBackingImageModalOpen, handleBackupBackingImageModalClose } = this
const { backupBackingImageModalVisible, selectedBackingImage } = this.state
const { data: volumeData } = this.props.volume
const { data, selected, createBackingImageModalVisible, createBackingImageModalKey, diskStateMapDetailModalVisible, diskStateMapDetailModalKey, diskStateMapDeleteDisabled, diskStateMapDeleteLoading, selectedDiskStateMapRows, selectedDiskStateMapRowKeys, selectedRows } = this.props.backingImage
const { backingImageUploadPercent, backingImageUploadStarted } = this.props.app
Expand Down Expand Up @@ -103,6 +124,9 @@ class BackingImage extends React.Component {
payload: record,
})
},
openBackupBackingImageModal: (record) => {
handleBackupBackingImageModalOpen(record)
},
downloadBackingImage(record) {
dispatch({
type: 'backingImage/downloadBackingImage',
Expand All @@ -128,6 +152,23 @@ class BackingImage extends React.Component {
},
}

const createBackupBackingImageModalProps = {
backingImage: selectedBackingImage,
availBackupTargets: getAvailBackupTargets(backupTarget),
visible: backupBackingImageModalVisible,
onOk(url, payload) {
dispatch({
type: 'backingImage/createBackingImageBackup',
url,
payload,
})
handleBackupBackingImageModalClose()
},
onCancel() {
handleBackupBackingImageModalClose()
},
}

const addBackingImage = () => {
dispatch({
type: 'backingImage/showCreateBackingImageModal',
Expand Down Expand Up @@ -315,6 +356,7 @@ class BackingImage extends React.Component {
<BackingImageList {...backingImageListProps} />
{ createBackingImageModalVisible ? <CreateBackingImage key={createBackingImageModalKey} {...createBackingImageModalProps} /> : ''}
{ diskStateMapDetailModalVisible ? <DiskStateMapDetail key={diskStateMapDetailModalKey} {...diskStateMapDetailModalProps} /> : ''}
<CreateBackupBackingImageModal {...createBackupBackingImageModalProps} />
</div>
)
}
Expand All @@ -323,10 +365,11 @@ class BackingImage extends React.Component {
BackingImage.propTypes = {
app: PropTypes.object,
backingImage: PropTypes.object,
backupTarget: PropTypes.object,
loading: PropTypes.bool,
location: PropTypes.object,
volume: PropTypes.object,
dispatch: PropTypes.func,
}

export default connect(({ app, volume, backingImage, loading }) => ({ app, volume, backingImage, loading: loading.models.backingImage }))(BackingImage)
export default connect(({ app, volume, backupTarget, backingImage, loading }) => ({ app, volume, backupTarget, backingImage, loading: loading.models.backingImage }))(BackingImage)
24 changes: 19 additions & 5 deletions src/routes/backupTarget/BackupTargetList.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'
import { Table, Icon, Tooltip } from 'antd'
import BackupTargetActions from './BackupTargetActions'
import { pagination } from '../../utils/page'
import readOnly from '../../assets/images/read-only.svg'

function list({ loading, dataSource, deleteBackupTarget, editBackupTarget, rowSelection, height }) {
const columns = [
Expand Down Expand Up @@ -58,7 +59,17 @@ function list({ loading, dataSource, deleteBackupTarget, editBackupTarget, rowSe
sorter: (a, b) => a.readOnly - b.readOnly,
render: (text) => {
return (
<div>{text.toString().firstUpperCase()}</div>
<>
{text === false ? (
<Tooltip title="This backup target is writable">
<Icon type="edit" theme="outlined" style={{ color: 'green', alignSelf: 'center' }} />
</Tooltip>
) : (
<Tooltip title="This backup target is read-only, so it does not allow any backups to synchronize to it.">
<img style={{ width: 20, height: 20 }} src={readOnly} alt="readOnlyIcon" />
</Tooltip>)
}
</>
)
},
}, {
Expand All @@ -69,7 +80,7 @@ function list({ loading, dataSource, deleteBackupTarget, editBackupTarget, rowSe
sorter: (a, b) => a.default - b.default,
render: (text) => {
return (
<div>{text.toString().firstUpperCase()}</div>
<>{text === true ? (<Icon type="pushpin" theme="twoTone" style={{ alignSelf: 'center' }} />) : ''}</>
)
},
}, {
Expand All @@ -81,10 +92,13 @@ function list({ loading, dataSource, deleteBackupTarget, editBackupTarget, rowSe
render: (text) => {
return (
<div style={{ display: 'flex', justifyContent: 'center' }}>
<div>{text.toString().firstUpperCase()}</div>
{text === false && (
{text === true ? (
<Tooltip title="This backup target is available to sync backup to it">
<Icon type="check-square" style={{ color: 'green', alignSelf: 'center' }} />
</Tooltip>
) : (
<Tooltip title="This backup target is unavailable, please check the URL and credential secret are all correct.">
<Icon type="exclamation-circle" style={{ color: 'red', marginLeft: 4, alignSelf: 'center' }} />
<Icon type="exclamation-circle" style={{ color: 'red', alignSelf: 'center' }} />
</Tooltip>
)}
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/routes/backupTarget/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ class BackupTarget extends React.Component {
<Button className="out-container-button" size="large" type="primary" disabled={loading} onClick={() => this.handleCreateModalOpen()}>
Create Backup Target
</Button>
<BackupTargetList {...backupTargetListProps} />
<BackupTargetList key={Math.random()} {...backupTargetListProps} />
{createBackupTargetModalVisible && <CreateBackupTargetModal {...createBackupTargetModalProps} />}
{editBackupTargetModalVisible && <EditBackupTargetModal {...editBackupTargetModalProps} />}
</div>
Expand Down
Loading

0 comments on commit f8eb8ab

Please sign in to comment.