123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409 |
- /*
- * <<
- * 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, { useState, useEffect, useCallback, useMemo } from 'react'
- import pick from 'lodash/pick'
- import { ISourceFormValues, IDatasourceInfo } from '../types'
- import {
- Modal,
- Form,
- Row,
- Col,
- Button,
- Input,
- Select,
- Icon,
- Cascader
- } from 'antd'
- const FormItem = Form.Item
- const TextArea = Input.TextArea
- const Option = Select.Option
- import { FormComponentProps } from 'antd/lib/form/Form'
- import { CascaderOptionType } from 'antd/lib/cascader'
- import { SourceProperty } from './types'
- import {
- EditableFormTable,
- EditableColumnProps
- } from 'components/Table/Editable'
- const utilStyles = require('assets/less/util.less')
- interface ISourceConfigModalProps
- extends FormComponentProps<ISourceFormValues> {
- visible: boolean
- formLoading: boolean
- testLoading: boolean
- source: ISourceFormValues
- datasourcesInfo: IDatasourceInfo[]
- onSave: (values: any) => void
- onClose: () => void
- onTestSourceConnection: (
- username: string,
- password: string,
- jdbcUrl: string,
- ext: boolean,
- version: string
- ) => any
- onCheckUniqueName: (
- pathname: string,
- data: any,
- resolve: () => any,
- reject: (error: string) => any
- ) => any
- }
- const commonFormItemStyle = {
- labelCol: { span: 6 },
- wrapperCol: { span: 16 }
- }
- const longFormItemStyle = {
- labelCol: { span: 3 },
- wrapperCol: { span: 20 }
- }
- const datasourceInfoDisplayRender = (label: string[]) => label.join(' : ')
- const columns: Array<EditableColumnProps<SourceProperty>> = [
- {
- title: '键',
- dataIndex: 'key',
- width: '30%',
- editable: true,
- inputType: 'input'
- },
- {
- title: '值',
- dataIndex: 'value',
- editable: true,
- inputType: 'input'
- }
- ]
- const SourceConfigModal: React.FC<ISourceConfigModalProps> = (props) => {
- const {
- visible,
- source,
- datasourcesInfo,
- form,
- formLoading,
- testLoading,
- onTestSourceConnection,
- onCheckUniqueName,
- onSave,
- onClose
- } = props
- if (!source) {
- return null
- }
- const { id: sourceId } = source
- const { getFieldDecorator } = form
- const [sourceProperties, setSourceProperties] = useState<SourceProperty[]>([])
- useEffect(
- () => {
- let fieldsKeys: Array<keyof ISourceFormValues> = [
- 'id',
- 'name',
- 'type',
- 'datasourceInfo',
- 'description'
- ]
- // @FIXME nested object properties name typing
- fieldsKeys = []
- .concat(fieldsKeys)
- .concat(['config.username', 'config.password', 'config.url'])
- const fieldsValue = pick(source, fieldsKeys)
- form.setFieldsValue(fieldsValue)
- },
- [source, visible]
- )
- useEffect(
- () => {
- setSourceProperties([...(source.config.properties || [])])
- },
- [source]
- )
- const addProperty = useCallback(
- () => {
- const propertySerial = sourceProperties.length + 1
- setSourceProperties([
- ...sourceProperties,
- {
- key: `新键 ${propertySerial}`,
- value: `新值 ${propertySerial}`
- }
- ])
- },
- [sourceProperties]
- )
- const saveProperties = useCallback((properties: SourceProperty[]) => {
- setSourceProperties(properties)
- }, [])
- const checkNameUnique = useCallback(
- (_, name = '', callback) => {
- const { id, projectId } = source
- const data = { id, name, projectId }
- if (!name) {
- callback()
- }
- onCheckUniqueName(
- 'source',
- data,
- () => {
- callback()
- },
- (err) => callback(err)
- )
- },
- [onCheckUniqueName, source]
- )
- const datasourceInfoChange = useCallback(
- (value: string[]) => {
- const datasourceName = value[0]
- const selectedDatasource = datasourcesInfo.find(
- ({ name }) => name === datasourceName
- )
- const prefix = selectedDatasource.prefix
- const currentUrl = form.getFieldValue('config.url') as string
- let hasMatched = false
- let newUrl = currentUrl.replace(/^jdbc:([\w:]+):/, (match) => {
- hasMatched = !!match
- return prefix
- })
- if (!hasMatched) {
- newUrl = prefix + currentUrl
- }
- form.setFieldsValue({ 'config.url': newUrl })
- },
- [datasourcesInfo]
- )
- const testSourceConnection = useCallback(
- () => {
- const {
- datasourceInfo,
- config
- } = form.getFieldsValue() as ISourceFormValues
- const { username, password, url } = config
- const version =
- datasourceInfo[1] === 'Default' ? '' : datasourceInfo[1] || ''
- onTestSourceConnection(username, password, url, !!version, version)
- },
- [form, onTestSourceConnection]
- )
- const save = useCallback(
- () => {
- form.validateFieldsAndScroll((err, values) => {
- if (!err) {
- values.config.properties = sourceProperties.filter(({ key }) => !!key)
- onSave(values)
- }
- })
- },
- [form, sourceProperties, onSave]
- )
- const reset = useCallback(
- () => {
- form.resetFields()
- setSourceProperties([])
- },
- [form]
- )
- const cascaderOptions: CascaderOptionType[] = useMemo(
- () =>
- datasourcesInfo.map(({ name, versions }) => ({
- label: name,
- value: name,
- ...(versions && {
- children: versions.map((ver) => ({
- label: ver,
- value: ver
- }))
- })
- })),
- [datasourcesInfo]
- )
- const modalButtons = useMemo(
- () => [
- <Button
- key="submit"
- size="large"
- type="primary"
- loading={formLoading}
- disabled={formLoading}
- onClick={save}
- >
- 保 存
- </Button>,
- <Button key="back" size="large" onClick={onClose}>
- 取 消
- </Button>
- ],
- [form, formLoading, sourceProperties, onSave, onClose]
- )
- return (
- <Modal
- title={`${!sourceId ? '新增' : '修改'}`}
- wrapClassName="ant-modal-large"
- maskClosable={false}
- visible={visible}
- footer={modalButtons}
- onCancel={onClose}
- afterClose={reset}
- >
- <Form>
- <Row>
- <FormItem className={utilStyles.hide}>
- {getFieldDecorator<ISourceFormValues>('id')(<Input />)}
- </FormItem>
- <Col span={12}>
- <FormItem label="名称" {...commonFormItemStyle} hasFeedback>
- {getFieldDecorator<ISourceFormValues>('name', {
- rules: [
- {
- required: true,
- message: '名称不能为空'
- },
- {
- validator: checkNameUnique
- }
- ]
- })(<Input autoComplete="off" placeholder="名称" />)}
- </FormItem>
- </Col>
- </Row>
- <Row>
- <Col span={12}>
- <FormItem label="类型" {...commonFormItemStyle}>
- {getFieldDecorator<ISourceFormValues>('type', {
- initialValue: 'jdbc'
- })(
- <Select>
- <Option value="jdbc">JDBC</Option>
- <Option value="csv">CSV文件</Option>
- </Select>
- )}
- </FormItem>
- </Col>
- <Col span={12}>
- <FormItem label="数据库" {...commonFormItemStyle}>
- {getFieldDecorator<ISourceFormValues>('datasourceInfo', {
- initialValue: []
- })(
- <Cascader
- options={cascaderOptions}
- displayRender={datasourceInfoDisplayRender}
- onChange={datasourceInfoChange}
- />
- )}
- </FormItem>
- </Col>
- <Col span={12}>
- <FormItem label="用户名" {...commonFormItemStyle}>
- {getFieldDecorator('config.username', {
- initialValue: ''
- })(<Input autoComplete="off" placeholder="用户名" />)}
- </FormItem>
- </Col>
- <Col span={12}>
- <FormItem label="密码" {...commonFormItemStyle}>
- {getFieldDecorator('config.password', {
- initialValue: ''
- })(
- <Input
- autoComplete="off"
- placeholder="密码"
- type="password"
- />
- )}
- </FormItem>
- </Col>
- </Row>
- <FormItem label="连接Url" {...longFormItemStyle}>
- {getFieldDecorator('config.url', {
- rules: [
- {
- required: true,
- message: 'Url 不能为空'
- }
- ],
- initialValue: ''
- })(
- <Input
- placeholder="连接Url"
- autoComplete="off"
- addonAfter={
- testLoading ? (
- <Icon type="loading" />
- ) : (
- <span
- onClick={testSourceConnection}
- style={{ cursor: 'pointer' }}
- >
- 点击测试
- </span>
- )
- }
- />
- )}
- </FormItem>
- <FormItem label="描述" {...longFormItemStyle}>
- {getFieldDecorator('description', {
- initialValue: ''
- })(
- <TextArea
- placeholder="描述"
- autosize={{ minRows: 2, maxRows: 6 }}
- />
- )}
- </FormItem>
- </Form>
- <FormItem label="配置信息" {...longFormItemStyle}>
- <Button
- type="primary"
- size="small"
- shape="circle"
- icon="plus"
- onClick={addProperty}
- />
- <EditableFormTable
- data={sourceProperties}
- dataKey="key"
- columns={columns}
- onSave={saveProperties}
- />
- </FormItem>
- </Modal>
- )
- }
- export default Form.create<ISourceConfigModalProps>()(SourceConfigModal)
|