Browse Source

(*)数据备份功能完善

master
peng.peng 2 years ago
parent
commit
1aa4564f98
  1. 35
      api/app/lib/controllers/backups/index.js
  2. 4
      api/app/lib/routes/backups/index.js
  3. 12
      web/client/src/sections/backups/actions/backups.js
  4. 7
      web/client/src/sections/backups/components/backupsModal.js
  5. 26
      web/client/src/sections/backups/components/taskModal.js
  6. 79
      web/client/src/sections/backups/containers/backupTask.js
  7. 2
      web/client/src/sections/homePage/nav-item.js
  8. 2
      web/client/src/sections/homePage/routes.js
  9. 2
      web/client/src/sections/resourceRetrieval/containers/retrieval.js

35
api/app/lib/controllers/backups/index.js

@ -33,6 +33,7 @@ function getBackupsList(opts) {
}
const res = await models.Backups.findAndCount(option);
res.time = moment()
ctx.status = 200;
ctx.body = res;
} catch (error) {
@ -56,11 +57,13 @@ function addBackups(opts) {
//调用后端备份接口
// const url = '10.8.30.160:8085/dumpDB?dbHost=10.8.30.75&dbPort=5432&user=postgres&password=1234&dbName=Anxinyun0916'//测试使用
const url = backupsUrl + `/dumpDB?dbHost=${host}&dbPort=${port}&user=${user}&password=${password}&dbName=${database}`;
const res = await request.post(url)
request.post(url).then(res => {
const { fileInfo: { name, size }, message } = res.body
await models.Backups.update({
models.Backups.update({
size, source: name, state: message, completeTime: moment()
}, { where: { id: backup.id } })
})
ctx.status = 204;
ctx.body = { message: '新建数据备份成功' }
} catch (error) {
@ -119,12 +122,38 @@ function deleteBackups(opts) {
}
}
// 新增数据备份
function restore(opts) {
return async function (ctx, next) {
const { backupsUrl } = opts;
const models = ctx.fs.dc.models;
try {
let rslt = ctx.request.body;
const { id, source, databases: { database, host, password, port, user } } = ctx.request.body
//调用后端备份接口
const url = backupsUrl + `/restoreDB?dbHost=${host}&dbPort=${port}&user=${user}&password=${password}&dbName=${database}&backFileName=${source}`;
request.post(url).then(res => {
const { fileInfo: { name, size }, message } = res.body
models.Backups.update({
size, source: name, state: message, completeTime: moment()
}, { where: { id: id } })
})
ctx.status = 204;
ctx.body = { message: '备份还原成功' }
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = { message: '备份还原失败' }
}
}
}
module.exports = {
getBackupsList,
addBackups,
editBackups,
deleteBackups,
restore
}

4
api/app/lib/routes/backups/index.js

@ -19,5 +19,7 @@ module.exports = function (app, router, opts, AuthCode) {
app.fs.api.logAttr['GET/meta/backups'] = { content: '获取数据备份信息列表', visible: true };
router.get('/meta/backups', backups.getBackupsList(opts));
//恢复备份
app.fs.api.logAttr['POST/backups/restore'] = { content: '恢复备份', visible: true };
router.post('/backups/restore', backups.restore(opts))
};

12
web/client/src/sections/backups/actions/backups.js

@ -54,3 +54,15 @@ export function modifyBackups(id, params, msg) {
});
}
export function restoreBackups(params) {
return (dispatch) => basicAction({
type: 'post',
data: params,
dispatch,
actionType: 'RESTORE_BACKUPS_REPORT',
url: ApiTable.restoreBackups,
msg: {
option: '备份恢复',
},
});
}

7
web/client/src/sections/backups/components/backupsModal.js

@ -36,18 +36,19 @@ export default (props) => {
values.databases = dataSources?.rows?.find(s => s.id == values?.databases?.value)?.config;
values.createTime = moment();
values.state = '备份中';
values.title = title;
return onFinish && await onFinish(values, editData, form)
// return true;
}}
width={500}
>
<ProFormText
{title != '恢复数据备份' && <ProFormText
rules={[{ required: true, message: '请输入姓名' },
{ max: 255, message: '姓名长度不能大于255个字符' },
]}
name="note"
label="备份信息"
/>
/>}
<ProFormTreeSelect
name="databases"
@ -61,7 +62,7 @@ export default (props) => {
title: 'postgre',
disabled: true,
value: '0-0',
children: dataSources?.rows?.filter(s => s?.type != '备份数据库')?.map(s => {
children: dataSources?.rows?.filter(s => (title != '恢复数据备份' && s?.type != '备份数据库') || (title == '恢复数据备份' && s?.type == '备份数据库'))?.map(s => {
return {
title: s.name,
value: s.id,

26
web/client/src/sections/backups/components/taskModal.js

@ -1,26 +0,0 @@
import React, { useEffect, useState } from 'react'
import { Tabs, Card, Modal } from 'antd'
import AdapterStep from './adapterStep';
import { STEP_CONFIG } from './steps/index'
function DataSourceModal(props) {
const { visible, editData, onFinish, onCancel,
type = 'postgre',//当前卡片的key (目前只有postgre,支持后续扩展)
dataSourceFilter,
} = props;
const { StepThree } = STEP_CONFIG[type];
// const onFinish = () => { }
return <>
<Modal
title={editData ? '编辑采集任务' : "新建采集任务"}
onCancel={() => { onCancel() }}
open={visible}
footer={null}
width={1200}
destroyOnClose={true}
>
<StepThree next={onFinish} dataSourceFilter={dataSourceFilter} editData={editData} />
</Modal>
</>
}
export default DataSourceModal

79
web/client/src/sections/backups/containers/backupTask.js

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'
import React, { useEffect, useState, useMemo } from 'react'
import { Spin, Popconfirm, Select, Row, Col, Button, Input, Table } from 'antd';
import { connect } from 'react-redux';
import ProTable from '@ant-design/pro-table';
@ -6,12 +6,15 @@ import moment from 'moment';
import BackupsModal from '../components/backupsModal';
import './style.less';
import { ApiTable, useFsRequest } from '$utils';
function Member(props) {
const { loading, clientHeight, actions, dispatch, backups, user, dataSources } = props;
const { loading, clientHeight, actions, dispatch, backups, backupsIsRequesting, dataSources } = props;
const [pageSize, setPageSize] = useState(10);
const [currentPage, setCurrentPage] = useState(1);
const [searchValue, setSearchValue] = useState('');
const [addLoading, setAddLoading] = useState(false)
const [autoSearchValue, setAutoSearchValue] = useState('');
const queryData = (search) => {
const query = {
limit: search ? 10 : pageSize || 10,
@ -22,6 +25,18 @@ function Member(props) {
dispatch(actions.backups.getBackupsList(query));
}
const { data: tableData = {} } = useFsRequest({
url: ApiTable.getBackupsList,
query: {
limit: pageSize || 10,
page: currentPage || 1,
name: autoSearchValue,
},
ready: !backupsIsRequesting,
refreshDeps: [pageSize, currentPage, autoSearchValue],
pollingInterval: 1000
});
useEffect(() => {
dispatch(actions.metadataAcquisition.getDataSources());
}, [])
@ -66,20 +81,17 @@ function Member(props) {
render: (text, record) => {
const options = [];
options.push(
<Popconfirm
key="del"
placement="top"
title={<><div>是否确认重置该数据备份密码</div>
</>}
onConfirm={() => {
dispatch(actions.backups.modifyBackups(record.id, { password: 'e10adc3949ba59abbe56e057f20f883e' }, '重置密码'))
}}
okText="是"
cancelText="否"
>
<a>恢复</a>
</Popconfirm>)
options.push(<a onClick={() => { window.open('/assets/files/backups/1.sql') }}>下载</a>)
record?.source ? <BackupsModal
dataSources={dataSources}
triggerRender={<a type='primary'>恢复</a>}
title="恢复数据备份"
onFinish={onFinish}
key="addModel"
/> : <a style={{ color: 'gray' }}></a>)
options.push(
record?.source ?
<a a onClick={() => { window.open(record?.source) }}> 下载</a> : <a style={{ color: 'gray' }}></a>
)
options.push(
<Popconfirm
@ -106,6 +118,22 @@ function Member(props) {
};
const onFinish = async (values, editData) => {
if (values?.title == '恢复数据备份') {
return dispatch(actions.backups.restoreBackups({
id: editData.id,
source: editData.source,
databases: values.databases
})).then(res => {
setAddLoading(false)
if (res.success) {
queryData();
return true;
} else {
return false;
}
});
} else {
setAddLoading(true)
return dispatch(actions.backups.addBackups({
...values,
@ -118,8 +146,18 @@ function Member(props) {
return false;
}
});
}
};
const tableDataFilter = useMemo(() => {
if (tableData?.count && backups?.count) {
return tableData.time > backups.time ? tableData : backups;
} else {
return backups;
}
}, [tableData, backups])
return <Spin spinning={loading || addLoading}>
<Row className='protable-title'>
<Col span={12}>
@ -137,10 +175,12 @@ function Member(props) {
style={{ width: 220, marginRight: 15 }} placeholder="请输入" />
<Button onClick={() => {
setAutoSearchValue(searchValue)
setCurrentPage(1)
setPageSize(10)
queryData(true)
}} type='primary'>查询</Button></Col>
}} type='primary'>查询</Button>
</Col>
</Row>
<ProTable
@ -155,7 +195,7 @@ function Member(props) {
}
pagination={{
size: 'large',
total: backups?.count,
total: tableDataFilter?.count,
showSizeChanger: true,
showQuickJumper: true,
current: currentPage,
@ -176,7 +216,7 @@ function Member(props) {
}
}}
dataSource={backups?.rows || []}
dataSource={tableDataFilter?.rows || []}
options={false}
/>
</Spin>
@ -194,6 +234,7 @@ function mapStateToProps(state) {
backups: backups?.data || {},
user: auth.user,
dataSources: datasources?.data || {},
backupsIsRequesting: backups.isRequesting
};
}

2
web/client/src/sections/homePage/nav-item.js

@ -5,7 +5,7 @@ import { HomeOutlined } from '@ant-design/icons';
export function getNavItem() {
return (
<Menu.Item key="homePage" icon={<HomeOutlined />}>
<Link to="/homePage">首页</Link>
<Link to="/homePage">数据监控平台</Link>
</Menu.Item>
);
}

2
web/client/src/sections/homePage/routes.js

@ -6,7 +6,7 @@ export default [{
route: {
path: '/homePage',
key: 'homePage',
breadcrumb: '首页',
breadcrumb: '数据监控平台',
// 不设置 component 则面包屑禁止跳转
component: homePage
}

2
web/client/src/sections/resourceRetrieval/containers/retrieval.js

@ -64,7 +64,7 @@ function Retrieval(props) {
id: searchDataId
},
refreshDeps: [searchDataId],
ready: !!searchDataId
ready: !!searchDataId,
});
useEffect(() => {

Loading…
Cancel
Save