123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640 |
- /*
- * <<
- * Davinci
- * ==
- * Copyright (C) 2016 - 2017 EDP
- * ==
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * >>
- */
- import React from 'react'
- import Helmet from 'react-helmet'
- import { connect } from 'react-redux'
- import { createStructuredSelector } from 'reselect'
- import memoizeOne from 'memoize-one'
- import { Link } from 'react-router-dom'
- import { RouteComponentWithParams } from 'utils/types'
- import { compose, Dispatch } from 'redux'
- import injectReducer from 'utils/injectReducer'
- import injectSaga from 'utils/injectSaga'
- import reducer from './reducer'
- import saga from './sagas'
- import Container, { ContainerTitle, ContainerBody } from 'components/Container'
- import Box from 'components/Box'
- import SearchFilterDropdown from 'components/SearchFilterDropdown'
- import SourceConfigModal from './components/SourceConfigModal'
- import UploadCsvModal from './components/UploadCsvModal'
- import ResetConnectionModal from './components/ResetConnectionModal'
- import {
- message,
- Row,
- Col,
- Table,
- Button,
- Tooltip,
- Icon,
- Popconfirm,
- Breadcrumb
- } from 'antd'
- import { ButtonProps } from 'antd/lib/button/button'
- import { ColumnProps, PaginationConfig, SorterResult } from 'antd/lib/table'
- import { SourceActions, SourceActionType } from './actions'
- import {
- makeSelectSources,
- makeSelectListLoading,
- makeSelectFormLoading,
- makeSelectTestLoading,
- makeSelectResetLoading,
- makeSelectDatasourcesInfo
- } from './selectors'
- import { checkNameUniqueAction } from '../App/actions'
- import { makeSelectCurrentProject } from '../Projects/selectors'
- import ModulePermission from '../Account/components/checkModulePermission'
- import { initializePermission } from '../Account/components/checkUtilPermission'
- import { IProject } from 'containers/Projects/types'
- import {
- ISourceBase,
- ISource,
- ICSVMetaInfo,
- ISourceFormValues,
- SourceResetConnectionProperties
- } from './types'
- import utilStyles from 'assets/less/util.less'
- type ISourceListProps = ReturnType<typeof mapStateToProps> &
- ReturnType<typeof mapDispatchToProps> &
- RouteComponentWithParams
- interface ISourceListStates {
- screenWidth: number
- tempFilterSourceName: string
- filterSourceName: string
- filterDropdownVisible: boolean
- tableSorter: SorterResult<ISource>
- sourceModalVisible: boolean
- uploadModalVisible: boolean
- csvUploading: boolean
- resetModalVisible: boolean
- resetSource: ISource
- editingSource: ISourceFormValues
- csvSourceId: number
- }
- const emptySource: ISourceFormValues = {
- id: 0,
- name: '',
- type: 'jdbc',
- description: '',
- projectId: 0,
- datasourceInfo: [],
- config: {
- username: '',
- password: '',
- url: '',
- properties: []
- }
- }
- export class SourceList extends React.PureComponent<ISourceListProps,
- ISourceListStates> {
- public state: Readonly<ISourceListStates> = {
- screenWidth: document.documentElement.clientWidth,
- tempFilterSourceName: '',
- tableSorter: null,
- filterSourceName: '',
- filterDropdownVisible: false,
- sourceModalVisible: false,
- uploadModalVisible: false,
- csvUploading: false,
- resetModalVisible: false,
- resetSource: null,
- editingSource: { ...emptySource },
- csvSourceId: null
- }
- private basePagination: PaginationConfig = {
- defaultPageSize: 20,
- showSizeChanger: true
- }
- public componentWillMount() {
- const { onLoadSources, onLoadDatasourcesInfo, match } = this.props
- const projectId = +match.params.projectId
- onLoadSources(projectId)
- onLoadDatasourcesInfo()
- window.addEventListener('resize', this.setScreenWidth, false)
- }
- public componentWillUnmount() {
- window.removeEventListener('resize', this.setScreenWidth, false)
- }
- private setScreenWidth = () => {
- this.setState({ screenWidth: document.documentElement.clientWidth })
- }
- private getFilterSources = memoizeOne(
- (sourceName: string, sources: ISourceBase[]) => {
- if (!Array.isArray(sources) || !sources.length) {
- return []
- }
- const regex = new RegExp(sourceName, 'gi')
- const filterSources = sources.filter(
- (v) => v.name.match(regex) || v.description.match(regex)
- )
- return filterSources
- }
- )
- private static getSourcePermission = memoizeOne((project: IProject) => ({
- sourcePermission: initializePermission(project, 'sourcePermission'),
- AdminButton: ModulePermission<ButtonProps>(project, 'source', true)(Button),
- EditButton: ModulePermission<ButtonProps>(project, 'source', false)(Button)
- }))
- private getTableColumns = ({
- sourcePermission,
- AdminButton,
- EditButton
- }: ReturnType<typeof SourceList.getSourcePermission>) => {
- const {
- tempFilterSourceName,
- filterSourceName,
- filterDropdownVisible,
- tableSorter
- } = this.state
- const { resetLoading } = this.props
- const columns: Array<ColumnProps<ISource>> = [
- {
- title: '名称',
- dataIndex: 'name',
- filterDropdown: (
- <SearchFilterDropdown
- placeholder='名称'
- value={tempFilterSourceName}
- onChange={this.filterSourceNameChange}
- onSearch={this.searchSource}
- />
- ),
- filterDropdownVisible,
- onFilterDropdownVisibleChange: (visible: boolean) =>
- this.setState({
- filterDropdownVisible: visible
- }),
- sorter: (a, b) => (a.name > b.name ? -1 : 1),
- sortOrder:
- tableSorter && tableSorter.columnKey === 'name'
- ? tableSorter.order
- : void 0
- },
- {
- title: '描述',
- dataIndex: 'description'
- },
- {
- title: '类型',
- dataIndex: 'type',
- filters: [
- {
- text: 'JDBC',
- value: 'jdbc'
- },
- {
- text: 'CSV',
- value: 'csv'
- }
- ],
- filterMultiple: false,
- onFilter: (val, record) => record.type === val,
- render: (_, record) => {
- const type = record.type
- return type && type.toUpperCase()
- }
- }
- ]
- if (filterSourceName) {
- const regex = new RegExp(`(${filterSourceName})`, 'gi')
- columns[0].render = (text: string) => (
- <span
- dangerouslySetInnerHTML={{
- __html: text.replace(
- regex,
- `<span class='${utilStyles.highlight}'>$1</span>`
- )
- }}
- />
- )
- }
- if (sourcePermission) {
- columns.push({
- title: '操作',
- key: 'action',
- width: 180,
- render: (_, record) => (
- <span className='ant-table-action-column'>
- <Tooltip title='重置连接'>
- <EditButton
- icon='reload'
- shape='circle'
- type='ghost'
- disabled={resetLoading}
- onClick={this.openResetSource(record)}
- />
- </Tooltip>
- <Tooltip title='修改'>
- <EditButton
- icon='edit'
- shape='circle'
- type='ghost'
- onClick={this.editSource(record.id)}
- />
- </Tooltip>
- <Popconfirm
- title='确定删除?'
- placement='bottom'
- onConfirm={this.deleteSource(record.id)}
- >
- <Tooltip title='删除'>
- <AdminButton icon='delete' shape='circle' type='ghost' />
- </Tooltip>
- </Popconfirm>
- {record && record.type === 'csv' ? (
- <Tooltip title='上传'>
- <EditButton
- icon='upload'
- shape='circle'
- type='ghost'
- onClick={this.showUploadModal(record.id)}
- />
- </Tooltip>
- ) : (
- ''
- )}
- </span>
- )
- })
- }
- return columns
- }
- private addSource = () => {
- this.setState({
- editingSource: {
- ...emptySource,
- projectId: +this.props.match.params.projectId
- },
- sourceModalVisible: true
- })
- }
- private openResetSource = (source: ISource) => () => {
- this.setState({
- resetModalVisible: true,
- resetSource: source
- })
- }
- private resetConnection = (properties: SourceResetConnectionProperties) => {
- this.props.onResetSourceConnection(properties, () => {
- this.closeResetConnectionModal()
- })
- }
- private closeResetConnectionModal = () => {
- this.setState({ resetModalVisible: false })
- }
- private editSource = (sourceId: number) => () => {
- this.props.onLoadSourceDetail(sourceId, (editingSource) => {
- this.setState({
- editingSource: {
- ...editingSource,
- datasourceInfo: this.getDatasourceInfo(editingSource)
- },
- sourceModalVisible: true
- })
- })
- }
- private getDatasourceInfo = (source: ISource): string[] => {
- const { datasourcesInfo } = this.props
- const { url, version } = source.config
- const matchResult = url.match(/^jdbc\:(\w+)\:/)
- if (matchResult) {
- const datasource = datasourcesInfo.find(
- (info) => info.name === matchResult[1]
- )
- return datasource
- ? datasource.versions.length
- ? [datasource.name, version || 'Default']
- : [datasource.name]
- : []
- } else {
- return []
- }
- }
- private deleteSource = (sourceId: number) => () => {
- const { onDeleteSource } = this.props
- onDeleteSource(sourceId)
- }
- private showUploadModal = (sourceId: number) => () => {
- this.setState({
- csvSourceId: sourceId,
- uploadModalVisible: true
- })
- }
- private saveSourceForm = (values: ISourceFormValues) => {
- const { match } = this.props
- const { datasourceInfo, config, ...rest } = values
- const version =
- datasourceInfo[1] === 'Default' ? '' : datasourceInfo[1] || ''
- const requestValue = {
- ...rest,
- config: {
- ...config,
- ext: !!version,
- version
- },
- projectId: Number(match.params.projectId)
- }
- if (!values.id) {
- this.props.onAddSource({ ...requestValue }, () => {
- this.closeSourceForm()
- })
- } else {
- this.props.onEditSource({ ...requestValue }, () => {
- this.closeSourceForm()
- })
- }
- }
- private closeSourceForm = () => {
- this.setState({
- sourceModalVisible: false
- })
- }
- private closeUploadModal = () => {
- this.setState({
- uploadModalVisible: false
- })
- }
- private uploadCsv = (csvMetaInfo: ICSVMetaInfo) => {
- this.setState({ csvUploading: true }, () => {
- this.props.onUploadCsvFile(
- csvMetaInfo,
- () => {
- this.closeUploadModal()
- message.info('csv 文件上传成功!')
- this.setState({ csvUploading: false })
- },
- () => {
- this.setState({ csvUploading: false })
- }
- )
- })
- }
- private tableChange = (_1, _2, sorter: SorterResult<ISource>) => {
- this.setState({
- tableSorter: sorter
- })
- }
- private filterSourceNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
- this.setState({
- tempFilterSourceName: e.target.value,
- filterSourceName: ''
- })
- }
- private searchSource = (value: string) => {
- this.setState({
- filterSourceName: value,
- filterDropdownVisible: false
- })
- }
- private testSourceConnection = (
- username,
- password,
- jdbcUrl,
- ext,
- version
- ) => {
- if (jdbcUrl) {
- this.props.onTestSourceConnection({
- username,
- password,
- url: jdbcUrl,
- ext,
- version
- })
- } else {
- message.error('连接 Url 都不能为空')
- }
- }
- public render() {
- const {
- filterSourceName,
- sourceModalVisible,
- uploadModalVisible,
- resetModalVisible,
- resetSource,
- csvUploading,
- csvSourceId,
- screenWidth,
- editingSource
- } = this.state
- const {
- sources,
- listLoading,
- formLoading,
- testLoading,
- currentProject,
- datasourcesInfo,
- onValidateCsvTableName,
- onCheckUniqueName
- } = this.props
- const {
- sourcePermission,
- AdminButton,
- EditButton
- } = SourceList.getSourcePermission(currentProject)
- const tableColumns = this.getTableColumns({
- sourcePermission,
- AdminButton,
- EditButton
- })
- const tablePagination: PaginationConfig = {
- ...this.basePagination,
- simple: screenWidth <= 768
- }
- const filterSources = this.getFilterSources(filterSourceName, sources)
- const pathname = this.props.history.location.pathname
- return (
- <Container>
- <Helmet title='数据源' />
- {
- !pathname.includes('dataManager') &&
- <ContainerTitle>
- <Row>
- <Col span={24} className={utilStyles.shortcut}>
- <Breadcrumb className={utilStyles.breadcrumb}>
- <Breadcrumb.Item>
- <Link to=''>Source</Link>
- </Breadcrumb.Item>
- </Breadcrumb>
- <Link to={`/account/organization/${currentProject.orgId}`}>
- <i className='iconfont icon-organization' />
- </Link>
- </Col>
- </Row>
- </ContainerTitle>}
- <ContainerBody>
- <Box>
- <Box.Header>
- <Box.Title>
- <Icon type='bars' />
- 数据源列表
- </Box.Title>
- <Box.Tools>
- <Tooltip placement='bottom' title='新增'>
- <AdminButton
- type='primary'
- icon='plus'
- onClick={this.addSource}
- />
- </Tooltip>
- </Box.Tools>
- </Box.Header>
- <Box.Body>
- <Row>
- <Col span={24}>
- <Table
- bordered
- rowKey='id'
- loading={listLoading}
- dataSource={filterSources}
- columns={tableColumns}
- pagination={tablePagination}
- onChange={this.tableChange}
- />
- </Col>
- </Row>
- <SourceConfigModal
- source={editingSource}
- datasourcesInfo={datasourcesInfo}
- visible={sourceModalVisible}
- formLoading={formLoading}
- testLoading={testLoading}
- onSave={this.saveSourceForm}
- onClose={this.closeSourceForm}
- onTestSourceConnection={this.testSourceConnection}
- onCheckUniqueName={onCheckUniqueName}
- />
- <UploadCsvModal
- visible={uploadModalVisible}
- uploading={csvUploading}
- sourceId={csvSourceId}
- onValidate={onValidateCsvTableName}
- onCancel={this.closeUploadModal}
- onOk={this.uploadCsv}
- />
- <ResetConnectionModal
- visible={resetModalVisible}
- source={resetSource}
- onConfirm={this.resetConnection}
- onCancel={this.closeResetConnectionModal}
- />
- </Box.Body>
- </Box>
- </ContainerBody>
- </Container>
- )
- }
- }
- const mapDispatchToProps = (dispatch: Dispatch<SourceActionType>) => ({
- onLoadSources: (projectId: number) =>
- dispatch(SourceActions.loadSources(projectId)),
- onLoadSourceDetail: (sourceId: number, resolve: (source: ISource) => void) =>
- dispatch(SourceActions.loadSourceDetail(sourceId, resolve)),
- onAddSource: (source: ISource, resolve: () => any) =>
- dispatch(SourceActions.addSource(source, resolve)),
- onDeleteSource: (id: number) => dispatch(SourceActions.deleteSource(id)),
- onEditSource: (source: ISource, resolve: () => void) =>
- dispatch(SourceActions.editSource(source, resolve)),
- onTestSourceConnection: (testSource: Omit<ISource['config'], 'properties'>) =>
- dispatch(SourceActions.testSourceConnection(testSource)),
- onResetSourceConnection: (
- properties: SourceResetConnectionProperties,
- resolve: () => void
- ) => dispatch(SourceActions.resetSourceConnection(properties, resolve)),
- onValidateCsvTableName: (
- csvMeta: ICSVMetaInfo,
- callback: (errMsg?: string) => void
- ) => dispatch(SourceActions.validateCsvTableName(csvMeta, callback)),
- onUploadCsvFile: (
- csvMeta: ICSVMetaInfo,
- resolve: () => void,
- reject: () => void
- ) => dispatch(SourceActions.uploadCsvFile(csvMeta, resolve, reject)),
- onCheckUniqueName: (
- pathname: string,
- data: any,
- resolve: () => void,
- reject: (err: string) => void
- ) => dispatch(checkNameUniqueAction(pathname, data, resolve, reject)),
- onLoadDatasourcesInfo: () => dispatch(SourceActions.loadDatasourcesInfo())
- })
- const mapStateToProps = createStructuredSelector({
- sources: makeSelectSources(),
- listLoading: makeSelectListLoading(),
- formLoading: makeSelectFormLoading(),
- testLoading: makeSelectTestLoading(),
- resetLoading: makeSelectResetLoading(),
- currentProject: makeSelectCurrentProject(),
- datasourcesInfo: makeSelectDatasourcesInfo()
- })
- const withConnect = connect(mapStateToProps, mapDispatchToProps)
- const withReducer = injectReducer({ key: 'source', reducer })
- const withSaga = injectSaga({ key: 'source', saga })
- export default compose(withReducer, withSaga, withConnect)(SourceList)
|