Browse Source

(*)备份恢复功能完善

master
peng.peng 1 year ago
parent
commit
af3c1f0993
  1. 2
      api/app/lib/controllers/backups/index.js
  2. 2
      api/app/lib/models/backups.js
  3. 10
      api/app/lib/routes/backups/index.js
  4. 56
      web/client/src/sections/backups/actions/backups.js
  5. 4
      web/client/src/sections/backups/actions/index.js
  6. 56
      web/client/src/sections/backups/actions/member.js
  7. 69
      web/client/src/sections/backups/actions/task.js
  8. 93
      web/client/src/sections/backups/components/backupsModal.js
  9. 78
      web/client/src/sections/backups/components/memberModal.js
  10. 61
      web/client/src/sections/backups/components/resetPassword.js
  11. 75
      web/client/src/sections/backups/containers/backupTask.js
  12. 69
      web/client/src/sections/memberManagement/actions/task.js
  13. 5
      web/client/src/utils/webapi.js

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

@ -8,7 +8,7 @@ function getBackupsList(opts) {
let errMsg = { message: '获取数据备份失败' }
try {
let searchWhere = {
username: { $not: 'SuperAdmin' }
}
let option = {
where: searchWhere,

2
api/app/lib/models/backups.js

@ -62,7 +62,7 @@ module.exports = dc => {
autoIncrement: false
},
state: {
type: user - defined,
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: null,

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

@ -5,19 +5,19 @@ 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.addUser(opts))
router.post('/meta/backups', backups.addBackups(opts))
// 修改数据备份信息
app.fs.api.logAttr['PUT/meta/backups/:id'] = { content: '修改数据备份信息', visible: true };
router.put('/meta/backups/:id', backups.editUser(opts))
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.deleteUser(opts))
router.del('/meta/backups/:id', backups.deleteBackups(opts))
//获取数据备份信息列表
app.fs.api.logAttr['GET/meta/backupss'] = { content: '获取数据备份信息列表', visible: true };
router.get('/meta/backupss', backups.getUserList(opts));
app.fs.api.logAttr['GET/meta/backups'] = { content: '获取数据备份信息列表', visible: true };
router.get('/meta/backups', backups.getBackupsList(opts));
};

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

@ -0,0 +1,56 @@
'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 || '数据备份编辑',
},
});
}

4
web/client/src/sections/backups/actions/index.js

@ -1,6 +1,6 @@
'use strict';
import * as member from './member';
import * as backups from './backups';
export default {
...member
...backups
}

56
web/client/src/sections/backups/actions/member.js

@ -1,56 +0,0 @@
'use strict';
import { basicAction } from '@peace/utils'
import { ApiTable } from '$utils'
export function getUserList(query) {
return dispatch => basicAction({
type: 'get',
dispatch: dispatch,
query: query || {},
actionType: 'GET_MEMBER_REPORT',
url: `${ApiTable.getUserList}`,
msg: { error: '获取用户列表失败' },
reducer: { name: 'member' }
});
}
export function addUser(params) {
return (dispatch) => basicAction({
type: 'post',
data: params,
dispatch,
actionType: 'ADD_MEMBER_REPORT',
url: ApiTable.addUser,
msg: {
option: '用户新增',
},
});
}
export function deleteUser(id) {
return (dispatch) => basicAction({
type: 'del',
dispatch,
actionType: 'DELETE_MEMBER_REPORT',
url: ApiTable.modifyUser.replace('{id}', id),
msg: {
option: '用户删除',
},
});
}
export function modifyUser(id, params, msg) {
return (dispatch) => basicAction({
type: 'put',
data: params,
dispatch,
actionType: 'MODIFY_MEMBER_REPORT',
url: ApiTable.modifyUser.replace('{id}', id),
msg: {
option: msg || '用户编辑',
},
});
}

69
web/client/src/sections/backups/actions/task.js

@ -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 || '任务执行',
},
});
}

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

@ -0,0 +1,93 @@
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 = '备份中';
return onFinish && await onFinish(values, editData, form)
// return true;
}}
width={500}
>
<ProFormText
rules={[{ required: true, message: '请输入姓名' },
{ max: 255, message: '姓名长度不能大于255个字符' },
]}
name="note"
label="备份信息"
/>
<ProFormTreeSelect
name="databases"
label='数据源'
placeholder="请选择数据源"
allowClear
secondary
request={async () => {
return [
{
title: 'postgre',
disabled: true,
value: '0-0',
children: dataSources?.rows?.filter(s => 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>
);
};

78
web/client/src/sections/backups/components/memberModal.js

@ -1,78 +0,0 @@
import React, { useRef } from 'react';
import { Button, Form } from 'antd';
import { InfoCircleOutlined } from '@ant-design/icons';
import {
ModalForm,
ProFormSelect,
ProFormTextArea,
ProFormDigit,
ProFormText,
ProFormSwitch
} from '@ant-design/pro-form';
export default (props) => {
const { title, triggerRender, editData = null, onFinish, paramsName } = 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) => {
return onFinish && await onFinish(values, editData, form)
// return true;
}}
width={500}
>
<ProFormText
rules={[{ required: true, message: '请输入姓名' },
{ max: 255, message: '姓名长度不能大于255个字符' },
]}
name="name"
label="姓名"
/>
<ProFormText
rules={[{ required: true, message: '请输入用户名' },
{ max: 255, message: '用户名长度不能大于255个字符' },
]}
name="username"
label="用户名"
/>
<ProFormSelect
disabled={editData}
rules={[{ required: true, message: '请选择角色' }]}
options={[
{ label: '系统管理员', value: '系统管理员' },
{ label: '数据消费者', value: '数据消费者' },
]}
name="role"
label="角色"
/>
<ProFormSwitch name="enabled" label="是否启用"
fieldProps={
{ defaultChecked: true }
}
/>
</ModalForm>
);
};

61
web/client/src/sections/backups/components/resetPassword.js

@ -1,61 +0,0 @@
import React, { useRef } from 'react';
import { Button, Form } from 'antd';
import {
ModalForm,
ProFormText,
} from '@ant-design/pro-form';
export default (props) => {
const { title, triggerRender, editData = null, onFinish } = props;
const formItemLayout = { labelCol: { span: 6 }, wrapperCol: { span: 16 } };
const initialValues = {};
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) => {
return onFinish && await onFinish({ ...values, msg: '重置密码' }, editData, '重置密码')
}}
width={500}
>
<ProFormText.Password
rules={[{ required: true, message: '请输入旧密码' },
{ max: 255, message: '旧密码长度不能大于255个字符' },
{
pattern: /^[a-z0-9A-Z]{6,20}$/, message: '密码由6-20位字母或数字组成'
},
]}
name="oldpassword"
label="旧密码"
/>
<ProFormText.Password
rules={[{ required: true, message: '请输入新密码' },
{ max: 255, message: '新密码长度不能大于255个字符' },
{
pattern: /^[a-z0-9A-Z]{6,20}$/, message: '密码由6-20位字母或数字组成'
},
]}
name="password"
label="新密码"
/>
</ModalForm>
);
};

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

@ -3,12 +3,11 @@ 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 MemberModal from '../components/memberModal';
import ResetPasswordModal from '../components/resetPassword';
import { useFsRequest, ApiTable } from '$utils';
import BackupsModal from '../components/backupsModal';
import './style.less';
function Member(props) {
const { loading, clientHeight, actions, dispatch, member, user } = props;
const { loading, clientHeight, actions, dispatch, backups, user, dataSources } = props;
const [pageSize, setPageSize] = useState(10);
const [currentPage, setCurrentPage] = useState(1);
const [searchValue, setSearchValue] = useState('')
@ -19,9 +18,12 @@ function Member(props) {
name: searchValue,
}
dispatch(actions.memberManagement.getUserList(query));
dispatch(actions.backups.getBackupsList(query));
}
useEffect(() => {
dispatch(actions.metadataAcquisition.getDataSources());
}, [])
useEffect(() => {
queryData();
@ -44,13 +46,11 @@ function Member(props) {
{
title: '备份时间',
dataIndex: 'createTime',
render: (text, record) => { return moment(record?.createTime).format('YYYY-MM-DD HH:mm:ss') }
},
{
title: '状态',
dataIndex: 'state',
render: (text, record) => {
return <span style={{ color: record?.enabled ? '#87d068' : '#f50' }}> {record?.enabled ? '正常' : '禁用'}</span>
}
},
{
title: '操作',
@ -60,34 +60,35 @@ function Member(props) {
render: (text, record) => {
const options = [];
options.push(
<Popconfirm
key="del"
placement="top"
title={<><div>是否确认删除该用户</div>
title={<><div>是否确认重置该数据备份密码</div>
</>}
onConfirm={() => handleDelete(record.id)}
onConfirm={() => {
dispatch(actions.backups.modifyBackups(record.id, { password: 'e10adc3949ba59abbe56e057f20f883e' }, '重置密码'))
}}
okText="是"
cancelText="否"
>
<a>删除</a>
<a>恢复</a>
</Popconfirm>)
user?.username == 'SuperAdmin' && options.push(
options.push(<a>下载</a>)
options.push(
<Popconfirm
key="del"
placement="top"
title={<><div>是否确认重置该用户密码</div>
title={<><div>是否确认删除该数据备份</div>
</>}
onConfirm={() => {
dispatch(actions.memberManagement.modifyUser(record.id, { password: 'e10adc3949ba59abbe56e057f20f883e' }, '重置密码'))
}}
onConfirm={() => handleDelete(record.id)}
okText="是"
cancelText="否"
>
<a>重置密码</a>
<a>删除</a>
</Popconfirm>)
return options;
},
@ -95,27 +96,13 @@ function Member(props) {
];
const handleDelete = (id) => {
dispatch(actions.memberManagement.deleteUser(id)).then(() => {
dispatch(actions.backups.deleteBackups(id)).then(() => {
queryData();
});
};
const onFinish = async (values, editData) => {
if (editData) {
const dataToSave = { ...values }
return dispatch(
actions.memberManagement.modifyUser(editData.id, dataToSave, values?.msg || ''),
).then((res) => {
if (res.success) {
queryData();
return true;
} else {
return false;
}
});
}
return dispatch(actions.memberManagement.addUser({
return dispatch(actions.backups.addBackups({
...values,
})).then(res => {
if (res.success) {
@ -130,9 +117,10 @@ function Member(props) {
return <Spin spinning={loading}>
<Row className='protable-title'>
<Col span={12}>
<MemberModal
<BackupsModal
dataSources={dataSources}
triggerRender={<Button type='primary'>新建</Button>}
title="新建用户"
title="新建数据备份"
onFinish={onFinish}
key="addModel"
/>
@ -161,7 +149,7 @@ function Member(props) {
}
pagination={{
size: 'large',
total: member?.count,
total: backups?.count,
showSizeChanger: true,
showQuickJumper: true,
current: currentPage,
@ -182,7 +170,7 @@ function Member(props) {
}
}}
dataSource={member?.rows || []}
dataSource={backups?.rows || []}
options={false}
/>
</Spin>
@ -191,14 +179,15 @@ function Member(props) {
function mapStateToProps(state) {
const {
auth, global, datasources, member
auth, global, datasources, backups
} = state;
return {
loading: datasources.isRequesting,
loading: backups.isRequesting || datasources.isRequesting,
clientHeight: global.clientHeight,
actions: global.actions,
member: member?.data || {},
user: auth.user
backups: backups?.data || {},
user: auth.user,
dataSources: datasources?.data || {},
};
}

69
web/client/src/sections/memberManagement/actions/task.js

@ -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 || '任务执行',
},
});
}

5
web/client/src/utils/webapi.js

@ -84,6 +84,11 @@ export const ApiTable = {
//元数据检索
searchMetadata: "meta/data/search",
//备份恢复
getBackupsList: 'meta/backups',
addBackups: 'meta/backups',
modifyBackups: 'meta/backups/{id}',
};
export const RouteTable = {

Loading…
Cancel
Save