wenlele 2 years ago
parent
commit
c6966f78f6
  1. 4
      api/app/lib/controllers/auth/index.js
  2. 23
      api/app/lib/controllers/latestMetadata/index.js
  3. 3
      api/app/lib/controllers/metadataAcquisition/taskHandle.js
  4. 37
      api/app/lib/controllers/metadataSearch/index.js
  5. 10
      api/app/lib/controllers/resourceConsumption/index.js
  6. 13
      api/app/lib/models/data_source.js
  7. 9
      api/app/lib/models/metadata_database.js
  8. 9
      api/app/lib/models/resource_consumption.js
  9. 7
      api/app/lib/routes/metadataSearch/index.js
  10. 4
      scripts/0.0.7/01_alter_t_resource_consumption.sql
  11. 9
      scripts/0.0.7/02_alter_t_data_source.sql
  12. 111
      web/client/src/sections/metadataAcquisition/components/steps/postgre/stepOne.js
  13. 2
      web/client/src/sections/metadataAcquisition/components/steps/postgre/stepThree.js
  14. 1
      web/client/src/sections/metadataAcquisition/containers/adapter.js
  15. 1
      web/client/src/sections/metadataAcquisition/containers/dataSourceManagement.js
  16. 8
      web/client/src/sections/metadataManagement/containers/databasesTable.js
  17. 8
      web/client/src/sections/metadataManagement/containers/filesTable.js
  18. 38
      web/client/src/sections/metadataManagement/containers/latestMetadata.js
  19. 13
      web/client/src/sections/metadataManagement/containers/metadataTab.js
  20. 8
      web/client/src/sections/metadataManagement/containers/restapisTable.js
  21. 46
      web/client/src/sections/resourceRetrieval/components/keyModal.js
  22. 122
      web/client/src/sections/resourceRetrieval/containers/retrieval.js
  23. 37
      web/client/src/sections/resourceRetrieval/utils/index.js

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

@ -19,12 +19,12 @@ async function login (ctx, next) {
attributes: { exclude: ['password'] }, attributes: { exclude: ['password'] },
where: { where: {
username: params.username, username: params.username,
password: password password: password,
}, },
}); });
} }
if (userRes) { if (userRes) {
if (userRes.forbidden) { if (!userRes.enabled) {
throw '用户已禁用' throw '用户已禁用'
} else { } else {
const token = uuid.v4(); const token = uuid.v4();

23
api/app/lib/controllers/latestMetadata/index.js

@ -120,7 +120,7 @@ async function delResourceCatalog(ctx) {
async function getMetadataDatabases(ctx) { async function getMetadataDatabases(ctx) {
try { try {
const models = ctx.fs.dc.models; const models = ctx.fs.dc.models;
const { catalog, limit, offset, keywords, orderBy = 'createAt', orderDirection = 'desc', id = null } = ctx.query; const { catalog, limit, offset, keywords, orderBy = 'createAt', orderDirection = 'desc', id = null, resourceId } = ctx.query;
const where = {}; const where = {};
if (catalog) { if (catalog) {
where.catalog = catalog; where.catalog = catalog;
@ -128,6 +128,10 @@ async function getMetadataDatabases(ctx) {
if (id) { if (id) {
where.parent = id; where.parent = id;
} }
if (resourceId) {
where.id = resourceId;
}
if (keywords) { if (keywords) {
where['$or'] = [{ name: { $iLike: `%${keywords}%` } }, where['$or'] = [{ name: { $iLike: `%${keywords}%` } },
{ code: { $iLike: `%${keywords}%` } }, { code: { $iLike: `%${keywords}%` } },
@ -168,13 +172,16 @@ async function getMetadataDatabases(ctx) {
async function getMetadataFiles(ctx) { async function getMetadataFiles(ctx) {
try { try {
const models = ctx.fs.dc.models; const models = ctx.fs.dc.models;
const { catalog, limit, offset, keywords, orderBy = 'updateAt', orderDirection = 'desc' } = ctx.query; const { catalog, limit, offset, keywords, orderBy = 'updateAt', orderDirection = 'desc', resourceId } = ctx.query;
const where = { catalog: catalog }; const where = { catalog: catalog };
//文件类型关键字查询时需匹配fileName不能为空。 //文件类型关键字查询时需匹配fileName不能为空。
//因存在编辑时将文件删除,但未重新上传直接点击取消的情况,此时文件已删除不可恢复,数据字段fileName更新为null //因存在编辑时将文件删除,但未重新上传直接点击取消的情况,此时文件已删除不可恢复,数据字段fileName更新为null
if (keywords) { if (keywords) {
where['$or'] = [{ name: { $iLike: `%${keywords}%` } }, { type: { $iLike: `%${keywords}%` }, fileName: { $not: null } }] where['$or'] = [{ name: { $iLike: `%${keywords}%` } }, { type: { $iLike: `%${keywords}%` }, fileName: { $not: null } }]
} }
if (resourceId) {
where.id = resourceId;
}
const findObj = { const findObj = {
include: [ include: [
// { // {
@ -210,11 +217,14 @@ async function getMetadataFiles(ctx) {
async function getMetadataRestapis(ctx) { async function getMetadataRestapis(ctx) {
try { try {
const models = ctx.fs.dc.models; const models = ctx.fs.dc.models;
const { catalog, limit, offset, keywords, orderBy = 'createAt', orderDirection = 'desc' } = ctx.query; const { catalog, limit, offset, keywords, orderBy = 'createAt', orderDirection = 'desc', resourceId } = ctx.query;
const where = { catalog: catalog }; const where = { catalog: catalog };
if (keywords) { if (keywords) {
where.name = { $iLike: `%${keywords}%` }; where.name = { $iLike: `%${keywords}%` };
} }
if (resourceId) {
where.id = resourceId;
}
const findObj = { const findObj = {
include: [ include: [
{ {
@ -366,6 +376,7 @@ async function delMetadataDatabases(ctx) {
} else { } else {
let resourceConsumptionInfo = await models.ResourceConsumption.findOne({ let resourceConsumptionInfo = await models.ResourceConsumption.findOne({
where: { where: {
resourceId: id,
resourceName: metadataDatabaseInfo.name, resourceName: metadataDatabaseInfo.name,
resourceType: '库表' resourceType: '库表'
} }
@ -516,14 +527,14 @@ async function getTagMetadata(ctx) {
//申请资源 //申请资源
async function postMetadataResourceApplications(ctx) { async function postMetadataResourceApplications(ctx) {
try { try {
const { resourceName, applyBy, resourceType } = ctx.request.body; const { resourceName, applyBy, resourceType, resourceId } = ctx.request.body;
if (!resourceName || !applyBy || !resourceType) { if (!resourceName || !applyBy || !resourceType || !resourceId) {
ctx.status = 400; ctx.status = 400;
ctx.body = { message: '参数不全,请重新申请资源' } ctx.body = { message: '参数不全,请重新申请资源' }
} else { } else {
const models = ctx.fs.dc.models; const models = ctx.fs.dc.models;
const postOne = await models.ResourceConsumption.findOne({ const postOne = await models.ResourceConsumption.findOne({
where: { applyBy: applyBy, resourceName: resourceName } where: { applyBy: applyBy, resourceName: resourceName, resourceId, resourceType }
}); });
if (postOne) { if (postOne) {
ctx.status = 400; ctx.status = 400;

3
api/app/lib/controllers/metadataAcquisition/taskHandle.js

@ -75,6 +75,8 @@ async function handleTask(app, task) {
dataToSave.name = table; dataToSave.name = table;
dataToSave.code = table; dataToSave.code = table;
dataToSave.type = '表'; dataToSave.type = '表';
dataToSave.datasourceConfig = dataSource.config;
const tableObj = { ...dataToSave } const tableObj = { ...dataToSave }
tableBodys.push(tableObj) tableBodys.push(tableObj)
} }
@ -93,6 +95,7 @@ async function handleTask(app, task) {
dataToSave.name = table; dataToSave.name = table;
dataToSave.code = table; dataToSave.code = table;
dataToSave.type = '表'; dataToSave.type = '表';
dataToSave.datasourceConfig = dataSource.config;
const tableObj = { ...dataToSave } const tableObj = { ...dataToSave }
tableBodys.push(tableObj) tableBodys.push(tableObj)
}); });

37
api/app/lib/controllers/metadataSearch/index.js

@ -1,4 +1,5 @@
'use strict'; 'use strict';
const { Pool } = require('pg');
function searchMeta(opts) { function searchMeta(opts) {
return async function (ctx, next) { return async function (ctx, next) {
@ -52,7 +53,7 @@ function searchMeta(opts) {
const rslt2 = await models.MetadataFile.findAndCountAll(findObj2); const rslt2 = await models.MetadataFile.findAndCountAll(findObj2);
const rslt3 = await models.MetadataRestapi.findAndCountAll(findObj3); const rslt3 = await models.MetadataRestapi.findAndCountAll(findObj3);
const fileRslt = rslt2.rows.map(s => { return { ...s.dataValues, type: '文件', tagDatabases: s.tagFiles } }) const fileRslt = rslt2.rows.map(s => { return { ...s.dataValues, type: '文件', tagDatabases: s.tagFiles } })
const restapiRslt = rslt3.rows.map(s => { return { ...s.dataValues, type: '接口', tagDatabases: s.tagRestapis } }) const restapiRslt = rslt3.rows.map(s => { return { ...s.dataValues, type: '接口', tagDatabases: s.tagRestapis } })
ctx.status = 200; ctx.status = 200;
ctx.body = { ctx.body = {
count: rslt1.count + rslt2.count + rslt3.count, count: rslt1.count + rslt2.count + rslt3.count,
@ -66,6 +67,38 @@ function searchMeta(opts) {
} }
} }
// 新增模型
function getTableData(opts) {
return async function (ctx, next) {
const models = ctx.fs.dc.models;
try {
const { id } = ctx.query;
let { user, host, database, password, port } = ctx.request.body;
const pool = new Pool({
user: user,
host: host,
database: database,
password: password,
port: port,
})
const client = await pool.connect()
const tableName = "user"
const ress = await client.query(`SELECT * from "${tableName}"`, [])
console.log(ress.rows)
ctx.status = 200;
ctx.body = { rslt: ress.rows }
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 200;
ctx.body = { message: '连接失败' }
}
}
}
module.exports = { module.exports = {
searchMeta searchMeta,
getTableData
} }

10
api/app/lib/controllers/resourceConsumption/index.js

@ -1,11 +1,11 @@
'use strict'; 'use strict';
const moment = require('moment') const moment = require('moment')
function getApproveList (opts) { function getApproveList(opts) {
return async function (ctx, next) { return async function (ctx, next) {
const models = ctx.fs.dc.models; const models = ctx.fs.dc.models;
const { page, limit, applyAt, approveState, resourceName, applyBy, applyById, state } = ctx.query; const { page, limit, applyAt, approveState, resourceName, applyBy, applyById, state ,} = ctx.query;
let errMsg = { message: '获取消费审批列表失败' } let errMsg = { message: '获取消费审批列表失败' }
try { try {
@ -81,7 +81,7 @@ function getApproveList (opts) {
} }
// 新增模型 // 新增模型
function addModelManagement (opts) { function addModelManagement(opts) {
return async function (ctx, next) { return async function (ctx, next) {
const models = ctx.fs.dc.models; const models = ctx.fs.dc.models;
@ -108,7 +108,7 @@ function addModelManagement (opts) {
} }
// 修改模型 // 修改模型
function postApprove (opts) { function postApprove(opts) {
return async function (ctx, next) { return async function (ctx, next) {
try { try {
@ -132,7 +132,7 @@ function postApprove (opts) {
} }
// 删除模型 // 删除模型
function deleteModelManagement (opts) { function deleteModelManagement(opts) {
return async function (ctx, next) { return async function (ctx, next) {
try { try {

13
api/app/lib/models/data_source.js

@ -49,7 +49,7 @@ module.exports = dc => {
}, },
mountPath: { mountPath: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false, allowNull: true,
defaultValue: null, defaultValue: null,
comment: "数据源挂载路径", comment: "数据源挂载路径",
primaryKey: false, primaryKey: false,
@ -95,7 +95,16 @@ module.exports = dc => {
primaryKey: false, primaryKey: false,
field: "catalogKey", field: "catalogKey",
autoIncrement: false autoIncrement: false
} },
type: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: "元数据类型",
primaryKey: false,
field: "type",
autoIncrement: false
},
}, { }, {
tableName: "t_data_source", tableName: "t_data_source",
comment: "", comment: "",

9
api/app/lib/models/metadata_database.js

@ -122,6 +122,15 @@ module.exports = dc => {
primaryKey: false, primaryKey: false,
field: "catalogKey", field: "catalogKey",
autoIncrement: false autoIncrement: false
},
datasourceConfig: {
type: DataTypes.JSONB,
allowNull: true,
defaultValue: null,
comment: null,
primaryKey: false,
field: "datasource_config",
autoIncrement: false
} }
}, { }, {
tableName: "t_metadata_database", tableName: "t_metadata_database",

9
api/app/lib/models/resource_consumption.js

@ -16,6 +16,15 @@ module.exports = dc => {
autoIncrement: true, autoIncrement: true,
unique: "t_resource_consumption_id_uindex" unique: "t_resource_consumption_id_uindex"
}, },
resourceId: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: "资源id",
primaryKey: false,
field: "resource_id",
autoIncrement: false
},
resourceName: { resourceName: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: false, allowNull: false,

7
api/app/lib/routes/metadataSearch/index.js

@ -3,7 +3,10 @@
const search = require('../../controllers/metadataSearch/index'); const search = require('../../controllers/metadataSearch/index');
module.exports = function (app, router, opts, AuthCode) { module.exports = function (app, router, opts, AuthCode) {
//获取适配器列表 //元数据资源检索
app.fs.api.logAttr['GET/meta/data/search'] = { content: '获取适配器列表', visible: true }; app.fs.api.logAttr['GET/meta/data/search'] = { content: '元数据资源检索', visible: true };
router.get('/meta/data/search', search.searchMeta(opts)); router.get('/meta/data/search', search.searchMeta(opts));
app.fs.api.logAttr['GET/meta/table/data'] = { content: '查询元数据表数据', visible: true };
router.get('/meta/table/data', search.getTableData(opts))
}; };

4
scripts/0.0.7/01_alter_t_resource_consumption.sql

@ -0,0 +1,4 @@
alter table t_resource_consumption
add resource_id int;
comment on column t_resource_consumption.resource_id is '元数据id';

9
scripts/0.0.7/02_alter_t_data_source.sql

@ -0,0 +1,9 @@
create type enum_datasource_type as enum ('原数据库', '备份数据库');
alter table t_data_source
alter column mount_path drop not null;
alter table t_data_source
add type enum_datasource_type;
comment on column t_data_source.type is '数据源类型';

111
web/client/src/sections/metadataAcquisition/components/steps/postgre/stepOne.js

@ -5,7 +5,8 @@ import {
ProFormSelect, ProFormSelect,
ProFormTextArea, ProFormTextArea,
ProFormText, ProFormText,
ProFormTreeSelect ProFormTreeSelect,
ProFormDependency
} from '@ant-design/pro-form'; } from '@ant-design/pro-form';
import { push } from 'react-router-redux'; import { push } from 'react-router-redux';
@ -16,6 +17,7 @@ function StepOne(props) {
adapterName: 'PostgreSQL采集适配器', adapterName: 'PostgreSQL采集适配器',
adapterVersion: '9.x', adapterVersion: '9.x',
mode: '数据库连接', mode: '数据库连接',
type: '原数据库'
// mountPath: 1, // mountPath: 1,
} }
@ -67,7 +69,7 @@ function StepOne(props) {
onCancel: () => { }, onCancel: () => { },
}} }}
onFinish={async (values) => { onFinish={async (values) => {
values.mountPath = values.catalogKey.split('-')[values.catalogKey.split('-')?.length - 1] if (values.catalogKey) values.mountPath = values.catalogKey.split('-')[values.catalogKey.split('-')?.length - 1]
next() next()
stepOneValuesFinish(values) stepOneValuesFinish(values)
return true; return true;
@ -117,54 +119,65 @@ function StepOne(props) {
label="采集模式" label="采集模式"
disabled={readOnly} disabled={readOnly}
/> />
{/* <ProFormSelect <ProFormSelect
rules={[{ required: true, message: '请选择输入控件' }]}
options={[
{ label: '库表/目录1', value: 1 },
]}
name="mountPath"
label="数据源挂载路径"
// disabled={true}
/> */}
{treeDataFilter.length > 0 ? <ProFormTreeSelect
key={JSON.stringify(treeDataFilter)}
// width={'md'}
name="catalogKey"
label="数据源挂载路径"
placeholder="请选择数据源挂载路径"
rules={[{ required: true, message: '请选择数据源挂载路径' }]}
allowClear
width={480}
secondary
request={async () => {
return treeDataFilter || [];
}}
// tree-select args
fieldProps={{
showArrow: false,
filterTreeNode: true,
showSearch: true,
dropdownMatchSelectWidth: false,
labelInValue: false,
autoClearSearchValue: true,
multiple: false,
treeNodeFilterProp: 'title',
fieldNames: {
label: 'title',
},
}}
addonAfter={renderAddonAfter()}
disabled={editData}
/> : <ProFormSelect
width={480}
rules={[{ required: true, message: '请选择' }]} rules={[{ required: true, message: '请选择' }]}
options={[]} options={
name="catalogKey" [
label="数据源挂载路径" { label: '原数据库', value: '原数据库' },
addonAfter={renderAddonAfter()} { label: '备份数据库', value: '备份数据库' }
placeholder="请选择数据源挂载路径" ]
disabled={editData} }
/>} name="type"
label="数据源属性"
disabled={readOnly}
/>
<ProFormDependency name={['type']}>
{({ type }) => {
return (
type == '备份数据库' ? null : treeDataFilter.length > 0 ? <ProFormTreeSelect
key={JSON.stringify(treeDataFilter)}
// width={'md'}
name="catalogKey"
label="数据源挂载路径"
placeholder="请选择数据源挂载路径"
rules={[{ required: true, message: '请选择数据源挂载路径' }]}
allowClear
width={480}
secondary
request={async () => {
return treeDataFilter || [];
}}
// tree-select args
fieldProps={{
showArrow: false,
filterTreeNode: true,
showSearch: true,
dropdownMatchSelectWidth: false,
labelInValue: false,
autoClearSearchValue: true,
multiple: false,
treeNodeFilterProp: 'title',
fieldNames: {
label: 'title',
},
}}
addonAfter={renderAddonAfter()}
disabled={editData}
/> : <ProFormSelect
width={480}
rules={[{ required: true, message: '请选择' }]}
options={[]}
name="catalogKey"
label="数据源挂载路径"
addonAfter={renderAddonAfter()}
placeholder="请选择数据源挂载路径"
disabled={editData}
/>
);
}}
</ProFormDependency>
<ProFormTextArea <ProFormTextArea
name="description" name="description"

2
web/client/src/sections/metadataAcquisition/components/steps/postgre/stepThree.js

@ -67,7 +67,7 @@ function StepThree(props) {
{dataSourceFilter && <ProFormSelect {dataSourceFilter && <ProFormSelect
width={'md'} width={'md'}
rules={[{ required: true, message: '请选择数据源' }]} rules={[{ required: true, message: '请选择数据源' }]}
options={dataSourceFilter?.map(s => { options={dataSourceFilter?.filter(x => x?.type != '备份数据库')?.map(s => {
return { label: s?.name, value: s?.id, disabled: s?.disabled } return { label: s?.name, value: s?.id, disabled: s?.disabled }
})} })}
disabled={editData} disabled={editData}

1
web/client/src/sections/metadataAcquisition/containers/adapter.js

@ -57,6 +57,7 @@ const Adapter = (props) => {
mountPath: stepOneValues?.mountPath, mountPath: stepOneValues?.mountPath,
catalogKey: stepOneValues?.catalogKey, catalogKey: stepOneValues?.catalogKey,
description: stepOneValues?.description, description: stepOneValues?.description,
type: stepOneValues?.type,
config: stepTwoValues, config: stepTwoValues,
time: moment() time: moment()
}, '')).then(res => { }, '')).then(res => {

1
web/client/src/sections/metadataAcquisition/containers/dataSourceManagement.js

@ -124,6 +124,7 @@ function DataSourceManagement(props) {
mountPath: stepOneValues?.mountPath, mountPath: stepOneValues?.mountPath,
catalogKey: stepOneValues?.catalogKey, catalogKey: stepOneValues?.catalogKey,
description: stepOneValues?.description, description: stepOneValues?.description,
type: stepOneValues?.type,
config: stepTwoValues, config: stepTwoValues,
time: moment() time: moment()
} }

8
web/client/src/sections/metadataManagement/containers/databasesTable.js

@ -11,7 +11,7 @@ import MetadataResourceModal from '../components/metadataResourceModal';
const DatabaseTable = (props) => { const DatabaseTable = (props) => {
const { user, dispatch, actions, clientHeight, resourceCatalogId, resourceCatalogKey, const { user, dispatch, actions, clientHeight, resourceCatalogId, resourceCatalogKey,
resourceCatalogPath, isRequesting, metadataModels, setView, tagList, metadataResourceApplications } = props; resourceCatalogPath, isRequesting, metadataModels, setView, tagList, metadataResourceApplications, params } = props;
const { metadataManagement } = actions; const { metadataManagement } = actions;
const SortValues = { 'ascend': 'asc', 'descend': 'desc' }; const SortValues = { 'ascend': 'asc', 'descend': 'desc' };
const [tableData, setTableData] = useState([]); const [tableData, setTableData] = useState([]);
@ -36,7 +36,7 @@ const DatabaseTable = (props) => {
}, [resourceCatalogId]); }, [resourceCatalogId]);
const initData = (query = {}) => { const initData = (query = {}) => {
dispatch(metadataManagement.getMetadataDatabases({ catalog: resourceCatalogId, keywords, orderBy: 'createAt', ...query })).then(res => { dispatch(metadataManagement.getMetadataDatabases({ resourceId: params?.type == 'databases' ? params?.resourceId : null, catalog: resourceCatalogId, keywords, orderBy: 'createAt', ...query })).then(res => {
if (res.success) { if (res.success) {
setTableData(res.payload.data.rows); setTableData(res.payload.data.rows);
setTableDataCount(res.payload.data.count); setTableDataCount(res.payload.data.count);
@ -92,7 +92,7 @@ const DatabaseTable = (props) => {
}); });
} }
const applyResources = (record) => { const applyResources = (record) => {
setEditResourceData({ record: { resourceName: record.name, applyBy: user.id, applyByName: user.name, resourceType: '库表' } }); setEditResourceData({ record: { resourceId: record.id, resourceName: record.name, applyBy: user.id, applyByName: user.name, resourceType: '库表' } });
setResourceModalVisible(true); setResourceModalVisible(true);
} }
@ -170,7 +170,7 @@ const DatabaseTable = (props) => {
width: '8%', width: '8%',
render: (text, record) => { render: (text, record) => {
let resourceApplicationsRecords = metadataResourceApplications.filter(ra => let resourceApplicationsRecords = metadataResourceApplications.filter(ra =>
ra.applyBy == user.id && ra.resourceName === record.name); ra.applyBy == user.id && ra.resourceName === record.name && ra.resourceId == record.id);
return <ButtonGroup> return <ButtonGroup>
<a onClick={() => onView(record)}>查看</a> <a onClick={() => onView(record)}>查看</a>
{user.role == '数据消费者' ? null : {user.role == '数据消费者' ? null :

8
web/client/src/sections/metadataManagement/containers/filesTable.js

@ -12,7 +12,7 @@ import { RouteTable } from '$utils'
const FilesTable = (props) => { const FilesTable = (props) => {
const { user, dispatch, actions, clientHeight, resourceCatalogId, resourceCatalogKey, const { user, dispatch, actions, clientHeight, resourceCatalogId, resourceCatalogKey,
isRequesting, metadataModels, tagList, metadataResourceApplications } = props; isRequesting, metadataModels, tagList, metadataResourceApplications, params } = props;
const { metadataManagement } = actions; const { metadataManagement } = actions;
const SortValues = { 'ascend': 'asc', 'descend': 'desc' }; const SortValues = { 'ascend': 'asc', 'descend': 'desc' };
const [tableData, setTableData] = useState([]); const [tableData, setTableData] = useState([]);
@ -37,7 +37,7 @@ const FilesTable = (props) => {
}, []); }, []);
const initData = (query = {}) => { const initData = (query = {}) => {
dispatch(metadataManagement.getMetadataFiles({ catalog: resourceCatalogId, keywords, orderBy: 'updateAt', ...query })).then(res => { dispatch(metadataManagement.getMetadataFiles({ resourceId: params?.type == 'files' ? params?.resourceId : null, catalog: resourceCatalogId, keywords, orderBy: 'updateAt', ...query })).then(res => {
if (res.success) { if (res.success) {
setTableData(res.payload.data.rows); setTableData(res.payload.data.rows);
setTableDataCount(res.payload.data.count); setTableDataCount(res.payload.data.count);
@ -102,7 +102,7 @@ const FilesTable = (props) => {
}); });
} }
const applyResources = (record) => { const applyResources = (record) => {
setEditResourceData({ record: { resourceName: record.name, applyBy: user.id, applyByName: user.name, resourceType: '文件' } }); setEditResourceData({ record: { resourceId: record.id, resourceName: record.name, applyBy: user.id, applyByName: user.name, resourceType: '文件' } });
setResourceModalVisible(true); setResourceModalVisible(true);
} }
@ -205,7 +205,7 @@ const FilesTable = (props) => {
width: '8%', width: '8%',
render: (text, record) => { render: (text, record) => {
let resourceApplicationsRecords = metadataResourceApplications.filter(ra => let resourceApplicationsRecords = metadataResourceApplications.filter(ra =>
ra.applyBy == user.id && ra.resourceName === record.name); ra.applyBy == user.id && ra.resourceName === record.name && ra.resourceId == record.id);
return <ButtonGroup> return <ButtonGroup>
{user.role == '数据消费者' ? null : {user.role == '数据消费者' ? null :
<> <>

38
web/client/src/sections/metadataManagement/containers/latestMetadata.js

@ -20,6 +20,12 @@ const LatestMetadata = (props) => {
const [resourceCatalogId, setResourceCatalogId] = useState(''); const [resourceCatalogId, setResourceCatalogId] = useState('');
const [resourceCatalogKey, setResourceCatalogKey] = useState(''); const [resourceCatalogKey, setResourceCatalogKey] = useState('');
const [resourceCatalogPath, setResourceCatalogPath] = useState([]); const [resourceCatalogPath, setResourceCatalogPath] = useState([]);
const params = GetRequest(location?.search);
useEffect(() => {
const jumpSelectedKey = sessionStorage.getItem('jumpSelectedKey') || null;
initData(true, jumpSelectedKey);
}, [location?.search])
useEffect(() => { useEffect(() => {
const jumpSelectedKey = sessionStorage.getItem('jumpSelectedKey') || null; const jumpSelectedKey = sessionStorage.getItem('jumpSelectedKey') || null;
@ -37,8 +43,9 @@ const LatestMetadata = (props) => {
if (jumpSelectedKey || selectedKeys.length) if (jumpSelectedKey || selectedKeys.length)
expandedKeysData = jumpSelectedKey ? [jumpSelectedKey] : selectedKeys; expandedKeysData = jumpSelectedKey ? [jumpSelectedKey] : selectedKeys;
let expandedKeys = getExpandKeys(expandedKeysData); let expandedKeys = getExpandKeys(expandedKeysData);
setSelectedKeys(expandedKeysData); params?.catalogKey ? setSelectedKeys([params?.catalogKey]) : setSelectedKeys(expandedKeysData);
setExpandedKeys(expandedKeys); params?.catalogKey ? setExpandedKeys(getExpandKeys([params?.catalogKey])) : setExpandedKeys(expandedKeys);
params?.treeId && setResourceCatalogId(params?.treeId);
expandedKeysData = []; expandedKeysData = [];
} }
} else { } else {
@ -52,7 +59,7 @@ const LatestMetadata = (props) => {
const keyArr = selectedData[0].split('-'); const keyArr = selectedData[0].split('-');
keyArr.shift();//['rc-2-5']->返回'rc';keyArr:['2','5'] keyArr.shift();//['rc-2-5']->返回'rc';keyArr:['2','5']
const allExpandedKeys = allTreeNodeKeys.filter(k => keyArr.includes(k.id.toString())); const allExpandedKeys = allTreeNodeKeys.filter(k => keyArr.includes(k.id.toString()));
setResourceCatalogId(keyArr.pop()); !params?.treeId && setResourceCatalogId(keyArr.pop());
setResourceCatalogKey(selectedData[0]); setResourceCatalogKey(selectedData[0]);
const resourceCatalogPath = allExpandedKeys.map(a => a.name); const resourceCatalogPath = allExpandedKeys.map(a => a.name);
setResourceCatalogPath(resourceCatalogPath); setResourceCatalogPath(resourceCatalogPath);
@ -177,6 +184,8 @@ const LatestMetadata = (props) => {
const { value } = e.target; const { value } = e.target;
onSearch(value); onSearch(value);
}; };
return <Spin spinning={isRequesting}> return <Spin spinning={isRequesting}>
<Row> <Row>
<Col style={{ width: 240 }}> <Col style={{ width: 240 }}>
@ -193,6 +202,7 @@ const LatestMetadata = (props) => {
expandedKeys={expandedKeys} expandedKeys={expandedKeys}
selectedKeys={selectedKeys} selectedKeys={selectedKeys}
onSelect={(keys, e) => { onSelect={(keys, e) => {
history.push(`/metadataManagement/latestMetadata`);
if (e.selected) { if (e.selected) {
setSelectedKeys(keys); setSelectedKeys(keys);
getExpandKeys(keys); getExpandKeys(keys);
@ -214,7 +224,14 @@ const LatestMetadata = (props) => {
match={match} /> : match={match} /> :
<MetadataTab resourceCatalogId={resourceCatalogId} <MetadataTab resourceCatalogId={resourceCatalogId}
resourceCatalogKey={resourceCatalogKey} resourceCatalogKey={resourceCatalogKey}
resourceCatalogPath={resourceCatalogPath} /> resourceCatalogPath={resourceCatalogPath}
params={params}
/>
// <MetadataTab resourceCatalogId={params?.treeId}
// resourceCatalogKey={params?.catalogKey}
// resourceCatalogPath={resourceCatalogPath}
// initTab={params?.type}
// />
} }
</Col> </Col>
</Row> </Row>
@ -239,3 +256,16 @@ function mapStateToProps(state) {
}; };
} }
export default connect(mapStateToProps)(LatestMetadata) export default connect(mapStateToProps)(LatestMetadata)
function GetRequest(search) {
let url = search; //获取url中"?“符后的字串
let theRequest = new Object();
if (url.indexOf("?") != -1) {
let str = url.substr(1);
let strs = str.split("&");
for (let i = 0; i < strs.length; i++) {
theRequest[strs[i].split("=")[0]] = unescape(strs[i].split("=")[1]);
}
}
return theRequest;
}

13
web/client/src/sections/metadataManagement/containers/metadataTab.js

@ -7,10 +7,11 @@ import RestapisTable from './restapisTable';
import { push } from 'react-router-redux'; import { push } from 'react-router-redux';
const MetadataTab = (props) => { const MetadataTab = (props) => {
const { resourceCatalogId, resourceCatalogKey, resourceCatalogPath, actions, dispatch } = props; const { resourceCatalogId, resourceCatalogKey, resourceCatalogPath, actions, dispatch, params } = props;
const [activeKey, setActiveKey] = useState('databases'); const [activeKey, setActiveKey] = useState(params?.type || 'databases');
useEffect(() => { useEffect(() => {
setActiveKey('databases'); if (!params?.type)
setActiveKey('databases');
}, [resourceCatalogId]); }, [resourceCatalogId]);
const onTabChange = (key) => { const onTabChange = (key) => {
@ -40,11 +41,11 @@ const MetadataTab = (props) => {
]}> ]}>
</Tabs> </Tabs>
{ {
activeKey === 'databases' && resourceCatalogId ? <DatabaseTable resourceCatalogId={resourceCatalogId} activeKey === 'databases' && resourceCatalogId ? <DatabaseTable params={params} resourceCatalogId={resourceCatalogId}
resourceCatalogKey={resourceCatalogKey} resourceCatalogPath={resourceCatalogPath} setView={onView} /> : resourceCatalogKey={resourceCatalogKey} resourceCatalogPath={resourceCatalogPath} setView={onView} /> :
activeKey === 'files' && resourceCatalogId ? <FilesTable resourceCatalogId={resourceCatalogId} activeKey === 'files' && resourceCatalogId ? <FilesTable params={params} resourceCatalogId={resourceCatalogId}
resourceCatalogKey={resourceCatalogKey} resourceCatalogPath={resourceCatalogPath} /> : resourceCatalogKey={resourceCatalogKey} resourceCatalogPath={resourceCatalogPath} /> :
activeKey === 'restapis' && resourceCatalogId ? < RestapisTable resourceCatalogId={resourceCatalogId} activeKey === 'restapis' && resourceCatalogId ? < RestapisTable params={params} resourceCatalogId={resourceCatalogId}
resourceCatalogKey={resourceCatalogKey} resourceCatalogPath={resourceCatalogPath} /> : null resourceCatalogKey={resourceCatalogKey} resourceCatalogPath={resourceCatalogPath} /> : null
} }
</div> </div>

8
web/client/src/sections/metadataManagement/containers/restapisTable.js

@ -8,7 +8,7 @@ import MetadataResourceModal from '../components/metadataResourceModal';
const RestapisTable = (props) => { const RestapisTable = (props) => {
const { user, dispatch, actions, clientHeight, resourceCatalogId, resourceCatalogKey, const { user, dispatch, actions, clientHeight, resourceCatalogId, resourceCatalogKey,
isRequesting, metadataModels, tagList, metadataResourceApplications } = props; isRequesting, metadataModels, tagList, metadataResourceApplications, params } = props;
const { metadataManagement } = actions; const { metadataManagement } = actions;
const [tableData, setTableData] = useState([]); const [tableData, setTableData] = useState([]);
const [tableDataCount, setTableDataCount] = useState(0);//Table数据 const [tableDataCount, setTableDataCount] = useState(0);//Table数据
@ -28,7 +28,7 @@ const RestapisTable = (props) => {
}, []); }, []);
const initData = (query = {}) => { const initData = (query = {}) => {
dispatch(metadataManagement.getMetadataRestapis({ catalog: resourceCatalogId, keywords, ...query })).then(res => { dispatch(metadataManagement.getMetadataRestapis({ resourceId: params?.type == 'restapis' ? params?.resourceId : null, catalog: resourceCatalogId, keywords, ...query })).then(res => {
if (res.success) { if (res.success) {
setTableData(res.payload.data.rows); setTableData(res.payload.data.rows);
setTableDataCount(res.payload.data.count); setTableDataCount(res.payload.data.count);
@ -79,7 +79,7 @@ const RestapisTable = (props) => {
}); });
} }
const applyResources = (record) => { const applyResources = (record) => {
setEditResourceData({ record: { resourceName: record.name, applyBy: user.id, applyByName: user.name, resourceType: '接口' } }); setEditResourceData({ record: { resourceId: record.id, resourceName: record.name, applyBy: user.id, applyByName: user.name, resourceType: '接口' } });
setResourceModalVisible(true); setResourceModalVisible(true);
} }
@ -141,7 +141,7 @@ const RestapisTable = (props) => {
width: '8%', width: '8%',
render: (text, record) => { render: (text, record) => {
let resourceApplicationsRecords = metadataResourceApplications.filter(ra => let resourceApplicationsRecords = metadataResourceApplications.filter(ra =>
ra.applyBy == user.id && ra.resourceName === record.name); ra.applyBy == user.id && ra.resourceName === record.name && ra.resourceId == record.id);
return <ButtonGroup> return <ButtonGroup>
{user.role == '数据消费者' ? null : {user.role == '数据消费者' ? null :
<> <>

46
web/client/src/sections/resourceRetrieval/components/keyModal.js

@ -0,0 +1,46 @@
import React from 'react'
import {
ModalForm,
ProFormText,
} from '@ant-design/pro-form';
import { Form, message } from 'antd';
export default (props) => {
const { resourceId, onFinish, approveList, } = props;
const [form] = Form.useForm();
return (
<ModalForm
title="输入令牌"
trigger={
<a></a>
}
visible={true}
form={form}
layout='horizontal'
autoFocusFirstInput
modalProps={{
destroyOnClose: true,
onCancel: () => { props.onCancel() }
}}
onFinish={async (values) => {
console.log(values.name);
const token = approveList?.rows?.find(s => s.resourceId == resourceId)?.token
if (token == values.name) {
onFinish()
return true;
} else {
message.error('令牌错误')
}
}}
width={500}
>
<ProFormText
name="name"
label="访问令牌"
placeholder="请输入访问令牌"
rules={[{ required: true, message: '访问令牌不可空' }]}
/>
</ModalForm>
);
};

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

@ -1,16 +1,40 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { Input, Tooltip, Empty } from 'antd' import { Input, Tooltip, Empty, Pagination, Popover, message } from 'antd'
import { InsertRowBelowOutlined, DatabaseOutlined, FileOutlined, PullRequestOutlined } from '@ant-design/icons'; import { InsertRowBelowOutlined, DatabaseOutlined, FileOutlined, PullRequestOutlined, KeyOutlined } from '@ant-design/icons';
import { useFsRequest, ApiTable } from '$utils'; import { useFsRequest, ApiTable } from '$utils';
import { connect } from 'react-redux';
import { downloadImg, markRedKeywords } from '../utils/index'
import KeyModal from '../components/keyModal';
const METADTA_TYPE = {
'库': 'databases',
'表': 'databases',
'文件': 'files',
'接口': 'restapis',
}
const METADTA_TYPE_NAMES = {
'库': '库表/库(Schema)',
'表': '库表/表(Table)',
'文件': '文件(File)',
'接口': '接口(Api)',
}
import './style.less'; import './style.less';
function Retrieval(props) { function Retrieval(props) {
const { user, catalogs, dispatch, actions } = props;
const [keywords, setKeywords] = useState() const [keywords, setKeywords] = useState()
const [firstInput, setFirstInput] = useState() const [firstInput, setFirstInput] = useState()
const [page, setPage] = useState(1)
const [currentData, setCurrentData] = useState(null)
const formRef = React.createRef();
// const { data: catalogs = [] } = useFsRequest({
// url: ApiTable.getResourceCatalog,
// });
useEffect(() => {
dispatch(actions.metadataManagement.getResourceCatalog())
}, [])
const { data: catalogs = [] } = useFsRequest({ //检索元数据
url: ApiTable.getResourceCatalog,
});
const { data: result = {} } = useFsRequest({ const { data: result = {} } = useFsRequest({
url: ApiTable.searchMetadata, url: ApiTable.searchMetadata,
query: { query: {
@ -20,7 +44,15 @@ function Retrieval(props) {
ready: !!keywords ready: !!keywords
}); });
console.log(result) //用户申请资源列表
const { data: approveList = {} } = useFsRequest({
url: ApiTable.approveList,
query: {
applyById: user?.id
},
refreshDeps: [user?.id],
ready: !!(user?.id)
});
const renderIcon = (type) => { const renderIcon = (type) => {
switch (type) { switch (type) {
@ -54,9 +86,33 @@ function Retrieval(props) {
</Tooltip> : text </Tooltip> : text
} }
const renderName = (text) => {
function createMarkup(textFilter) {
return { __html: textFilter.length == text.length ? markRedKeywords(textFilter, keywords) : markRedKeywords(textFilter, keywords) + '...' };
}
return text?.length > 16 ?
<Popover placement="topRight" title={null} content={<span dangerouslySetInnerHTML={createMarkup(text)} />}>
<span dangerouslySetInnerHTML={createMarkup(text.substring(0, 16))} />
</Popover>
: <span dangerouslySetInnerHTML={createMarkup(text)} />
}
const downloadData = (s) => {
if (s?.type == '文件') {
const suffix = s?.fileName?.substring(s?.fileName?.length - 3, s?.fileName?.length)
if (suffix == 'png' || suffix == 'jpg') {
downloadImg(s.fileName)
} else {
window.open('/assets/files/common/' + s.fileName)
}
} else {
alert('库表下载待开发')
}
}
return !result?.rows ? <div className='search-container'> return !result?.rows ? <div className='search-container'>
<div className='title'>数据资源检索</div> <div className='title'>数据资源检索</div>
<Input addonAfter={<div onClick={() => { setKeywords(firstInput) }} style={{ color: '#fff' }}>搜索一下</div>} <Input addonAfter={<div onClick={() => { setKeywords(firstInput) }} style={{ color: '#fff', cursor: 'pointer' }}>搜索一下</div>}
style={{ width: '40%', marginLeft: 10, marginBottom: 30 }} style={{ width: '40%', marginLeft: 10, marginBottom: 30 }}
onChange={e => { setFirstInput(e.target.value) }} onChange={e => { setFirstInput(e.target.value) }}
onPressEnter={e => { setKeywords(e.target.value) }} onPressEnter={e => { setKeywords(e.target.value) }}
@ -66,25 +122,63 @@ function Retrieval(props) {
<Input.Search defaultValue={keywords} style={{ width: '30%', marginLeft: 10, marginBottom: 30 }} <Input.Search defaultValue={keywords} style={{ width: '30%', marginLeft: 10, marginBottom: 30 }}
onSearch={value => { setKeywords(value) }} onSearch={value => { setKeywords(value) }}
/> />
{result?.rows?.map(s => { {result?.rows?.slice((page - 1) * 10, (page - 1) * 10 + 10).map(s => {
const catalogText = renderCatalog(s?.catalog).split('/').reverse().toString().replaceAll(',', '/') const catalogText = renderCatalog(s?.catalog).split('/').reverse().toString().replaceAll(',', '/')
const tagText = s?.tagDatabases?.map(x => x.tag.name).toString() || '-' const tagText = s?.tagDatabases?.map(x => x.tag.name).toString() || '-'
return <div className='result-row'> return <div className='result-row'>
<div className='column1'> <div className='column1'>
{renderIcon(s?.type)} {renderIcon(s?.type)}
{s?.code && <span> 代码{renderText(s?.code)}</span>} {s?.code && <span> 代码{renderText(s?.code)}</span>}
<span> 名称{renderText(s?.name)}</span> <span> 名称{renderName(s?.name)}</span>
<span> 类型{s?.type}</span> <span> 类型{METADTA_TYPE_NAMES[s?.type]}</span>
<span> 路径{renderText(catalogText)}</span> <span> 路径{renderText(catalogText)}</span>
<span> 标签{renderText(tagText)}</span> <span> 标签{renderText(tagText)}</span>
</div> </div>
<div className='column2'>相关操作<a>定位</a> <div className='column2'>相关操作<a onClick={() => { window.open(`/metadataManagement/latestMetadata?type=${METADTA_TYPE[s.type]}&treeId=${s?.catalog}&resourceId=${s.id}&catalogKey=${s.catalogKey}`) }}>定位</a>
{s?.type == '表' || s?.type == '文件' ? <a>下载</a> : ''} {s?.type == '表' || s?.type == '文件' ?
user?.role == '数据消费者' ?
<a onClick={() => {
const token = approveList?.rows?.find(x => x.resourceId == s.id)?.token
if (!token) {
message.warning('您暂未申请该数据资源,请先申请该数据资源')
} else {
setCurrentData(s)
}
}}>下载</a> :
<a onClick={() => { downloadData(s) }}>下载</a> : ''}
</div> </div>
</div> </div>
})} })}
{result?.rows?.length > 0 && <Pagination
defaultCurrent={1} total={result?.rows?.length}
style={{ float: 'right', paddingRight: '5%' }}
showTotal={total => `${total} 条数据`}
onChange={(page, pageSize) => {
setPage(page)
}}
/>}
{!result?.rows?.length > 0 && <Empty />} {!result?.rows?.length > 0 && <Empty />}
{currentData && <KeyModal
resourceId={currentData?.id}
approveList={approveList}
onFinish={() => {
downloadData(currentData)
setCurrentData(null)
}}
onCancel={() => { setCurrentData(null) }}
/>}
</> </>
} }
export default Retrieval function mapStateToProps(state) {
const { auth, resourceCatalog, global } = state;
return {
catalogs: resourceCatalog?.data || [],
user: auth.user,
actions: global.actions,
};
}
export default connect(mapStateToProps)(Retrieval)

37
web/client/src/sections/resourceRetrieval/utils/index.js

@ -0,0 +1,37 @@
const imageToBase64 = (img) => {
var canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext("2d");
console.log(img, canvas)
ctx.drawImage(img, 0, 0, img.width, img.height);
var ext = img.src.substring(img.src.lastIndexOf(".") + 1).toLowerCase();
var dataURL = canvas.toDataURL("image/" + ext);
return dataURL;
}
export const downloadImg = (fileName) => {
const url = '/assets/files/common/' + fileName
var image = new Image();
image.crossOrigin = '';
image.src = url;
image.onload = function () {
let base64 = imageToBase64(image); //图片转base64
const link = document.createElement('a')
link.style.display = 'none'
//设置下载的图片名称
link.download = fileName
link.href = base64
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
}
export const markRedKeywords = (str, key) => {
var reg = new RegExp((`(${key})`), "gi");
var replace = '<span style="color:#FD463E;font-weight:bold;margin-right:0px;">$1</span>';
return str.replace(reg, replace);
}
Loading…
Cancel
Save