wenlele
2 years ago
28 changed files with 905 additions and 94 deletions
@ -0,0 +1,163 @@ |
|||||
|
'use strict'; |
||||
|
const request = require('superagent') |
||||
|
const moment = require('moment'); |
||||
|
function getBackupsList(opts) { |
||||
|
return async function (ctx, next) { |
||||
|
|
||||
|
const models = ctx.fs.dc.models; |
||||
|
const { page, limit, name } = ctx.query; |
||||
|
const Op = ctx.fs.dc.ORM.Op; |
||||
|
let errMsg = { message: '获取数据备份失败' } |
||||
|
try { |
||||
|
let searchWhere = { |
||||
|
|
||||
|
} |
||||
|
let option = { |
||||
|
where: searchWhere, |
||||
|
order: [["id", "desc"]], |
||||
|
attributes: { exclude: ['password'] }, |
||||
|
} |
||||
|
|
||||
|
if (name) { |
||||
|
searchWhere.note = { $like: '%' + name + '%' }; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
option.where = searchWhere |
||||
|
let limit_ = limit || 10; |
||||
|
let page_ = page || 1; |
||||
|
let offset = (page_ - 1) * limit_; |
||||
|
if (limit && page) { |
||||
|
option.limit = limit_ |
||||
|
option.offset = offset |
||||
|
} |
||||
|
|
||||
|
const res = await models.Backups.findAndCount(option); |
||||
|
res.time = moment() |
||||
|
ctx.status = 200; |
||||
|
ctx.body = res; |
||||
|
} catch (error) { |
||||
|
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); |
||||
|
ctx.status = 400; |
||||
|
ctx.body = errMsg |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 新增数据备份
|
||||
|
function addBackups(opts) { |
||||
|
return async function (ctx, next) { |
||||
|
const { backupsUrl } = opts; |
||||
|
const models = ctx.fs.dc.models; |
||||
|
try { |
||||
|
let rslt = ctx.request.body; |
||||
|
const { database, host, password, port, user } = ctx.request.body.databases |
||||
|
let backup = await models.Backups.create(Object.assign({}, rslt)) |
||||
|
|
||||
|
//调用后端备份接口
|
||||
|
// 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}`; |
||||
|
request.post(url).then(res => { |
||||
|
const { fileInfo: { name, size }, code } = res.body |
||||
|
models.Backups.update({ |
||||
|
size, source: name, state: code == 200 ? '备份成功' : '备份失败', completeTime: moment() |
||||
|
}, { where: { id: backup.id } }) |
||||
|
if (code != 200) ctx.fs.logger.error(`path: ${ctx.path}, error: ${message}`); |
||||
|
}) |
||||
|
|
||||
|
ctx.status = 204; |
||||
|
ctx.body = { message: '新建数据备份成功' } |
||||
|
} catch (error) { |
||||
|
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); |
||||
|
ctx.status = 400; |
||||
|
ctx.body = { message: '新建数据备份失败' } |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 修改数据备份
|
||||
|
function editBackups(opts) { |
||||
|
return async function (ctx, next) { |
||||
|
|
||||
|
try { |
||||
|
const models = ctx.fs.dc.models; |
||||
|
const { id } = ctx.params; |
||||
|
const body = ctx.request.body; |
||||
|
|
||||
|
|
||||
|
await models.Backups.update( |
||||
|
body, |
||||
|
{ 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: '修改数据备份失败' } |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 删除数据备份
|
||||
|
function deleteBackups(opts) { |
||||
|
return async function (ctx, next) { |
||||
|
|
||||
|
try { |
||||
|
const models = ctx.fs.dc.models; |
||||
|
const { id } = ctx.params; |
||||
|
|
||||
|
await models.Backups.destroy({ |
||||
|
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: '删除数据备份失败' } |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//备份恢复
|
||||
|
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 |
||||
|
await models.Backups.update({ |
||||
|
state: '恢复中', |
||||
|
}, { where: { id: id } }) |
||||
|
//调用后端备份接口
|
||||
|
const url = backupsUrl + `/restoreDB?dbHost=${host}&dbPort=${port}&user=${user}&password=${password}&dbName=${database}&backFileName=${source}`; |
||||
|
request.post(url).then(res => { |
||||
|
const { code, message } = res.body |
||||
|
models.Backups.update({ |
||||
|
state: code == 200 ? '恢复成功' : '恢复失败', |
||||
|
}, { where: { id: id } }) |
||||
|
if (code != 200) ctx.fs.logger.error(`path: ${ctx.path}, error: ${message}`); |
||||
|
}) |
||||
|
|
||||
|
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 |
||||
|
} |
@ -0,0 +1,89 @@ |
|||||
|
/* eslint-disable*/ |
||||
|
|
||||
|
'use strict'; |
||||
|
|
||||
|
module.exports = dc => { |
||||
|
const DataTypes = dc.ORM; |
||||
|
const sequelize = dc.orm; |
||||
|
const Backups = sequelize.define("backups", { |
||||
|
id: { |
||||
|
type: DataTypes.INTEGER, |
||||
|
allowNull: false, |
||||
|
defaultValue: null, |
||||
|
comment: null, |
||||
|
primaryKey: true, |
||||
|
field: "id", |
||||
|
autoIncrement: true, |
||||
|
unique: "backups_id_uindex" |
||||
|
}, |
||||
|
note: { |
||||
|
type: DataTypes.STRING, |
||||
|
allowNull: false, |
||||
|
defaultValue: null, |
||||
|
comment: "备注信息", |
||||
|
primaryKey: false, |
||||
|
field: "note", |
||||
|
autoIncrement: false |
||||
|
}, |
||||
|
databases: { |
||||
|
type: DataTypes.JSONB, |
||||
|
allowNull: true, |
||||
|
defaultValue: null, |
||||
|
comment: null, |
||||
|
primaryKey: false, |
||||
|
field: "databases", |
||||
|
autoIncrement: false |
||||
|
}, |
||||
|
size: { |
||||
|
type: DataTypes.STRING, |
||||
|
allowNull: true, |
||||
|
defaultValue: null, |
||||
|
comment: null, |
||||
|
primaryKey: false, |
||||
|
field: "size", |
||||
|
autoIncrement: false |
||||
|
}, |
||||
|
createTime: { |
||||
|
type: DataTypes.DATE, |
||||
|
allowNull: true, |
||||
|
defaultValue: null, |
||||
|
comment: null, |
||||
|
primaryKey: false, |
||||
|
field: "create_time", |
||||
|
autoIncrement: false |
||||
|
}, |
||||
|
completeTime: { |
||||
|
type: DataTypes.DATE, |
||||
|
allowNull: true, |
||||
|
defaultValue: null, |
||||
|
comment: "备份完成时间", |
||||
|
primaryKey: false, |
||||
|
field: "complete_time", |
||||
|
autoIncrement: false |
||||
|
}, |
||||
|
state: { |
||||
|
type: DataTypes.STRING, |
||||
|
allowNull: true, |
||||
|
defaultValue: null, |
||||
|
comment: null, |
||||
|
primaryKey: false, |
||||
|
field: "state", |
||||
|
autoIncrement: false |
||||
|
}, |
||||
|
source: { |
||||
|
type: DataTypes.TEXT, |
||||
|
allowNull: true, |
||||
|
defaultValue: null, |
||||
|
comment: "备份文件路径", |
||||
|
primaryKey: false, |
||||
|
field: "source", |
||||
|
autoIncrement: false |
||||
|
} |
||||
|
}, { |
||||
|
tableName: "backups", |
||||
|
comment: "", |
||||
|
indexes: [] |
||||
|
}); |
||||
|
dc.models.Backups = Backups; |
||||
|
return Backups; |
||||
|
}; |
@ -0,0 +1,25 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
const backups = require('../../controllers/backups/index'); |
||||
|
|
||||
|
module.exports = function (app, router, opts, AuthCode) { |
||||
|
|
||||
|
app.fs.api.logAttr['POST/meta/backups'] = { content: '增加数据备份', visible: true }; |
||||
|
router.post('/meta/backups', backups.addBackups(opts)) |
||||
|
|
||||
|
// 修改数据备份信息
|
||||
|
app.fs.api.logAttr['PUT/meta/backups/:id'] = { content: '修改数据备份信息', visible: true }; |
||||
|
router.put('/meta/backups/:id', backups.editBackups(opts)) |
||||
|
|
||||
|
// 删除数据备份信息
|
||||
|
app.fs.api.logAttr['DEL/meta/backups/:id'] = { content: '删除数据备份信息', visible: true }; |
||||
|
router.del('/meta/backups/:id', backups.deleteBackups(opts)) |
||||
|
|
||||
|
//获取数据备份信息列表
|
||||
|
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)) |
||||
|
}; |
@ -0,0 +1,25 @@ |
|||||
|
create table backups |
||||
|
( |
||||
|
id serial |
||||
|
constraint backups_pk |
||||
|
primary key, |
||||
|
note varchar(255) not null, |
||||
|
databases jsonb, |
||||
|
size varchar(255), |
||||
|
create_time timestamp with time zone, |
||||
|
complete_time timestamp with time zone, |
||||
|
state enum_task_state, |
||||
|
source text |
||||
|
); |
||||
|
|
||||
|
comment on table backups is '备份任务表'; |
||||
|
|
||||
|
comment on column backups.note is '备注信息'; |
||||
|
|
||||
|
comment on column backups.complete_time is '备份完成时间'; |
||||
|
|
||||
|
comment on column backups.source is '备份文件路径'; |
||||
|
|
||||
|
create unique index backups_id_uindex |
||||
|
on backups (id); |
||||
|
|
@ -0,0 +1,4 @@ |
|||||
|
alter table t_resource_consumption |
||||
|
add resource_id int; |
||||
|
|
||||
|
comment on column t_resource_consumption.resource_id is '元数据id'; |
@ -0,0 +1,68 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
import { basicAction } from '@peace/utils' |
||||
|
import { ApiTable } from '$utils' |
||||
|
|
||||
|
export function getBackupsList(query) { |
||||
|
return dispatch => basicAction({ |
||||
|
type: 'get', |
||||
|
dispatch: dispatch, |
||||
|
query: query || {}, |
||||
|
actionType: 'GET_BACKUPS_REPORT', |
||||
|
url: `${ApiTable.getBackupsList}`, |
||||
|
msg: { error: '获取数据备份列表失败' }, |
||||
|
reducer: { name: 'backups' } |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
export function addBackups(params) { |
||||
|
return (dispatch) => basicAction({ |
||||
|
type: 'post', |
||||
|
data: params, |
||||
|
dispatch, |
||||
|
actionType: 'ADD_BACKUPS_REPORT', |
||||
|
url: ApiTable.addBackups, |
||||
|
msg: { |
||||
|
option: '新增数据备份下发', |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function deleteBackups(id) { |
||||
|
return (dispatch) => basicAction({ |
||||
|
type: 'del', |
||||
|
dispatch, |
||||
|
actionType: 'DELETE_BACKUPS_REPORT', |
||||
|
url: ApiTable.modifyBackups.replace('{id}', id), |
||||
|
msg: { |
||||
|
option: '数据备份删除', |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function modifyBackups(id, params, msg) { |
||||
|
return (dispatch) => basicAction({ |
||||
|
type: 'put', |
||||
|
data: params, |
||||
|
dispatch, |
||||
|
actionType: 'MODIFY_BACKUPS_REPORT', |
||||
|
url: ApiTable.modifyBackups.replace('{id}', id), |
||||
|
msg: { |
||||
|
option: msg || '数据备份编辑', |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function restoreBackups(params) { |
||||
|
return (dispatch) => basicAction({ |
||||
|
type: 'post', |
||||
|
data: params, |
||||
|
dispatch, |
||||
|
actionType: 'RESTORE_BACKUPS_REPORT', |
||||
|
url: ApiTable.restoreBackups, |
||||
|
msg: { |
||||
|
option: '备份恢复下发', |
||||
|
}, |
||||
|
}); |
||||
|
} |
@ -0,0 +1,6 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
import * as backups from './backups'; |
||||
|
export default { |
||||
|
...backups |
||||
|
} |
@ -0,0 +1,95 @@ |
|||||
|
import React, { useRef } from 'react'; |
||||
|
import { Button, Form } from 'antd'; |
||||
|
import { InfoCircleOutlined } from '@ant-design/icons'; |
||||
|
import { |
||||
|
ModalForm, |
||||
|
ProFormTreeSelect, |
||||
|
ProFormText, |
||||
|
} from '@ant-design/pro-form'; |
||||
|
import moment from 'moment'; |
||||
|
export default (props) => { |
||||
|
const { title, triggerRender, editData = null, onFinish, dataSources } = props; |
||||
|
const formItemLayout = { labelCol: { span: 6 }, wrapperCol: { span: 16 } }; |
||||
|
const initialValues = editData ? { |
||||
|
...editData, |
||||
|
} : {}; |
||||
|
const [form] = Form.useForm(); |
||||
|
const formRef = useRef(); |
||||
|
return ( |
||||
|
<ModalForm |
||||
|
formRef={formRef} |
||||
|
title={title || ''} |
||||
|
initialValues={initialValues} |
||||
|
trigger={ |
||||
|
triggerRender ? triggerRender : <Button type="primary" > |
||||
|
{title || ''} |
||||
|
</Button> |
||||
|
} |
||||
|
layout="horizontal" |
||||
|
grid={true} |
||||
|
{...formItemLayout} |
||||
|
modalProps={{ |
||||
|
destroyOnClose: true, |
||||
|
onCancel: () => { }, |
||||
|
}} |
||||
|
onFinish={async (values) => { |
||||
|
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} |
||||
|
> |
||||
|
{title != '恢复数据备份' && <ProFormText |
||||
|
rules={[{ required: true, message: '请输入姓名' }, |
||||
|
{ max: 255, message: '姓名长度不能大于255个字符' }, |
||||
|
]} |
||||
|
name="note" |
||||
|
label="备份信息" |
||||
|
/>} |
||||
|
|
||||
|
<ProFormTreeSelect |
||||
|
name="databases" |
||||
|
label='数据源' |
||||
|
placeholder="请选择数据源" |
||||
|
tooltip={title == '恢复数据备份' ? '恢复前请确保恢复数据源数据库为空数据库' : ''} |
||||
|
allowClear |
||||
|
secondary |
||||
|
request={async () => { |
||||
|
return [ |
||||
|
{ |
||||
|
title: 'postgre', |
||||
|
disabled: true, |
||||
|
value: '0-0', |
||||
|
children: dataSources?.rows?.filter(s => (title != '恢复数据备份' && s?.type != '备份数据库') || (title == '恢复数据备份' && s?.type == '备份数据库'))?.map(s => { |
||||
|
return { |
||||
|
title: s.name, |
||||
|
value: s.id, |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
]; |
||||
|
}} |
||||
|
rules={[{ required: true, message: '请选择数据源' }]} |
||||
|
// tree-select args
|
||||
|
fieldProps={{ |
||||
|
showArrow: false, |
||||
|
filterTreeNode: true, |
||||
|
showSearch: true, |
||||
|
popupMatchSelectWidth: false, |
||||
|
labelInValue: true, |
||||
|
autoClearSearchValue: true, |
||||
|
multiple: false, |
||||
|
treeDefaultExpandAll: true, |
||||
|
treeNodeFilterProp: 'title', |
||||
|
fieldNames: { |
||||
|
label: 'title', |
||||
|
}, |
||||
|
}} |
||||
|
/> |
||||
|
|
||||
|
</ModalForm> |
||||
|
); |
||||
|
}; |
@ -0,0 +1,6 @@ |
|||||
|
.step-footer { |
||||
|
display: flex; |
||||
|
justify-content: flex-end; |
||||
|
margin-top: 20px; |
||||
|
width: 100%; |
||||
|
} |
@ -0,0 +1,248 @@ |
|||||
|
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'; |
||||
|
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, 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, |
||||
|
page: search ? 1 : currentPage || 1, |
||||
|
name: searchValue, |
||||
|
} |
||||
|
|
||||
|
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()); |
||||
|
}, []) |
||||
|
|
||||
|
useEffect(() => { |
||||
|
queryData(); |
||||
|
}, [pageSize, currentPage]); |
||||
|
|
||||
|
const columns = [ |
||||
|
{ |
||||
|
title: '序号', |
||||
|
dataIndex: 'index', |
||||
|
render: (text, record, index) => { return index + 1 } |
||||
|
}, |
||||
|
{ |
||||
|
title: '备份信息', |
||||
|
dataIndex: 'note', |
||||
|
}, |
||||
|
{ |
||||
|
title: '备份大小', |
||||
|
dataIndex: 'size', |
||||
|
}, |
||||
|
{ |
||||
|
title: '备份时间', |
||||
|
dataIndex: 'createTime', |
||||
|
render: (text, record) => { return moment(record?.createTime).format('YYYY-MM-DD HH:mm:ss') } |
||||
|
}, |
||||
|
{ |
||||
|
title: '备份完成时间', |
||||
|
dataIndex: 'completeTime', |
||||
|
render: (text, record) => { return record?.completeTime ? moment(record?.completeTime).format('YYYY-MM-DD HH:mm:ss') : '-' } |
||||
|
}, |
||||
|
{ |
||||
|
title: '状态', |
||||
|
dataIndex: 'state', |
||||
|
}, |
||||
|
{ |
||||
|
title: '操作', |
||||
|
width: 160, |
||||
|
key: 'option', |
||||
|
valueType: 'option', |
||||
|
render: (text, record) => { |
||||
|
const options = []; |
||||
|
options.push( |
||||
|
(record?.state != '恢复中' && record?.source) ? <BackupsModal |
||||
|
editData={record} |
||||
|
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> |
||||
|
) |
||||
|
|
||||
|
if (record?.state != '备份中' && record?.state != '恢复中') { |
||||
|
options.push( |
||||
|
<Popconfirm |
||||
|
key="del" |
||||
|
placement="top" |
||||
|
title={<><div>是否确认删除该数据备份?</div> |
||||
|
</>} |
||||
|
onConfirm={() => handleDelete(record.id)} |
||||
|
okText="是" |
||||
|
cancelText="否" |
||||
|
> |
||||
|
<a>删除</a> |
||||
|
</Popconfirm>) |
||||
|
} |
||||
|
|
||||
|
return options; |
||||
|
|
||||
|
}, |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
const handleDelete = (id) => { |
||||
|
dispatch(actions.backups.deleteBackups(id)).then(() => { |
||||
|
queryData(); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
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, |
||||
|
})).then(res => { |
||||
|
setAddLoading(false) |
||||
|
if (res.success) { |
||||
|
queryData(); |
||||
|
return true; |
||||
|
} else { |
||||
|
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}> |
||||
|
<BackupsModal |
||||
|
dataSources={dataSources} |
||||
|
triggerRender={<Button type='primary'>新建</Button>} |
||||
|
title="新建数据备份" |
||||
|
onFinish={onFinish} |
||||
|
key="addModel" |
||||
|
/> |
||||
|
</Col> |
||||
|
<Col span={12} style={{ display: 'flex', justifyContent: 'flex-end', alignItems: 'center' }}> |
||||
|
<span>备份信息: </span> <Input |
||||
|
value={searchValue} onChange={e => { setSearchValue(e.target.value) }} |
||||
|
style={{ width: 220, marginRight: 15 }} placeholder="请输入" /> |
||||
|
|
||||
|
<Button onClick={() => { |
||||
|
setAutoSearchValue(searchValue) |
||||
|
setCurrentPage(1) |
||||
|
setPageSize(10) |
||||
|
queryData(true) |
||||
|
}} type='primary'>查询</Button> |
||||
|
</Col> |
||||
|
|
||||
|
</Row> |
||||
|
<ProTable |
||||
|
columns={columns} |
||||
|
dateFormatter="string" |
||||
|
search={false} |
||||
|
scroll={ |
||||
|
{ |
||||
|
scrollToFirstRowOnChange: true, |
||||
|
y: clientHeight - 260 |
||||
|
} |
||||
|
} |
||||
|
pagination={{ |
||||
|
size: 'large', |
||||
|
total: tableDataFilter?.count, |
||||
|
showSizeChanger: true, |
||||
|
showQuickJumper: true, |
||||
|
current: currentPage, |
||||
|
pageSize: pageSize || 10, |
||||
|
defaultPageSize: 10, |
||||
|
pageSizeOptions: [10, 20, 50], |
||||
|
showTotal: (total) => { |
||||
|
return <span style={{ fontSize: 15 }}>{`共${Math.ceil(total / pageSize)}页,${total}项`}</span> |
||||
|
}, |
||||
|
onShowSizeChange: (currentPage, pageSize) => { |
||||
|
setCurrentPage(currentPage); |
||||
|
setPageSize(pageSize); |
||||
|
|
||||
|
}, |
||||
|
onChange: (page, pageSize) => { |
||||
|
setCurrentPage(page); |
||||
|
setPageSize(pageSize); |
||||
|
|
||||
|
} |
||||
|
}} |
||||
|
dataSource={tableDataFilter?.rows || []} |
||||
|
options={false} |
||||
|
/> |
||||
|
</Spin> |
||||
|
|
||||
|
} |
||||
|
|
||||
|
function mapStateToProps(state) { |
||||
|
const { |
||||
|
auth, global, datasources, backups |
||||
|
} = state; |
||||
|
return { |
||||
|
loading: backups.isRequesting || datasources.isRequesting, |
||||
|
clientHeight: global.clientHeight, |
||||
|
actions: global.actions, |
||||
|
backups: backups?.data || {}, |
||||
|
user: auth.user, |
||||
|
dataSources: datasources?.data || {}, |
||||
|
backupsIsRequesting: backups.isRequesting |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
export default connect(mapStateToProps)(Member); |
||||
|
|
||||
|
|
||||
|
|
@ -0,0 +1,5 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
import Restore from './backupTask'; |
||||
|
|
||||
|
export { Restore }; |
@ -0,0 +1,5 @@ |
|||||
|
.protable-title { |
||||
|
margin-bottom: 16px; |
||||
|
padding-left: 24px; |
||||
|
padding-right: 24px; |
||||
|
} |
@ -0,0 +1,15 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
import reducers from './reducers'; |
||||
|
import routes from './routes'; |
||||
|
import actions from './actions'; |
||||
|
import { getNavItem } from './nav-item'; |
||||
|
|
||||
|
export default { |
||||
|
key: 'backups', |
||||
|
name: '数据备份恢复', |
||||
|
reducers: reducers, |
||||
|
routes: routes, |
||||
|
actions: actions, |
||||
|
getNavItem: getNavItem |
||||
|
}; |
@ -0,0 +1,17 @@ |
|||||
|
import React from 'react'; |
||||
|
import { Link } from 'react-router-dom'; |
||||
|
import { Menu } from 'antd'; |
||||
|
import { ControlOutlined } from '@ant-design/icons'; |
||||
|
const SubMenu = Menu.SubMenu; |
||||
|
|
||||
|
export function getNavItem(user) { |
||||
|
|
||||
|
return ( |
||||
|
user?.role == '系统管理员' && <SubMenu key="数据备份恢复" icon={<ControlOutlined />} title='数据备份恢复'> |
||||
|
<Menu.Item key="backups"> |
||||
|
<Link to="/backups/restore">备份恢复</Link> |
||||
|
</Menu.Item> |
||||
|
</ SubMenu > |
||||
|
) |
||||
|
|
||||
|
} |
@ -0,0 +1,5 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
export default { |
||||
|
|
||||
|
} |
@ -0,0 +1,18 @@ |
|||||
|
'use strict'; |
||||
|
import { Restore } from './containers'; |
||||
|
export default [{ |
||||
|
type: 'inner', |
||||
|
route: { |
||||
|
path: '/backups', |
||||
|
key: 'backups', |
||||
|
breadcrumb: '数据备份恢复', |
||||
|
// 不设置 component 则面包屑禁止跳转
|
||||
|
childRoutes: [{ |
||||
|
path: '/restore', |
||||
|
key: 'restore', |
||||
|
component: Restore, |
||||
|
breadcrumb: '备份恢复' |
||||
|
}] |
||||
|
} |
||||
|
}]; |
||||
|
|
@ -1,69 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
import { basicAction } from '@peace/utils' |
|
||||
import { ApiTable } from '$utils' |
|
||||
|
|
||||
export function addTask(params, msg) { |
|
||||
return (dispatch) => basicAction({ |
|
||||
type: 'post', |
|
||||
data: params, |
|
||||
dispatch, |
|
||||
actionType: 'ADD_ACQ_TASK', |
|
||||
url: ApiTable.addTask, |
|
||||
msg: { |
|
||||
option: msg || '新增采集任务', |
|
||||
}, |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
export function getTasks(query) { |
|
||||
return dispatch => basicAction({ |
|
||||
type: 'get', |
|
||||
dispatch: dispatch, |
|
||||
query: query || {}, |
|
||||
actionType: 'GET_ACQ_TASKS', |
|
||||
url: `${ApiTable.getTasks}`, |
|
||||
msg: { error: '获取采集任务列表失败' }, |
|
||||
reducer: { name: 'tasks' } |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
export function deleteTask(id) { |
|
||||
return (dispatch) => basicAction({ |
|
||||
type: 'del', |
|
||||
dispatch, |
|
||||
actionType: 'DELETE_ACQ_TASK', |
|
||||
url: ApiTable.modifyTask.replace('{id}', id), |
|
||||
msg: { |
|
||||
option: '采集任务删除', |
|
||||
}, |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
export function modifyTask(id, params, msg) { |
|
||||
return (dispatch) => basicAction({ |
|
||||
type: 'put', |
|
||||
data: params, |
|
||||
dispatch, |
|
||||
actionType: 'MODIFY_ACQ_TASK', |
|
||||
url: ApiTable.modifyTask.replace('{id}', id), |
|
||||
msg: { |
|
||||
option: msg || '采集任务编辑', |
|
||||
}, |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
export function runTask(params, msg) { |
|
||||
return (dispatch) => basicAction({ |
|
||||
type: 'post', |
|
||||
data: params, |
|
||||
dispatch, |
|
||||
actionType: 'RUN_ACQ_TASK', |
|
||||
url: ApiTable.runTask, |
|
||||
msg: { |
|
||||
option: msg || '任务执行', |
|
||||
}, |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
|
|
@ -1,5 +1,5 @@ |
|||||
'use strict'; |
'use strict'; |
||||
|
|
||||
import DataSourceManagement from './member'; |
import MemberManagement from './member'; |
||||
|
|
||||
export { DataSourceManagement }; |
export { MemberManagement }; |
||||
|
Loading…
Reference in new issue