Commit 5ce9dced by zhaochengxiang

Merge branch 'master' into 'dev-2106'

# Conflicts:
#   src/view/Manage/Map/Component/Relation.css
#   src/view/Manage/Map/Component/Relation.jsx
#   src/view/Manage/Map/Component/Relation1.jsx
parents 71efdb3a d8648343
......@@ -19,6 +19,7 @@
"insert-css": "^2.0.0",
"less": "^4.1.1",
"less-loader": "^8.0.0",
"local-storage": "^2.0.0",
"react": "^17.0.1",
"react-dnd": "^14.0.2",
"react-dnd-html5-backend": "^14.0.0",
......@@ -28,6 +29,7 @@
"react-scripts": "4.0.3",
"redux": "^4.0.1",
"redux-saga": "^1.0.5",
"smooth-scroll": "^16.1.3",
"web-vitals": "^1.0.1"
},
"scripts": {
......
......@@ -15,8 +15,20 @@ import AssetBrowse from './view/Manage/AssetBrowse';
import AssetRecycle from './view/Manage/AssetRecycle';
import DatasourceManage from './view/Manage/DatasourceManage';
import ImportModal from './view/Manage/Model/Component/ImportModal';
export default class App extends React.Component {
render() {
const { message, callback } = this.props;
if (message === 'showDataModelDetail') {
return <ImportModal
visible={true}
onCancel={callback}
/>;
}
return (
<React.Fragment>
<Router basename={window.__POWERED_BY_QIANKUN__ ? '/data-govern' : '/'}>
......@@ -30,6 +42,7 @@ export default class App extends React.Component {
<Route path={'/center-home/view/asset-manage'} component={AssetManage} exact />
<Route path={'/center-home/view/asset-browse'} component={AssetBrowse} exact />
<Route path={'/center-home/view/asset-recycle'} component={AssetRecycle} exact />
<Route path={'/center-home/data-modal-detail'} component={AssetRecycle} exact />
</Switch>
</Router>
</React.Fragment>
......
......@@ -11,17 +11,15 @@ import App from './App'
import './index.less';
const app = (
//解决主次应用样式冲突
//https://qiankun.umijs.org/faq#how-to-guarantee-the-main-app-stylesheet-isolated-with-sub-apps
<ConfigProvider locale={zh_CN} prefixCls="yy">
<Provider store={store}><App /></Provider>
</ConfigProvider>
);
function render(props) {
const { container } = props;
ReactDOM.render(app, container ? container.querySelector('#root') : document.querySelector('#root'));
const { container, ...restProps } = props;
ReactDOM.render(
//解决主次应用样式冲突
//https://qiankun.umijs.org/faq#how-to-guarantee-the-main-app-stylesheet-isolated-with-sub-apps
<ConfigProvider locale={zh_CN} prefixCls="yy">
<Provider store={store}><App {...restProps} /></Provider>
</ConfigProvider>,
container ? container.querySelector('#root') : document.querySelector('#root'));
}
function storeTest(props) {
......
import React, { useState, useEffect } from 'react';
import { Dropdown, Input, Tabs, List, Spin, Empty, Avatar, Typography } from 'antd';
import { SearchOutlined } from '@ant-design/icons';
import LocalStorage from 'local-storage';
import useDebounce from './useDebounce';
import './FullSearch.less';
const { TabPane } = Tabs;
const recordsKey = 'records';
const keywordKey = 'keyword';
const modes = [
{
title: '数据源',
shortTitle: '源',
key: 'data-source',
},
{
title: '元数据',
shortTitle: '元',
key: 'data-meta',
},
{
title: '数据标准',
shortTitle: '标',
key: 'data-standard',
},
{
title: '数据模型',
shortTitle: '模',
key: 'data-model',
},
{
title: '数据资产',
shortTitle: '资',
key: 'data-asset',
},
]
const list = [
{
title: 'test',
did: '609a4eaf9e14e664f6fee2ee',
id: '60b9978f9e14e64eb2ec5384',
desc: '模型描述'
},
{
title: 'test9',
did: '608b99d19e14e66dea274bbe',
id: '60b83b7f9e14e64eb2ec5382',
desc: '模型描述'
},
{
title: 'test',
did: '609a4eaf9e14e664f6fee2ee',
id: '60b9978f9e14e64eb2ec5384',
desc: '模型描述'
},
{
title: 'test9',
did: '608b99d19e14e66dea274bbe',
id: '60b83b7f9e14e64eb2ec5382',
desc: '模型描述'
},
{
title: 'test',
did: '609a4eaf9e14e664f6fee2ee',
id: '60b9978f9e14e64eb2ec5384',
desc: '模型描述'
},
{
title: 'test9',
did: '608b99d19e14e66dea274bbe',
id: '60b83b7f9e14e64eb2ec5382',
desc: '模型描述'
},
{
title: 'test',
did: '609a4eaf9e14e664f6fee2ee',
id: '60b9978f9e14e64eb2ec5384',
desc: '模型描述'
},
{
title: 'test9',
did: '608b99d19e14e66dea274bbe',
id: '60b83b7f9e14e64eb2ec5382',
desc: '模型描述'
},
{
title: 'test',
did: '609a4eaf9e14e664f6fee2ee',
id: '60b9978f9e14e64eb2ec5384',
desc: '模型描述'
},
{
title: 'test9',
did: '608b99d19e14e66dea274bbe',
id: '60b83b7f9e14e64eb2ec5382',
desc: '模型描述'
},
{
title: 'test',
did: '609a4eaf9e14e664f6fee2ee',
id: '60b9978f9e14e64eb2ec5384',
desc: '模型描述'
},
{
title: 'test9',
did: '608b99d19e14e66dea274bbe',
id: '60b83b7f9e14e64eb2ec5382',
desc: '模型描述'
},
{
title: 'test',
did: '609a4eaf9e14e664f6fee2ee',
id: '60b9978f9e14e64eb2ec5384',
desc: '模型描述'
},
{
title: 'test9',
did: '608b99d19e14e66dea274bbe',
id: '60b83b7f9e14e64eb2ec5382',
desc: '模型描述'
},
]
const saveRecord = (item) => {
let records = getAllRecords();
const index = records.findIndex((record) => record.id === item.id);
if (index !== -1) {
records.splice(index, 1);
}
records = [item, ...records];
//最多只保存十条
LocalStorage.set(recordsKey, JSON.stringify(records.slice(0, 10)));
}
const getAllRecords = () => {
return (JSON.parse(LocalStorage.get(recordsKey)) || []);
}
const clearAllRecords = () => {
LocalStorage.remove(recordsKey);
}
const FullSearchEmpty = (props) => {
const [reports, setReports] = useState([]);
useEffect(() => {
setReports(getAllRecords);
}, [])
const onPreventMouseDown = (event) => {
event.preventDefault();
event.stopPropagation();
}
const onItemClick = (item) => {
saveRecord(item);
window.location.href=`data-model?did=${item.did}&id=${item.id}`;
}
const clear = () => {
clearAllRecords();
setReports([]);
}
return (
<div className='full-search-empty' onMouseDown={onPreventMouseDown}>
{
(reports||[]).length===0 ? (
<div className='no-record-content'>
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="输入关键字开始搜索" />
</div>
) : (
<div style={{ padding: 10 }}>
<div className='header'>
<span className='recent'>最近搜索</span>
<span className='clear' onClick={clear}>清空</span>
</div>
<div className='content'>
{
(reports||[]).map((report, index) => {
return (
<div className='tag' key={index} onClick={() => { onItemClick(report); }}>
<div style={{ width: 24, height: 24, marginRight: 5, marginBottom: 2 }}>
<Avatar shape="square" size="small" title={report.mode?(report.mode.title||''):''}>{report.mode?(report.mode.shortTitle||''):''}</Avatar>
</div>
<Typography.Paragraph
title={report.title||''}
ellipsis
>
{report.title||''}
</Typography.Paragraph>
</div>
);
})
}
</div>
</div>
)
}
</div>
);
}
const FullSearchContent = (props) => {
const { keyword } = props;
const [currentModeKey, setCurrentModeKey] = useState('data-source');
const [loading, setLoading] = useState(false);
const debouncedKeyword = useDebounce(keyword, 300)
useEffect(() => {
setLoading(true);
setTimeout(() => {
setLoading(false);
}, 1000)
}, [debouncedKeyword])
const onPreventMouseDown = (event) => {
event.preventDefault();
event.stopPropagation();
}
const onModeChange = (key) => {
setCurrentModeKey(key);
setLoading(true);
setTimeout(() => {
setLoading(false);
}, 1000)
}
const onItemClick = (item) => {
const index = modes.findIndex((mode) => mode.key === currentModeKey);
saveRecord({...item, mode: modes[index]});
window.location.href=`data-model?did=${item.did}&id=${item.id}`;
}
return (
<div className='full-search-content' onMouseDown={onPreventMouseDown}>
<Tabs value={currentModeKey} onChange={onModeChange}>
{
modes && modes.map(mode => {
return (
<TabPane tab={mode.title} key={mode.key}>
<Spin spinning={loading}>
<List
dataSource={list}
renderItem={(item, index) => {
return (
<>
<div className='list-item' onClick={() => { onItemClick(item); }}>
<div>{item.title||''}</div>
<div className='desc'>{item.desc||''}</div>
</div>
{
(index===list.length-1) && <div style={{ display: "flex", height: 44, fontSize: 12, alignItems: 'center', justifyContent: 'center', color: '#959899' }}>
已展示全部结果
</div>
}
</>
);
}}
/>
</Spin>
</TabPane>
);
})
}
</Tabs>
</div>
);
}
const FullSearch = (props) => {
const [focused, setFocused] = useState(false);
const [keyword, setKeyword] = useState(LocalStorage.get(keywordKey)||'');
const onKeywordChange = (e) => {
setKeyword(e.target.value||'');
LocalStorage.set(keywordKey, e.target.value);
}
const onFocus = () => {
triggerFocus(true);
}
const onBlur = () => {
triggerFocus(false);
}
const triggerFocus = (focus) => {
setFocused(focus);
}
return (
<Dropdown
overlay={
(keyword||'') === '' ? <FullSearchEmpty /> : <FullSearchContent keyword={keyword} />
}
visible={focused}
>
<Input
prefix={<SearchOutlined style={{ color: '#ced4d9' }} />}
placeholder='在数据治理系统中搜索'
style={{
width: focused ? 390 : 200,
}}
value={keyword}
allowClear
onChange={onKeywordChange}
onFocus={onFocus}
onBlur={onBlur}
/>
</Dropdown>
);
}
export default FullSearch;
\ No newline at end of file
.full-search-content {
width: 390px;
height: 540px;
background-color: #fff;
border-radius: 2px;
box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
box-shadow: 0 0 8px rgba(0, 0, 0, 0.15) \9;
.yy-tabs-nav {
padding: 0 10px !important;
margin-bottom: 0 !important;
}
.yy-tabs-content-holder {
height: 494px;
overflow: auto !important;
}
.list-item {
cursor: pointer;
padding: 10px;
border-bottom: 1px solid #f0f0f0;
&:hover {
background-color: #e7f7ff;
}
.desc {
color: #959899;
}
}
}
.full-search-empty {
width: 390px;
height: 540px;
background-color: #fff;
border-radius: 2px;
box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
box-shadow: 0 0 8px rgba(0, 0, 0, 0.15) \9;
.no-record-content {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
.recent {
font-size: 16px;
}
.clear {
cursor: pointer;
font-size: 12px;
color: #a2a3a4;
&:hover {
color: #2593fc;
}
}
}
.content {
overflow: auto;
display: flex;
align-items: center;
flex-wrap: wrap;
border-radius: 2px;
.tag {
display: flex;
align-items: center;
cursor: pointer;
font-size: 16px;
padding: 5px 7px;
background-color: #eaeced;
margin-bottom: 5px;
margin-right: 5px;
min-width: 0;
&:hover {
background-color: #e0e2e4;
}
.title {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
word-break: break-all;
}
}
}
}
\ No newline at end of file
......@@ -12,6 +12,7 @@ import { routes, routeMap } from '../routes';
import './index.less';
import { dispatchLatest } from "../model";
import logo from "../assets/logo.png";
import FullSearch from './FullSearch';
const { Header, Sider, Content } = Layout;
const { SubMenu } = Menu;
......@@ -87,9 +88,11 @@ export const ManageLayout = function ({ content, location }) {
</Link> */}
{
collapsed ? <MenuUnfoldOutlined style={{ marginLeft: '16px' }} onClick={() => toggle(!collapsed)} /> : <MenuFoldOutlined style={{ marginLeft: '16px' }} onClick={() => toggle(!collapsed)} />
collapsed ? <MenuUnfoldOutlined style={{ marginLeft: '16px', marginRight: '100px' }} onClick={() => toggle(!collapsed)} /> : <MenuFoldOutlined style={{ marginLeft: '16px', marginRight: '185px' }} onClick={() => toggle(!collapsed)} />
}
<FullSearch />
<Logout />
</Header>
......
import { useState, useEffect } from 'react';
function useDebounce(value, delay = 300) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = window.setTimeout(() => {
setDebouncedValue(value);
}, delay)
return () => {
clearTimeout(handler);
}
}, [value, delay])
return debouncedValue;
}
export default useDebounce;
\ No newline at end of file
......@@ -136,3 +136,13 @@ export function generateList(data,dataList){
}
return dataList
};
export const getQueryParam = (param, url) => {
//eslint-disable-next-line
const name = param.replace(/[\[\]]/g, '\\$&')
const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)')
const results = regex.exec(url)
if (!results) return null
if (!results[2]) return ''
return decodeURIComponent(results[2].replace(/\+/g, ' '))
}
import React from 'react';
export const DatasourceContext = React.createContext();
\ No newline at end of file
import React, { useEffect, useState } from 'react';
import React, { useEffect, useState, useContext } from 'react';
import { Row, Col, Typography, Divider, Spin, Empty } from 'antd';
import TaskItem from './TaskItem';
import { dispatch } from '../../../../model';
import { DatasourceContext } from './ContextManager';
import './DatasourceItem.less';
const DatasourceItem = (props) => {
const { data, onStartTask, onEditTask, onDeleteTask, expanded, refresh, refreshSuccess, scope } = props;
const { data, expanded, refresh, scope } = props;
const [ tasks, setTasks ] = useState(null);
const [ loading, setLoading ] = useState(false);
const { refreshTasksSuccess } = useContext(DatasourceContext);
useEffect(() => {
if (expanded && !tasks) {
getTasks();
......@@ -42,7 +45,7 @@ const DatasourceItem = (props) => {
callback: data => {
setLoading(false);
setTasks(data);
refresh && refreshSuccess && refreshSuccess();
refresh && refreshTasksSuccess && refreshTasksSuccess();
},
error: () => {
setLoading(false);
......@@ -93,9 +96,6 @@ const DatasourceItem = (props) => {
<TaskItem
key={index}
data={task}
onStart={onStartTask}
onEdit={onEditTask}
onDelete={onDeleteTask}
/>
</Col>
);
......
......@@ -6,7 +6,7 @@ import DatasourceItem from './DatasourceItem';
const DatasourceList = (props) => {
const { loading, data, onEdit, onAddTask, onDelete, onStartTask, onEditTask, onDeleteTask, idBindTasksNeedRefresh, refreshTasksSuccess, scope } = props;
const { loading, data, onEdit, onAddTask, onDelete, idBindTasksNeedRefresh, scope } = props;
const [ expandedBindId, setExpandedBindId ] = useState({});
......@@ -69,7 +69,7 @@ const DatasourceList = (props) => {
</Space>
</div>
}
description={ <DatasourceItem data={item} scope={scope} expanded={expanded} refresh={idBindTasksNeedRefresh===item.id} onStartTask={onStartTask} onEditTask={onEditTask} onDeleteTask={onDeleteTask} refreshSuccess={refreshTasksSuccess} /> }
description={ <DatasourceItem data={item} scope={scope} expanded={expanded} refresh={idBindTasksNeedRefresh===item.id} /> }
/>
</List.Item>
);
......
import React from 'react';
import React, { useContext } from 'react';
import { Card, Typography, Space, Button, Tooltip } from 'antd';
import { EditOutlined, DeleteOutlined, DatabaseOutlined } from '@ant-design/icons';
import { EditOutlined, DeleteOutlined, DatabaseOutlined, FileOutlined } from '@ant-design/icons';
import { DatasourceContext } from './ContextManager';
import './TaskItem.less';
const TaskItem = (props) => {
const { data, onStart, onEdit, onDelete } = props;
const { data } = props;
const { onStartTask, onLogTask, onEditTask, onDeleteTask } = useContext(DatasourceContext);
return (
<Card className='task-item'>
<div style={{ height: 80, overflow: 'auto' }}>
......@@ -23,13 +26,16 @@ const TaskItem = (props) => {
<div className='d-flex mt-2'>
<Space style={{ marginLeft: 'auto' }}>
<Tooltip placement='bottom' title='抽取'>
<Button icon={<DatabaseOutlined />} size='small' onClick={() => { onStart && onStart(data); }} />
<Button icon={<DatabaseOutlined />} size='small' onClick={() => { onStartTask && onStartTask(data); }} />
</Tooltip>
<Tooltip placement='bottom' title='日志'>
<Button icon={<FileOutlined />} size='small' onClick={() => { onLogTask && onLogTask(data); }} />
</Tooltip>
<Tooltip placement='bottom' title='修改'>
<Button icon={<EditOutlined />} size='small' onClick={() => { onEdit && onEdit(data); }} />
<Button icon={<EditOutlined />} size='small' onClick={() => { onEditTask && onEditTask(data); }} />
</Tooltip>
<Tooltip placement='bottom' title='删除'>
<Button icon={<DeleteOutlined />} size='small' onClick={() => { onDelete && onDelete(data); }} />
<Button icon={<DeleteOutlined />} size='small' onClick={() => { onDeleteTask && onDeleteTask(data); }} />
</Tooltip>
</Space>
</div>
......
import React, { useState, useEffect } from 'react';
import { Modal, Table, Typography, Spin, Empty, Button } from 'antd';
import { dispatch } from '../../../../model';
const TaskLogModal = (props) => {
const { visible, onCancel, id } = props;
const [ stateDatas, setStateDatas ] = useState([]);
const [ taskTracersBindStateId, setTaskTracersBindStateId ] = useState([]);
const [ expandedRowKeys, setExpandedRowKeys ] = useState([]);
const columns = [
{
title: '序号',
dataIndex: 'key',
render: (text, record, index) => {
return (index+1).toString();
},
width:80
},
{
title: '执行状态',
dataIndex: 'stateDesc',
ellipsis: true,
},
{
title: '开始时间',
dataIndex: 'tsDate',
ellipsis: true,
},
{
title: '结束时间',
dataIndex: 'etsDate',
ellipsis: true
}
];
useEffect(() => {
if (visible) {
getTaskStates();
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [ visible ])
const getTaskStates = () => {
dispatch({
type: 'datasource.getTaskStates',
payload: {
harvestingTaskId: id
},
callback: data => {
setStateDatas(data||[]);
}
})
}
const reset = () => {
setStateDatas([]);
setTaskTracersBindStateId([]);
setExpandedRowKeys([]);
}
return (
<Modal
className='task-report-modal'
forceRender
title={'日志详情'}
visible={visible}
width={1000}
onCancel={() => {
reset();
onCancel && onCancel()
}}
footer = {
<Button
key="1"
type="primary"
onClick={() => {
reset();
onCancel && onCancel()
}}
>
取消
</Button>
}
>
<Table
className='mt-5'
columns={columns}
rowKey={'id'}
dataSource={stateDatas||[]}
pagination={false}
sticky
expandable={{
expandedRowRender: record => {
let _tracer = null;
(taskTracersBindStateId||[]).forEach(item => {
if (item.key === record.id) {
_tracer = item.value||[];
}
})
return (
<Spin spinning={_tracer===null}>
{
(_tracer||[]).length === 0 && <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无日志" />
}
{
_tracer && _tracer.map((_item, index) => {
return (
<Typography.Paragraph key={index} >
{_item.value||''}
</Typography.Paragraph>
)
})
}
</Spin>
);
},
expandedRowKeys,
onExpand: (expanded, record) => {
let exsit = false;
(taskTracersBindStateId||[]).forEach(item => {
if (item.key === record.id) {
exsit = true;
}
})
const newExpandedKeys = [...expandedRowKeys];
if (expanded) {
newExpandedKeys.push(record.id||'');
} else {
const index = newExpandedKeys.indexOf(record.id||'');
newExpandedKeys.splice(index, 1);
}
setExpandedRowKeys([...newExpandedKeys]);
if (expanded && !exsit) {
setTaskTracersBindStateId([...taskTracersBindStateId, { key: record.id, value: record.executionTraces||[] }]);
}
}
}}
/>
</Modal>
);
}
export default TaskLogModal;
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import { Modal, Row, Col, Card, Table, Typography, Spin, Empty, Button, DatePicker } from 'antd';
import { Modal, Row, Col, Card, Table, Typography, Spin, Empty, Button, DatePicker, Input } from 'antd';
import classNames from 'classnames';
import 'moment/locale/zh-cn';
import locale from 'antd/es/date-picker/locale/zh_CN';
import { dispatch, dispatchLatest } from '../../../../model';
import './TaskReportModal.less';
import './TaskLogsModal.less';
const { Meta } = Card;
const { RangePicker } = DatePicker;
const TaskReportModal = (props) => {
const TaskLogsModal = (props) => {
const { visible, onCancel, scope, scopes } = props;
const [ summaryDatas, setSummaryDatas ] = useState([]);
......@@ -19,6 +19,8 @@ const TaskReportModal = (props) => {
const [ taskTracersBindStateId, setTaskTracersBindStateId ] = useState([]);
const [ rangeValue, setRangeValue ] = useState([]);
const [ expandedRowKeys, setExpandedRowKeys ] = useState([]);
const [ keyword, setKeyword ] = useState('');
const [ filterReportData, setFilterReportData ] = useState([]);
useEffect(() => {
......@@ -108,6 +110,8 @@ const TaskReportModal = (props) => {
})
setReportDetail(data||{});
setFilterReportData(data?(data.datas||[]).filter(item => (item.scope||'').indexOf(keyword)!==-1 || (item.databaseName).indexOf(keyword)!==-1):[]);
}
})
}
......@@ -148,16 +152,23 @@ const TaskReportModal = (props) => {
setTaskTracersBindStateId([]);
setCurrentSummaryData(item);
setExpandedRowKeys([]);
getTaskReportDetail(item.params);
getTaskReportDetail(item.params, (rangeValue.length)>=1?rangeValue[0]: null, (rangeValue.length)===2?rangeValue[1]: null);
}
}
const onSearchInputChange = (e) => {
setKeyword(e.target.value||'');
setFilterReportData((reportDetail.datas||[]).filter(item => (item.scope||'').indexOf(e.target.value||'')!==-1 || (item.databaseName||'').indexOf(e.target.value||'')!==-1));
}
const reset = () => {
setSummaryDatas([]);
setCurrentSummaryData({});
setReportDetail({});
setTaskTracersBindStateId([]);
setExpandedRowKeys([]);
setFilterReportData([]);
setKeyword('');
}
return (
......@@ -224,11 +235,21 @@ const TaskReportModal = (props) => {
})
}
</Row>
<div className='d-flex mt-5' style={{ alignItems: 'center' }}>
<span className='mr-3'>日志搜索:</span>
<Input
placeholder="请输入系统名称或者数据源名称"
allowClear
value={keyword}
onChange={onSearchInputChange}
style={{ width: 250 }}
/>
</div>
<Table
className='mt-5'
className='mt-3'
columns={reportDetail.title||[]}
rowKey={'taskStateId'}
dataSource={reportDetail.datas||[]}
dataSource={filterReportData||[]}
pagination={false}
sticky
expandable={{
......@@ -288,4 +309,4 @@ const TaskReportModal = (props) => {
);
}
export default TaskReportModal;
\ No newline at end of file
export default TaskLogsModal;
\ No newline at end of file
......@@ -3,10 +3,12 @@ import { Space, Select, Button, Modal, Cascader } from 'antd';
import DatasourceList from './Component/DatasourceList';
import UpdateDatasourceModal from './Component/UpdateDatasourceModal';
import TaskReportModal from './Component/TaskReportModal';
import TaskLogsModal from './Component/TaskLogsModal';
import TaskLogModal from './Component/TaskLogModal';
import UpdateTaskModal from './Component/UpdateTaskModal';
import { dispatch } from '../../../model';
import { showMessage } from '../../../util';
import { DatasourceContext } from './Component/ContextManager.js';
import './index.less';
......@@ -27,7 +29,8 @@ const DatasourceManage = () => {
const [ updateDatasourceModalAction, setUpdateDatasourceModalAction ] = useState('');
const [ updateTaskModalVisible, setUpdateTaskModalVisible ] = useState(false);
const [ updateTaskModalAction, setUpdateTaskModalAction ] = useState('');
const [ taskReportModalVisible, setTaskReportModalVisible ] = useState(false);
const [ taskLogsModalVisible, setTaskLogsModalVisible ] = useState(false);
const [ taskLogModalVisible, setTaskLogModalVisible ] = useState(false);
const [ currentDatasourceId, setCurrentDatasourceId ] = useState('');
const [ currentDatasourceIdBindTasksNeedRefresh, setCurrentDatasourceIdBindTasksNeedRefresh ] = useState('');
const [ currentTaskId, setCurrentTaskId ] = useState('');
......@@ -136,18 +139,18 @@ const DatasourceManage = () => {
setFilterDatasources((datasources||[]).filter(item=>value==='all'||item.type===value));
}
const showTaskReport = () => {
const showTaskLogs = () => {
if (loadingDatabases) {
showMessage('info', '正在获取系统信息,请稍后');
return;
}
setTaskReportModalVisible(true);
setTaskLogsModalVisible(true);
}
const onTaskReportModalCancel = () => {
setTaskReportModalVisible(false);
const onTaskLogsModalCancel = () => {
setTaskLogsModalVisible(false);
}
const addDatasource = () => {
......@@ -224,6 +227,11 @@ const DatasourceManage = () => {
setUpdateTaskModalVisible(true);
}
const logTask = (task) => {
setCurrentTaskId(task.id);
setTaskLogModalVisible(true);
}
const deleteTask = (task) => {
modal.confirm({
title: '是否确认删除该任务?',
......@@ -249,11 +257,22 @@ const DatasourceManage = () => {
refresh && setCurrentDatasourceIdBindTasksNeedRefresh(currentDatasourceId);
}
const onTaskLogModalCancel = () => {
setTaskLogModalVisible(false);
}
const refreshTasksSuccess = () => {
setCurrentDatasourceIdBindTasksNeedRefresh('');
}
return (
<DatasourceContext.Provider value={{
refreshTasksSuccess: refreshTasksSuccess,
onStartTask: startTask,
onLogTask: logTask,
onEditTask: editTask,
onDeleteTask: deleteTask,
}}>
<div className='datasource-manage' style={{ backgroundColor: '#fff' }}>
<div
className='d-flex p-3'
......@@ -288,7 +307,7 @@ const DatasourceManage = () => {
</Select>
</Space>
<Space>
<Button type='primary' onClick={showTaskReport}>日志报告</Button>
<Button type='primary' onClick={showTaskLogs}>日志报告</Button>
<Button type='primary' onClick={addDatasource}>新增数据源</Button>
</Space>
</div>
......@@ -302,18 +321,14 @@ const DatasourceManage = () => {
onEdit={editDatasource}
onAddTask={addTask}
onDelete={deleteDatasource}
onStartTask={startTask}
onEditTask={editTask}
onDeleteTask={deleteTask}
refreshTasksSuccess={refreshTasksSuccess}
/>
</div>
<TaskReportModal
visible={taskReportModalVisible}
<TaskLogsModal
visible={taskLogsModalVisible}
scope={selectedScope}
scopes={scopes}
onCancel={onTaskReportModalCancel}
onCancel={onTaskLogsModalCancel}
/>
<UpdateDatasourceModal
......@@ -332,9 +347,16 @@ const DatasourceManage = () => {
action={updateTaskModalAction}
onCancel={onUpdateTaskModalVisibleCancel}
/>
<TaskLogModal
visible={taskLogModalVisible}
id={currentTaskId}
onCancel={onTaskLogModalCancel}
/>
{contextHolder}
</div>
</DatasourceContext.Provider>
);
}
......
#left-control-container {
opacity: .5;
position: absolute;
top: 50%;
left: 20px;
......@@ -39,7 +38,6 @@
#right-control-container {
opacity: .5;
position: absolute;
top: 50%;
right: 20px;
......
......@@ -5,7 +5,6 @@ import circle from './circle.93e1.png'
import node from './node-bg-select.171e.png'
import './Relation.css'
import { EXPAND_ICON } from './Relation1';
const colors = [
'#BDD2FD',
......@@ -19,8 +18,6 @@ const colors = [
'#FFD6E7',
];
const highlight = '#ff7800'
class Relation extends React.Component {
......@@ -28,21 +25,11 @@ class Relation extends React.Component {
// console.debug(this.props?.data)
}
componentDidUpdate(prevProps, _prevState) {
const { childData, parentNodeId, data } = this.props;
if (parentNodeId && parentNodeId !== prevProps.parentNodeId) {
const parentData = this.graph?.findDataById(parentNodeId);
if (!parentData.children) {
parentData.children = [];
}
parentData.children = childData;
this.graph?.changeData();
this.graph?.fitView();
} else if (this.props.data) {
this.graph?.destroy();
this.graph = init(this)(this.elem, this.props.data)
componentDidUpdate() {
this.graph?.destroy();
if (this.props.data) {
this.graph = init(this.elem, this.props.data)
}
}
graph = undefined
......@@ -51,11 +38,11 @@ class Relation extends React.Component {
render() {
return (
<React.Fragment>
<div ref={ref => this.elem = ref} style={{ width: '100%', height: '100%', }}>
</div>
</React.Fragment>
<div ref={ref => this.elem = ref} style={{ width: '100%', height: '100%', overflow: 'hidden', backgroundImage: 'linear-gradient(to bottom left,#2e3b4e,#0d0d13 52%,#0d0d13)' }}>
<div id="left-control-container"></div>
<div id="right-control-container"></div>
<div id="bottom-bg-container"></div>
</div>
);
}
}
......@@ -81,7 +68,7 @@ function countDepth(node, _depth = 0) {
}
}
const init = (ctx) => function (container, data) {
function init(container, data) {
depthCount = {}
countDepth(data)
// console.debug(depthCount)
......@@ -157,47 +144,15 @@ const init = (ctx) => function (container, data) {
});
});
graph.on('edge:mouseenter', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', true);
});
graph.on('edge:mouseleave', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', false);
});
// click node to show the detail drawer
graph.on('node:click', (evt) => {
const focusNodes = graph.findAllByState('node', 'focus');
focusNodes.forEach((fnode) => {
graph.setItemState(fnode, 'focus', false); // false
});
const { item, target } = evt;
const { item } = evt;
graph.setItemState(item, 'focus', true);
graph.setItemState(item, 'hover', false);
const targetType = target.get('type');
const name = target.get('name');
if (targetType === 'marker') { // 响应展开按钮
const model = item.getModel();
if (name === 'add-item') {
const nodeId = item.get('id');
if (model.dbType === 'Dir') {
const children = model.children;
if (!children) {
ctx.props?.loadMoreData(model.dirId || '', nodeId);
}
} else if (model.dbType !== 'Root') {
//通过资产id跳转到资产详情页
// history && history.push(`${ContextPath}/home`);
}
}
} else { // 展示 node 详情
}
});
if (typeof window !== 'undefined')
......@@ -219,44 +174,46 @@ G6.registerNode(
let r = 10, width1 = 0, width2 = 0, lineWidth = 0
if (depthCount[cfg.depth] > 10) {
r = 6
r = 7
}
width1 = r * 2.5
width2 = r * 4
width1 = r * 3.5
width2 = r * 5
if (cfg.depth === 0) {
root = cfg
}
// if (depthCount[cfg.depth] > 10) {
group.addShape('image', {
attrs: {
x: -width1 / 2,
y: -width1 / 2,
width: width1,
height: width1,
img: circle,
},
name: 'halo-shape',
visible: false,
});
// }
if (depthCount[cfg.depth] > 10) {
group.addShape('image', {
attrs: {
x: -width1 / 2,
y: -width1 / 2,
width: width1,
height: width1,
img: circle,
},
name: 'halo-shape',
visible: false,
});
}
width1 = r * 2.5
width2 = r * 4
// if (depthCount[cfg.depth] > 10) {
group.addShape('image', {
attrs: {
x: -width2 / 2,
y: -width2 / 2,
width: width2,
height: width2,
img: node,
},
name: 'focus-shape',
visible: false,
});
if (depthCount[cfg.depth] > 10) {
group.addShape('image', {
attrs: {
x: -width2 / 2,
y: -width2 / 2,
width: width2,
height: width2,
img: node,
},
name: 'focus-shape',
visible: false,
});
lineWidth = .8
// }
lineWidth = .8
}
const keyShape = group.addShape('circle', {
attrs: {
......@@ -264,7 +221,7 @@ G6.registerNode(
y: 0,
r,
fill: colors[cfg.depth % colors.length],
stroke: highlight,
stroke: '#ff7800',
lineWidth,
cursor: 'pointer',
},
......@@ -300,40 +257,7 @@ G6.registerNode(
},
name: 'text-shape',
className: 'text-shape',
});
}
const bbox = keyShape.getBBox()
if (cfg.dbType === 'Root' || cfg.dbType === 'Dir') {
if (!cfg.children) {
const marker = group.addShape('marker', {
attrs: {
x: 0,
y: 0,
r: 3,
symbol: EXPAND_ICON,
stroke: '#73d13d',
lineWidth: .7,
cursor: 'pointer',
},
name: 'add-item',
});
const mbbox = marker.getBBox()
// console.debug(bbox, mbbox)
marker.moveTo(bbox.maxX + mbbox.width/2, bbox.minY + mbbox.height)
}
// else if (cfg?.children?.length > 10) {
// group.addShape('marker', {
// attrs: {
// x: bbox.maxX + 8,
// y: bbox.height >> 1,
// r: 6,
// symbol: cfg.collapsed ? EXPAND_ICON : COLLAPSE_ICON,
// stroke: cfg.collapsed ? '#73d13d' : '#ff4d4f',
// lineWidth: 2,
// }
// });
// }
});
}
return keyShape;
......@@ -477,51 +401,43 @@ G6.registerEdge(
return group;
},
// afterDraw: (cfg, group) => {
afterDraw: (cfg, group) => {
// },
},
setState: (name, value, item) => {
if (name === 'active') {
const keyShape = item.get('keyShape').get('children')[0];
if (name === 'focus') {
const keyShape = item.get('keyShape');
if (value) {
keyShape.attr('stroke', highlight);
if (keyShape.cfg.animating) {
return
}
// const length = keyShape.getTotalLength();
// To fix stopAnimate not immediately bug
window.setTimeout(() => {
keyShape.animate(
(ratio) => {
// the operations in each frame. Ratio ranges from 0 to 1 indicating the prograss of the animation. Returns the modified configurations
// const startLen = ratio * length;
// Calculate the lineDash
const cfg = {
// lineDash: [startLen, length - startLen],
lineDash,
lineDashOffset: - Math.floor(ratio * 10),
};
return cfg;
},
{
repeat: true, // Whether executes the animation repeatly
duration: 1000, // the duration for executing once
},
);
}, 100)
} else {
keyShape.attr('stroke', '#aaa');
keyShape.stopAnimate();
keyShape.attr('lineDash', null)
}
}
// if (name === 'focus') {
// const keyShape = item.get('keyShape');
// if (value) {
// if (keyShape.cfg.animating) {
// return
// }
// // const length = keyShape.getTotalLength();
// // To fix stopAnimate not immediately bug
// window.setTimeout(() => {
// keyShape.animate(
// (ratio) => {
// // the operations in each frame. Ratio ranges from 0 to 1 indicating the prograss of the animation. Returns the modified configurations
// // const startLen = ratio * length;
// // Calculate the lineDash
// const cfg = {
// // lineDash: [startLen, length - startLen],
// lineDash,
// lineDashOffset: - Math.floor(ratio * 10),
// };
// return cfg;
// },
// {
// repeat: true, // Whether executes the animation repeatly
// duration: 1000, // the duration for executing once
// },
// );
// }, 100)
// } else {
// keyShape.stopAnimate();
// keyShape.attr('lineDash', null)
// }
// }
}
},
// 'single-edge',
......
......@@ -16,7 +16,7 @@ const colors = [
'#FFD6E7',
];
export const COLLAPSE_ICON = function COLLAPSE_ICON(x, y, r) {
const COLLAPSE_ICON = function COLLAPSE_ICON(x, y, r) {
return [
['M', x - r, y - r],
['a', r, r, 0, 1, 0, r * 2, 0],
......@@ -25,7 +25,7 @@ export const COLLAPSE_ICON = function COLLAPSE_ICON(x, y, r) {
['L', x + r - 2, y - r],
];
};
export const EXPAND_ICON = function EXPAND_ICON(x, y, r) {
const EXPAND_ICON = function EXPAND_ICON(x, y, r) {
return [
['M', x - r, y - r],
['a', r, r, 0, 1, 0, r * 2, 0],
......
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useRef } from "react";
import { Table, Space, Button, Tooltip, Modal } from 'antd';
import { EditOutlined, ReconciliationOutlined, DeleteOutlined } from '@ant-design/icons';
import SmoothScroll from 'smooth-scroll';
import { dispatchLatest } from '../../../../model';
import { showMessage } from '../../../../util';
import { showMessage, getQueryParam } from '../../../../util';
import './ModelTable.less';
const ModelTable = (props) => {
......@@ -13,6 +14,9 @@ const ModelTable = (props) => {
const [modal, contextHolder] = Modal.useModal();
const anchorId = getQueryParam('id', props.location.search);
const shouldScrollRef = useRef(anchorId!==null);
useEffect(() => {
setSelectedRowKeys([]);
......@@ -21,6 +25,21 @@ const ModelTable = (props) => {
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [ catalogId ]);
useEffect(() => {
if (shouldScrollRef.current) {
SmoothScroll('a[href*="#"]');
const _id = getQueryParam('id', props.location.search);
var scroll = new SmoothScroll();
var anchor = document.querySelector(`#data-model-${_id}`);
if (anchor) {
scroll.animateScroll(anchor);
shouldScrollRef.current = false;
}
}
})
const columns = [
{
title: '序号',
......@@ -122,10 +141,24 @@ const ModelTable = (props) => {
onChange: onSelectChange,
};
const AnchorRow = (props) => {
const id = props['data-row-key']||'';
return (
<tr id={`data-model-${id}`} {...props} style={{ backgroundColor: (id===anchorId)?'#e7f7ff':'transparent' }} />
);
}
return (
<div className='model-table'>
<Table
loading={loading}
components={{
body: {
row: AnchorRow
}
}}
rowSelection={rowSelection}
columns={columns}
rowKey={'id'}
......
......@@ -4,7 +4,7 @@ import { PlusOutlined, EditOutlined, SyncOutlined, DeleteOutlined } from '@ant-
import UpdateTreeItemModal from './UpdateTreeItemModal';
import { dispatch } from '../../../../model';
import { showMessage } from '../../../../util';
import { showMessage, getQueryParam } from '../../../../util';
import './ModelTree.less';
const ModelTree = (props) => {
......@@ -16,6 +16,8 @@ const ModelTree = (props) => {
const [ visible, setVisible ] = useState(false);
const [ type, setType ] = useState(null);
const [ rootId, setRootId ] = useState('');
const [ expandedKeys, setExpandedKeys ] = useState([]);
const [ autoExpandParent, setAutoExpandParent ] = useState(false);
const [modal, contextHolder] = Modal.useModal();
......@@ -23,11 +25,13 @@ const ModelTree = (props) => {
itemRef.current = item;
useEffect(() => {
getTreeData();
const _did = getQueryParam('did', props.location.search);
getTreeData(_did);
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const getTreeData = () => {
const getTreeData = (defaultSelectedId='') => {
setLoading(true);
dispatch({
......@@ -38,6 +42,8 @@ const ModelTree = (props) => {
data.title = data.name||'';
data.children = data.subCatalogs||[];
let defaultItem = null;
function recursion(subCatalogs) {
if ((subCatalogs||[]).length===0) return;
......@@ -46,6 +52,11 @@ const ModelTree = (props) => {
catalog.key = catalog.id||'';
catalog.title = catalog.name||'';
catalog.children = catalog.subCatalogs||[];
if (catalog.id === defaultSelectedId) {
defaultItem = catalog;
}
recursion(catalog.subCatalogs);
})
}
......@@ -55,16 +66,40 @@ const ModelTree = (props) => {
setTreeData(data.subCatalogs||[]);
setRootId(data.id||'');
let firstItem = itemRef.current;
if (firstItem === null) {
firstItem = (data.subCatalogs||[]).length>0?data.subCatalogs[0]: null;
if (defaultItem) {
setItem(firstItem);
}
const _dataList = [];
generateList(data.subCatalogs||[], _dataList);
const expandedKeys = _dataList
.map(item => {
if (item.key.indexOf(defaultSelectedId) > -1) {
return getParentKey(item.key, data.subCatalogs||[]);
}
return null;
})
.filter((item, i, self) => item && self.indexOf(item) === i);
setExpandedKeys(expandedKeys);
setAutoExpandParent(true);
itemRef.current = firstItem;
setItem(defaultItem);
onSelect && onSelect(defaultItem.key||'');
} else {
onSelect && onSelect(firstItem?(firstItem.key||''):'');
let firstItem = itemRef.current;
if (firstItem === null) {
firstItem = (data.subCatalogs||[]).length>0?data.subCatalogs[0]: null;
setItem(firstItem);
}
itemRef.current = firstItem;
onSelect && onSelect(firstItem?(firstItem.key||''):'');
}
},
error: () => {
setLoading(false);
......@@ -72,6 +107,37 @@ const ModelTree = (props) => {
})
}
const generateList = (treeData, list) => {
for (let i = 0; i < treeData.length; i++) {
const node = treeData[i];
const { id, name } = node;
list.push({ key: id , title: name });
if (node.children) {
generateList(node.children, list);
}
}
};
const getParentKey = (key, tree) => {
let parentKey;
for (let i = 0; i < tree.length; i++) {
const node = tree[i];
if (node.children) {
if (node.children.some(item => item.id === key)) {
parentKey = node.id;
} else if (getParentKey(key, node.children)) {
parentKey = getParentKey(key, node.children);
}
}
}
return parentKey;
};
const onExpand = (expandedKeys) => {
setExpandedKeys(expandedKeys);
setAutoExpandParent(false);
};
const onTreeSelect = (keys,data) => {
if ((keys||[]).length === 0) {
......@@ -188,6 +254,9 @@ const ModelTree = (props) => {
>
<Spin spinning={loading} >
<Tree
onExpand={onExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
showLine
showIcon={false}
onSelect={onTreeSelect}
......
......@@ -215,7 +215,7 @@ class Model extends React.Component {
<Row>
<Col span={6} >
<div className='mr-4' style={{ backgroundColor: '#fff' }}>
<ModelTree onSelect={this.onTreeSelect} />
<ModelTree onSelect={this.onTreeSelect} {...this.props} />
</div>
</Col>
<Col span={18}>
......@@ -270,7 +270,7 @@ class Model extends React.Component {
</Space>
</div>
<div className='p-3'>
<ModelTable loading={loadingTableData} catalogId={catalogId} data={filterTableData} onChange={this.onTableChange} onSelect={this.onTableSelect} onItemAction={this.onTableItemAction} />
<ModelTable loading={loadingTableData} catalogId={catalogId} data={filterTableData} onChange={this.onTableChange} onSelect={this.onTableSelect} onItemAction={this.onTableItemAction} {...this.props} />
</div>
</div>
</Col>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment