Browse Source

(*)用户权限功能完善

master
peng.peng 2 years ago
parent
commit
62ee16a25c
  1. 3
      api/app/lib/controllers/auth/index.js
  2. 53
      api/app/lib/controllers/member/index.js
  3. 28
      web/client/src/layout/components/header/index.js
  4. 44
      web/client/src/sections/memberManagement/actions/adapter.js
  5. 56
      web/client/src/sections/memberManagement/actions/dataSource.js
  6. 15
      web/client/src/sections/memberManagement/actions/example.js
  7. 12
      web/client/src/sections/memberManagement/actions/index.js
  8. 16
      web/client/src/sections/memberManagement/actions/log.js
  9. 56
      web/client/src/sections/memberManagement/actions/member.js
  10. 7
      web/client/src/sections/memberManagement/components/memberModal.js
  11. 61
      web/client/src/sections/memberManagement/components/resetPassword.js
  12. 108
      web/client/src/sections/memberManagement/containers/member.js
  13. 2
      web/client/src/sections/memberManagement/index.js
  14. 8
      web/client/src/sections/memberManagement/nav-item.js
  15. 2
      web/client/src/sections/memberManagement/routes.js
  16. 7
      web/client/src/utils/webapi.js

3
api/app/lib/controllers/auth/index.js

@ -19,8 +19,7 @@ async function login(ctx, next) {
attributes: { exclude: ['password'] },
where: {
username: params.username,
password: password,
enabled: true
password: password
},
});
}

53
api/app/lib/controllers/member/index.js

@ -1,5 +1,6 @@
'use strict';
const moment = require('moment')
const Hex = require('crypto-js/enc-hex');
const MD5 = require('crypto-js/md5');
function getUserList(opts) {
return async function (ctx, next) {
@ -17,7 +18,7 @@ function getUserList(opts) {
}
if (name) {
searchWhere.name = { $like: name };
searchWhere.name = { $like: '%' + name + '%' };
}
if (role) {
@ -25,7 +26,6 @@ function getUserList(opts) {
}
option.where = searchWhere
let total = await models.User.findAll(option);
let limit_ = limit || 10;
let page_ = page || 1;
let offset = (page_ - 1) * limit_;
@ -51,14 +51,14 @@ function addUser(opts) {
const models = ctx.fs.dc.models;
try {
const { attributeName, attributeCode, modelType } = ctx.request.body
const checkName = await models.User.findOne({ where: { attributeName, modelType } });
const checkCode = await models.User.findOne({ where: { attributeCode, modelType } });
if (checkName || checkCode) {
const { username } = ctx.request.body
const checkName = await models.User.findOne({ where: { username } });
if (checkName) {
ctx.status = 400;
ctx.body = { message: checkName ? '该属性名称已存在' : "该属性代码已存在" }
ctx.body = { message: "该用户名已存在" }
} else {
let rslt = ctx.request.body;
rslt.password = 'e10adc3949ba59abbe56e057f20f883e';
await models.User.create(Object.assign({}, rslt))
ctx.status = 204;
ctx.body = { message: '新建用户成功' }
@ -80,14 +80,25 @@ function editUser(opts) {
const models = ctx.fs.dc.models;
const { id } = ctx.params;
const body = ctx.request.body;
let modelFind = await models.User.findOne({ where: { id: id } });
const { attributeName, attributeCode } = ctx.request.body;
const { modelType } = modelFind;
const checkName = await models.User.findOne({ where: { id: { $not: id }, attributeName, modelType } });
const checkCode = await models.User.findOne({ where: { id: { $not: id }, attributeCode, modelType } });
if (checkName || checkCode) {
if (body.oldpassword) {
const password = Hex.stringify(MD5(body.oldpassword));
const checkPwd = await models.User.findOne({ where: { id: id, password } });
if (!checkPwd) {
ctx.status = 400;
ctx.body = { message: checkName ? '该属性名称已存在' : "该属性代码已存在" }
ctx.body = { message: '旧密码错误' }
} else {
await models.User.update(
{ password: Hex.stringify(MD5(body.password)) },
{ where: { id: id, } }
)
ctx.status = 204;
ctx.body = { message: '修改用户成功' }
}
} else {
const checkName = await models.User.findOne({ where: { id: { $not: id }, username: body.username } });
if (checkName) {
ctx.status = 400;
ctx.body = { message: '该用户名已存在' }
} else {
await models.User.update(
body,
@ -97,6 +108,8 @@ function editUser(opts) {
ctx.body = { message: '修改用户成功' }
}
}
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
@ -113,6 +126,14 @@ function deleteUser(opts) {
try {
const models = ctx.fs.dc.models;
const { id } = ctx.params;
const checkName1 = await models.MetadataDatabase.findOne({ where: { createBy: id } });
const checkName2 = await models.MetadataFile.findOne({ where: { createBy: id } });
const checkName3 = await models.MetadataRestapi.findOne({ where: { createBy: id } });
if (checkName1 || checkName2 || checkName3) {
ctx.status = 400;
ctx.body = { message: '该用户下存在依赖资源无法删除!' }
} else {
await models.User.destroy({
where: {
id: id
@ -120,6 +141,8 @@ function deleteUser(opts) {
})
ctx.status = 204;
ctx.body = { message: '删除用户成功' }
}
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;

28
web/client/src/layout/components/header/index.js

@ -7,10 +7,23 @@ import styles from './style.css';
import {
MenuFoldOutlined, MenuUnfoldOutlined, UserOutlined, LogoutOutlined
} from '@ant-design/icons';
import ResetPasswordModal from '../../../sections/memberManagement/components/resetPassword';
const Header = props => {
const { dispatch, history, user, pathname, toggleCollapsed, collapsed, actions } = props
const onFinish = async (values) => {
const dataToSave = { ...values }
return dispatch(
actions.memberManagement.modifyUser(user.id, dataToSave, values?.msg || ''),
).then((res) => {
if (res.success) {
return true;
} else {
return false;
}
});
};
const handelClick = item => {
if (item.key == 'logout') {
dispatch(actions.auth.logout(user));
@ -55,7 +68,18 @@ const Header = props => {
icon: <img className={styles['header-nav-user-img']} src={`/assets/images/avatar/5.png`} />,
children: [{
label: '退出', key: 'logout'
}],
},
{
label: <ResetPasswordModal
editData={user}
triggerRender={<a>重置密码</a>}
title="重置密码"
onFinish={onFinish}
key="resetPassword"
/>,
key: 'resetPassword'
}
],
}]}
/>
</div>

44
web/client/src/sections/memberManagement/actions/adapter.js

@ -1,44 +0,0 @@
'use strict';
import { basicAction } from '@peace/utils'
import { ApiTable } from '$utils'
export function checkPostgreConnect(params) {
return (dispatch) => basicAction({
type: 'post',
data: params,
dispatch,
actionType: 'CHECK_POSTGRE_CONNECT',
url: ApiTable.pgCheckConnect,
msg: {
option: '',
},
});
}
export function addAdapter(params) {
return (dispatch) => basicAction({
type: 'post',
data: params,
dispatch,
actionType: 'ADD_ADAPTER',
url: ApiTable.addAdapter,
msg: {
option: '新增适配器',
},
});
}
export function getAdapters(query) {
return dispatch => basicAction({
type: 'get',
dispatch: dispatch,
query: query || {},
actionType: 'GET_ACQ_ADAPTERS',
url: `${ApiTable.getAdapters}`,
msg: { error: '获取适配器列表失败' },
reducer: { name: 'adapters' }
});
}

56
web/client/src/sections/memberManagement/actions/dataSource.js

@ -1,56 +0,0 @@
'use strict';
import { basicAction } from '@peace/utils'
import { ApiTable } from '$utils'
export function addDataSource(params, msg) {
return (dispatch) => basicAction({
type: 'post',
data: params,
dispatch,
actionType: 'ADD_DATSOURCE',
url: ApiTable.addDataSource,
msg: {
option: msg == '' ? '' : '新增数据源',
},
});
}
export function getDataSources(query) {
return dispatch => basicAction({
type: 'get',
dispatch: dispatch,
query: query || {},
actionType: 'GET_ACQ_DATASOURCES',
url: `${ApiTable.getDataSources}`,
msg: { error: '获取数据源列表失败' },
reducer: { name: 'datasources' }
});
}
export function deleteDataSource(id) {
return (dispatch) => basicAction({
type: 'del',
dispatch,
actionType: 'DELETE_ACQ_DATASOURCE',
url: ApiTable.modifyDataSource.replace('{id}', id),
msg: {
option: '数据源删除',
},
});
}
export function modifyDataSource(id, params, msg) {
return (dispatch) => basicAction({
type: 'put',
data: params,
dispatch,
actionType: 'MODIFY_ACQ_DATASOURCE',
url: ApiTable.modifyDataSource.replace('{id}', id),
msg: {
option: msg || '数据源编辑',
},
});
}

15
web/client/src/sections/memberManagement/actions/example.js

@ -1,15 +0,0 @@
'use strict';
import { basicAction } from '@peace/utils'
import { ApiTable } from '$utils'
// export function getMembers(orgId) {
// return dispatch => basicAction({
// type: 'get',
// dispatch: dispatch,
// actionType: 'GET_MEMBERS',
// url: `${ApiTable.getEnterprisesMembers.replace('{enterpriseId}', orgId)}`,
// msg: { error: '获取用户列表失败' },
// reducer: { name: 'members' }
// });
// }

12
web/client/src/sections/memberManagement/actions/index.js

@ -1,14 +1,6 @@
'use strict';
import * as example from './example'
import * as adapter from './adapter'
import * as dataSource from './dataSource'
import * as task from './task';
import * as log from './log';
import * as member from './member';
export default {
...example,
...adapter,
...dataSource,
...task,
...log
...member
}

16
web/client/src/sections/memberManagement/actions/log.js

@ -1,16 +0,0 @@
'use strict';
import { basicAction } from '@peace/utils'
import { ApiTable } from '$utils'
export function getLogs(query) {
return dispatch => basicAction({
type: 'get',
dispatch: dispatch,
query: query || {},
actionType: 'GET_ACQ_LOGS',
url: `${ApiTable.getLogs}`,
msg: { error: '获取采集日志失败' },
reducer: { name: 'acqlogs' }
});
}

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

@ -0,0 +1,56 @@
'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 || '用户编辑',
},
});
}

7
web/client/src/sections/memberManagement/components/memberModal.js

@ -58,6 +58,7 @@ export default (props) => {
/>
<ProFormSelect
disabled={editData}
rules={[{ required: true, message: '请选择角色' }]}
options={[
{ label: '系统管理员', value: '系统管理员' },
@ -67,7 +68,11 @@ export default (props) => {
label="角色"
/>
<ProFormSwitch name="enabled" label="是否启用" />
<ProFormSwitch name="enabled" label="是否启用"
fieldProps={
{ defaultChecked: true }
}
/>
</ModalForm>
);
};

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

@ -0,0 +1,61 @@
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>
);
};

108
web/client/src/sections/memberManagement/containers/member.js

@ -4,10 +4,11 @@ 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 './style.less';
function Member(props) {
const { loading, clientHeight, actions, dispatch, dataSources, adapters } = props;
const { loading, clientHeight, actions, dispatch, member } = props;
const [pageSize, setPageSize] = useState(10);
const [currentPage, setCurrentPage] = useState(1);
const [searchValue, setSearchValue] = useState('')
@ -19,10 +20,11 @@ function Member(props) {
const query = {
limit: search ? 10 : pageSize || 10,
page: search ? 1 : currentPage || 1,
name: searchValue
name: searchValue,
role: searchRole
}
dispatch(actions.metadataAcquisition.getDataSources(query));
dispatch(actions.memberManagement.getUserList(query));
}
const { data: treeData = [] } = useFsRequest({
url: ApiTable.getResourceCatalog,
@ -30,16 +32,9 @@ function Member(props) {
});
useEffect(() => {
dispatch(actions.metadataAcquisition.getAdapters())
queryData();
}, [pageSize, currentPage]);
const handleDelete = (id) => {
dispatch(actions.metadataAcquisition.deleteDataSource(id)).then(() => {
queryData();
});
};
const columns = [
{
title: '序号',
@ -61,6 +56,9 @@ function Member(props) {
{
title: '状态',
dataIndex: 'enabled',
render: (text, record) => {
return <span style={{ color: record?.enabled ? '#87d068' : '#f50' }}> {record?.enabled ? '正常' : '禁用'}</span>
}
},
{
title: '操作',
@ -69,65 +67,71 @@ function Member(props) {
valueType: 'option',
render: (text, record) => {
const options = [];
options.push(<a onClick={() => {
setVisible(true)
setEditData(record)
}} style={{ marginRight: 8 }}>编辑</a>)
options.push(<MemberModal
editData={record}
triggerRender={<a>编辑</a>}
title="编辑用户"
onFinish={onFinish}
key="editUser"
/>)
options.push(
<Popconfirm
key="del"
placement="top"
title={<><div>是否确认删除该数据源</div>
<div>(将同步删除数据源下的元数据)</div></>}
title={<><div>是否确认删除该用户</div>
</>}
onConfirm={() => handleDelete(record.id)}
okText="是"
cancelText="否"
>
<a>删除</a>
</Popconfirm>)
options.push(<ResetPasswordModal
editData={record}
triggerRender={<a>重置密码</a>}
title="重置密码"
onFinish={onFinish}
key="resetPassword"
/>)
return options;
},
},
];
const onFinish = (values) => {
const { stepOneValues, stepTwoValues, } = values;
const adapterInfo = adapters?.find(x => x.adapterName == stepOneValues?.adapterName)
if (adapterInfo) {
const dataToSave = {
name: stepOneValues?.name,
audited: true,
adapterId: adapterInfo?.id,
mountPath: stepOneValues?.mountPath,
catalogKey: stepOneValues?.catalogKey,
description: stepOneValues?.description,
config: stepTwoValues,
time: moment()
}
const handleDelete = (id) => {
dispatch(actions.memberManagement.deleteUser(id)).then(() => {
queryData();
});
};
const onFinish = async (values, editData) => {
if (editData) {
dispatch(actions.metadataAcquisition.modifyDataSource(editData?.id, dataToSave)).then(res => {
const dataToSave = { ...values }
return dispatch(
actions.memberManagement.modifyUser(editData.id, dataToSave, values?.msg || ''),
).then((res) => {
if (res.success) {
setVisible(false);
setEditData(null);
queryData();
}
})
return true;
} else {
dispatch(actions.metadataAcquisition.addDataSource(dataToSave)).then(res => {
if (res.success) {
setVisible(false);
setEditData(null);
queryData();
}
})
}
return false;
}
});
}
const refresh = () => {
// queryData();
setRefreshTree(refreshTree + 1)
return dispatch(actions.memberManagement.addUser({
...values,
})).then(res => {
if (res.success) {
queryData();
return true;
} else {
return false;
}
});
};
return <Spin spinning={loading}>
<Row className='protable-title'>
@ -145,6 +149,7 @@ function Member(props) {
style={{ width: 220, marginRight: 15 }} placeholder="请输入" />
<span>角色 </span> <Select
allowClear
value={searchRole} onChange={e => { setSearchRole(e) }}
style={{ width: 220, marginRight: 15 }} placeholder="请选择" >
<Select.Option value={'系统管理员'}>系统管理员</Select.Option>
@ -171,7 +176,7 @@ function Member(props) {
//1490 2256
pagination={{
size: 'large',
total: dataSources?.count,
total: member?.count,
showSizeChanger: true,
showQuickJumper: true,
current: currentPage,
@ -192,7 +197,7 @@ function Member(props) {
}
}}
dataSource={dataSources?.rows || []}
dataSource={member?.rows || []}
options={false}
/>
</Spin>
@ -201,14 +206,13 @@ function Member(props) {
function mapStateToProps(state) {
const {
auth, global, datasources, adapters
auth, global, datasources, member
} = state;
return {
loading: datasources.isRequesting || adapters?.isRequesting,
loading: datasources.isRequesting,
clientHeight: global.clientHeight,
actions: global.actions,
dataSources: datasources?.data || {},
adapters: adapters?.data || []
member: member?.data || {},
};
}

2
web/client/src/sections/memberManagement/index.js

@ -7,7 +7,7 @@ import { getNavItem } from './nav-item';
export default {
key: 'memberManagement',
name: '元数据采集',
name: '用户管理',
reducers: reducers,
routes: routes,
actions: actions,

8
web/client/src/sections/memberManagement/nav-item.js

@ -4,12 +4,14 @@ import { Menu } from 'antd';
import { BarChartOutlined } from '@ant-design/icons';
const SubMenu = Menu.SubMenu;
export function getNavItem() {
export function getNavItem(user) {
return (
<SubMenu key="memberManagement" icon={<BarChartOutlined />} title='用户管理'>
user?.role == '系统管理员' && <SubMenu key="memberManagement" icon={<BarChartOutlined />} title='用户管理'>
<Menu.Item key="auth">
<Link to="/memberManagement/auth">用户权限</Link>
</Menu.Item>
</ SubMenu >
);
)
}

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

@ -5,7 +5,7 @@ export default [{
route: {
path: '/memberManagement',
key: 'memberManagement',
breadcrumb: '元数据采集',
breadcrumb: '用户管理',
// 不设置 component 则面包屑禁止跳转
childRoutes: [{
path: '/auth',

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

@ -73,7 +73,12 @@ export const ApiTable = {
runTask: 'run/acq/task',
//采集日志
getLogs: "meta/acq/logs"
getLogs: "meta/acq/logs",
//用户管理
getUserList: 'meta/members',
addUser: 'meta/member',
modifyUser: 'meta/member/{id}',
};
export const RouteTable = {

Loading…
Cancel
Save