first commit
This commit is contained in:
672
frontend/src/pages/DataSources/DataSources.tsx
Normal file
672
frontend/src/pages/DataSources/DataSources.tsx
Normal file
@@ -0,0 +1,672 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import {
|
||||
Table, Tag, Space, message, Button, Form, Input, Select,
|
||||
Drawer, Tabs, Empty, Tooltip, Popconfirm, Collapse, InputNumber
|
||||
} from 'antd'
|
||||
import {
|
||||
PlayCircleOutlined, PauseCircleOutlined, PlusOutlined,
|
||||
EditOutlined, DeleteOutlined, ApiOutlined,
|
||||
CheckCircleOutlined, CloseCircleOutlined, ExperimentOutlined,
|
||||
SyncOutlined
|
||||
} from '@ant-design/icons'
|
||||
import axios from 'axios'
|
||||
|
||||
interface BuiltInDataSource {
|
||||
id: number
|
||||
name: string
|
||||
module: string
|
||||
priority: string
|
||||
frequency: string
|
||||
is_active: boolean
|
||||
collector_class: string
|
||||
}
|
||||
|
||||
interface CustomDataSource {
|
||||
id: number
|
||||
name: string
|
||||
description: string | null
|
||||
source_type: string
|
||||
endpoint: string
|
||||
auth_type: string
|
||||
is_active: boolean
|
||||
created_at: string
|
||||
updated_at: string | null
|
||||
}
|
||||
|
||||
interface ViewDataSource {
|
||||
id: number
|
||||
name: string
|
||||
description: string | null
|
||||
source_type: string
|
||||
endpoint: string
|
||||
auth_type: string
|
||||
headers: Record<string, string>
|
||||
config: Record<string, any>
|
||||
collector_class: string
|
||||
module: string
|
||||
priority: string
|
||||
frequency: string
|
||||
}
|
||||
|
||||
function DataSources() {
|
||||
const [activeTab, setActiveTab] = useState('builtin')
|
||||
const [builtInSources, setBuiltInSources] = useState<BuiltInDataSource[]>([])
|
||||
const [customSources, setCustomSources] = useState<CustomDataSource[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [drawerVisible, setDrawerVisible] = useState(false)
|
||||
const [viewDrawerVisible, setViewDrawerVisible] = useState(false)
|
||||
const [editingConfig, setEditingConfig] = useState<CustomDataSource | null>(null)
|
||||
const [viewingSource, setViewingSource] = useState<ViewDataSource | null>(null)
|
||||
const [testing, setTesting] = useState(false)
|
||||
const [testResult, setTestResult] = useState<any>(null)
|
||||
const [form] = Form.useForm()
|
||||
|
||||
const fetchData = async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const [builtinRes, customRes] = await Promise.all([
|
||||
axios.get('/api/v1/datasources'),
|
||||
axios.get('/api/v1/datasources/configs')
|
||||
])
|
||||
setBuiltInSources(builtinRes.data.data || [])
|
||||
setCustomSources(customRes.data.data || [])
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch data:', error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchData()
|
||||
}, [])
|
||||
|
||||
const handleTrigger = async (id: number) => {
|
||||
try {
|
||||
await axios.post(`/api/v1/datasources/${id}/trigger`)
|
||||
message.success('任务已触发')
|
||||
} catch (error: unknown) {
|
||||
const err = error as { response?: { data?: { detail?: string } } }
|
||||
message.error(err.response?.data?.detail || '触发失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleToggle = async (id: number, current: boolean) => {
|
||||
const endpoint = current ? 'disable' : 'enable'
|
||||
try {
|
||||
await axios.post(`/api/v1/datasources/${id}/${endpoint}`)
|
||||
message.success(`${current ? '已禁用' : '已启用'}`)
|
||||
fetchData()
|
||||
} catch (error: unknown) {
|
||||
const err = error as { response?: { data?: { detail?: string } } }
|
||||
message.error(err.response?.data?.detail || '操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleViewSource = async (source: BuiltInDataSource) => {
|
||||
try {
|
||||
const res = await axios.get(`/api/v1/datasources/${source.id}`)
|
||||
const data = res.data
|
||||
setViewingSource({
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
description: null,
|
||||
source_type: data.collector_class,
|
||||
endpoint: '',
|
||||
auth_type: 'none',
|
||||
headers: {},
|
||||
config: {},
|
||||
collector_class: data.collector_class,
|
||||
module: data.module,
|
||||
priority: data.priority,
|
||||
frequency: data.frequency,
|
||||
})
|
||||
setViewDrawerVisible(true)
|
||||
} catch (error) {
|
||||
message.error('获取数据源信息失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleUpdateSource = async () => {
|
||||
if (!viewingSource) return
|
||||
try {
|
||||
await axios.post(`/api/v1/datasources/${viewingSource.id}/trigger`)
|
||||
message.success('已触发更新')
|
||||
setViewDrawerVisible(false)
|
||||
} catch (error: unknown) {
|
||||
const err = error as { response?: { data?: { detail?: string } } }
|
||||
message.error(err.response?.data?.detail || '更新失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleTest = async () => {
|
||||
try {
|
||||
const values = await form.validateFields()
|
||||
setTesting(true)
|
||||
setTestResult(null)
|
||||
const res = await axios.post('/api/v1/datasources/configs/test', values)
|
||||
setTestResult(res.data)
|
||||
if (res.data.success) {
|
||||
message.success('连接测试成功')
|
||||
} else {
|
||||
message.error('连接测试失败')
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
const err = error as { response?: { data?: { detail?: string; message?: string } } }
|
||||
message.error(err.response?.data?.message || err.response?.data?.detail || '测试失败')
|
||||
} finally {
|
||||
setTesting(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
const values = await form.validateFields()
|
||||
if (editingConfig) {
|
||||
await axios.put(`/api/v1/datasources/configs/${editingConfig.id}`, values)
|
||||
message.success('配置已更新')
|
||||
} else {
|
||||
await axios.post('/api/v1/datasources/configs', values)
|
||||
message.success('配置已创建')
|
||||
}
|
||||
setDrawerVisible(false)
|
||||
form.resetFields()
|
||||
setEditingConfig(null)
|
||||
setTestResult(null)
|
||||
fetchData()
|
||||
} catch (error: unknown) {
|
||||
const err = error as { response?: { data?: { detail?: string; message?: string } } }
|
||||
message.error(err.response?.data?.message || err.response?.data?.detail || '保存失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
await axios.delete(`/api/v1/datasources/configs/${id}`)
|
||||
message.success('配置已删除')
|
||||
fetchData()
|
||||
} catch (error: unknown) {
|
||||
const err = error as { response?: { data?: { detail?: string } } }
|
||||
message.error(err.response?.data?.detail || '删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleToggleCustom = async (id: number, current: boolean) => {
|
||||
try {
|
||||
await axios.put(`/api/v1/datasources/configs/${id}`, { is_active: !current })
|
||||
message.success(`${current ? '已禁用' : '已启用'}`)
|
||||
fetchData()
|
||||
} catch (error: unknown) {
|
||||
const err = error as { response?: { data?: { detail?: string } } }
|
||||
message.error(err.response?.data?.detail || '操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
const openDrawer = (config?: CustomDataSource) => {
|
||||
setEditingConfig(config || null)
|
||||
if (config) {
|
||||
form.setFieldsValue({
|
||||
...config,
|
||||
auth_config: {},
|
||||
})
|
||||
} else {
|
||||
form.resetFields()
|
||||
form.setFieldsValue({
|
||||
source_type: 'http',
|
||||
auth_type: 'none',
|
||||
config: { timeout: 30, retry: 3 },
|
||||
headers: {},
|
||||
})
|
||||
}
|
||||
setDrawerVisible(true)
|
||||
setTestResult(null)
|
||||
}
|
||||
|
||||
const builtinColumns = [
|
||||
{ title: 'ID', dataIndex: 'id', key: 'id', width: 60 },
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
render: (name: string, record: BuiltInDataSource) => (
|
||||
<Button type="link" onClick={() => handleViewSource(record)}>
|
||||
{name}
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
{ title: '模块', dataIndex: 'module', key: 'module' },
|
||||
{
|
||||
title: '优先级',
|
||||
dataIndex: 'priority',
|
||||
key: 'priority',
|
||||
render: (p: string) => <Tag color={p === 'P0' ? 'red' : 'orange'}>{p}</Tag>,
|
||||
},
|
||||
{ title: '频率', dataIndex: 'frequency', key: 'frequency' },
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'is_active',
|
||||
key: 'is_active',
|
||||
render: (active: boolean) => (
|
||||
<Tag color={active ? 'green' : 'red'}>{active ? '运行中' : '已暂停'}</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
render: (_: unknown, record: BuiltInDataSource) => (
|
||||
<Space>
|
||||
<Button
|
||||
type="link"
|
||||
icon={<SyncOutlined />}
|
||||
onClick={() => handleTrigger(record.id)}
|
||||
>
|
||||
触发
|
||||
</Button>
|
||||
<Button
|
||||
type="link"
|
||||
icon={record.is_active ? <PauseCircleOutlined /> : <PlayCircleOutlined />}
|
||||
onClick={() => handleToggle(record.id, record.is_active)}
|
||||
>
|
||||
{record.is_active ? '禁用' : '启用'}
|
||||
</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
const customColumns = [
|
||||
{ title: 'ID', dataIndex: 'id', key: 'id', width: 60 },
|
||||
{ title: '名称', dataIndex: 'name', key: 'name' },
|
||||
{ title: '类型', dataIndex: 'source_type', key: 'source_type' },
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'is_active',
|
||||
key: 'is_active',
|
||||
render: (active: boolean) => (
|
||||
<Tag color={active ? 'green' : 'red'}>{active ? '启用' : '禁用'}</Tag>
|
||||
),
|
||||
},
|
||||
{ title: '创建时间', dataIndex: 'created_at', key: 'created_at' },
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
render: (_: unknown, record: CustomDataSource) => (
|
||||
<Space>
|
||||
<Tooltip title="编辑">
|
||||
<Button type="link" icon={<EditOutlined />} onClick={() => openDrawer(record)} />
|
||||
</Tooltip>
|
||||
<Tooltip title={record.is_active ? '禁用' : '启用'}>
|
||||
<Button
|
||||
type="link"
|
||||
icon={record.is_active ? <PauseCircleOutlined /> : <PlayCircleOutlined />}
|
||||
onClick={() => handleToggleCustom(record.id, record.is_active)}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Popconfirm
|
||||
title="确定删除此配置?"
|
||||
onConfirm={() => handleDelete(record.id)}
|
||||
>
|
||||
<Tooltip title="删除">
|
||||
<Button type="link" danger icon={<DeleteOutlined />} />
|
||||
</Tooltip>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
const tabItems = [
|
||||
{
|
||||
key: 'builtin',
|
||||
label: '内置数据源',
|
||||
children: (
|
||||
<Table
|
||||
columns={builtinColumns}
|
||||
dataSource={builtInSources}
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
pagination={false}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'custom',
|
||||
label: (
|
||||
<span>
|
||||
<ApiOutlined /> 自定义数据源
|
||||
</span>
|
||||
),
|
||||
children: (
|
||||
<>
|
||||
<div style={{ marginBottom: 16, textAlign: 'right' }}>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => openDrawer()}>
|
||||
添加数据源
|
||||
</Button>
|
||||
</div>
|
||||
{customSources.length === 0 ? (
|
||||
<Empty description="暂无自定义数据源" />
|
||||
) : (
|
||||
<Table
|
||||
columns={customColumns}
|
||||
dataSource={customSources}
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
pagination={false}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>数据源管理</h2>
|
||||
<Tabs activeKey={activeTab} onChange={setActiveTab} items={tabItems} />
|
||||
|
||||
<Drawer
|
||||
title={editingConfig ? '编辑数据源' : '添加数据源'}
|
||||
width={600}
|
||||
open={drawerVisible}
|
||||
onClose={() => {
|
||||
setDrawerVisible(false)
|
||||
form.resetFields()
|
||||
setEditingConfig(null)
|
||||
setTestResult(null)
|
||||
}}
|
||||
footer={
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<Button
|
||||
icon={<ExperimentOutlined />}
|
||||
loading={testing}
|
||||
onClick={handleTest}
|
||||
>
|
||||
测试连接
|
||||
</Button>
|
||||
<Space>
|
||||
<Button onClick={() => setDrawerVisible(false)}>取消</Button>
|
||||
<Button type="primary" onClick={handleSave}>
|
||||
保存
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="名称"
|
||||
rules={[{ required: true, message: '请输入名称' }]}
|
||||
>
|
||||
<Input placeholder="My API Data Source" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="description" label="描述">
|
||||
<Input.TextArea rows={2} placeholder="数据源描述" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="source_type"
|
||||
label="数据源类型"
|
||||
rules={[{ required: true, message: '请选择类型' }]}
|
||||
>
|
||||
<Select>
|
||||
<Select.Option value="http">HTTP API</Select.Option>
|
||||
<Select.Option value="api">REST API</Select.Option>
|
||||
<Select.Option value="database">数据库</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="endpoint"
|
||||
label="接口地址"
|
||||
rules={[{ required: true, message: '请输入接口地址' }]}
|
||||
>
|
||||
<Input placeholder="https://api.example.com/data" />
|
||||
</Form.Item>
|
||||
|
||||
<Collapse
|
||||
items={[
|
||||
{
|
||||
key: 'auth',
|
||||
label: '认证配置',
|
||||
children: (
|
||||
<>
|
||||
<Form.Item name="auth_type" label="认证方式">
|
||||
<Select>
|
||||
<Select.Option value="none">无</Select.Option>
|
||||
<Select.Option value="bearer">Bearer Token</Select.Option>
|
||||
<Select.Option value="api_key">API Key</Select.Option>
|
||||
<Select.Option value="basic">Basic Auth</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<div>
|
||||
<Form.Item noStyle shouldUpdate={(_, { auth_type }) => auth_type === 'bearer'}>
|
||||
{({ getFieldValue }) => {
|
||||
if (getFieldValue('auth_type') === 'bearer') {
|
||||
return (
|
||||
<Form.Item name={['auth_config', 'token']} label="Token">
|
||||
<Input.Password placeholder="Bearer Token" />
|
||||
</Form.Item>
|
||||
)
|
||||
}
|
||||
return null
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item noStyle shouldUpdate={(_, { auth_type }) => auth_type === 'api_key'}>
|
||||
{({ getFieldValue }) => {
|
||||
if (getFieldValue('auth_type') === 'api_key') {
|
||||
return (
|
||||
<>
|
||||
<Form.Item name={['auth_config', 'key_name']} label="Header名称" initialValue="X-API-Key">
|
||||
<Input placeholder="X-API-Key" />
|
||||
</Form.Item>
|
||||
<Form.Item name={['auth_config', 'api_key']} label="API Key">
|
||||
<Input.Password placeholder="API Key" />
|
||||
</Form.Item>
|
||||
</>
|
||||
)
|
||||
}
|
||||
return null
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item noStyle shouldUpdate={(_, { auth_type }) => auth_type === 'basic'}>
|
||||
{({ getFieldValue }) => {
|
||||
if (getFieldValue('auth_type') === 'basic') {
|
||||
return (
|
||||
<>
|
||||
<Form.Item name={['auth_config', 'username']} label="用户名">
|
||||
<Input placeholder="Username" />
|
||||
</Form.Item>
|
||||
<Form.Item name={['auth_config', 'password']} label="密码">
|
||||
<Input.Password placeholder="Password" />
|
||||
</Form.Item>
|
||||
</>
|
||||
)
|
||||
}
|
||||
return null
|
||||
}}
|
||||
</Form.Item>
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<Collapse
|
||||
items={[
|
||||
{
|
||||
key: 'headers',
|
||||
label: '请求头',
|
||||
children: (
|
||||
<Form.List name="headers">
|
||||
{(fields, { add, remove }) => (
|
||||
<>
|
||||
{fields.map(({ key, name, ...restField }) => (
|
||||
<Space key={key} style={{ display: 'flex', marginBottom: 8 }} align="baseline">
|
||||
<Form.Item {...restField} name={[name, 'key']} rules={[{ required: true, message: 'Header键' }]}>
|
||||
<Input placeholder="Content-Type" />
|
||||
</Form.Item>
|
||||
<Form.Item {...restField} name={[name, 'value']} rules={[{ required: true, message: 'Header值' }]}>
|
||||
<Input placeholder="application/json" />
|
||||
</Form.Item>
|
||||
<Button type="link" danger onClick={() => remove(name)}>删除</Button>
|
||||
</Space>
|
||||
))}
|
||||
<Button type="dashed" onClick={() => add()} block>
|
||||
添加请求头
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<Collapse
|
||||
items={[
|
||||
{
|
||||
key: 'config',
|
||||
label: '高级配置',
|
||||
children: (
|
||||
<>
|
||||
<Form.Item name={['config', 'timeout']} label="超时时间(秒)">
|
||||
<InputNumber min={1} max={300} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<Form.Item name={['config', 'retry']} label="重试次数">
|
||||
<InputNumber min={0} max={10} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
{testResult && (
|
||||
<div style={{
|
||||
marginTop: 16,
|
||||
padding: 16,
|
||||
background: testResult.success ? '#f6ffed' : '#fff2f0',
|
||||
border: `1px solid ${testResult.success ? '#b7eb8f' : '#ffa39e'}`,
|
||||
borderRadius: 4,
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
|
||||
{testResult.success ? (
|
||||
<CheckCircleOutlined style={{ color: '#52c41a', fontSize: 18 }} />
|
||||
) : (
|
||||
<CloseCircleOutlined style={{ color: '#ff4d4f', fontSize: 18 }} />
|
||||
)}
|
||||
<span style={{ fontWeight: 'bold' }}>
|
||||
{testResult.success ? '连接成功' : '连接失败'}
|
||||
</span>
|
||||
</div>
|
||||
{testResult.status_code && <div>状态码: {testResult.status_code}</div>}
|
||||
{testResult.response_time_ms && <div>响应时间: {testResult.response_time_ms.toFixed(0)}ms</div>}
|
||||
{testResult.error && <div style={{ color: '#ff4d4f' }}>错误: {testResult.error}</div>}
|
||||
{testResult.data_preview && (
|
||||
<div style={{ marginTop: 8, fontSize: 12, color: '#666' }}>
|
||||
预览: {testResult.data_preview}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Form>
|
||||
</Drawer>
|
||||
|
||||
<Drawer
|
||||
title="查看数据源"
|
||||
width={600}
|
||||
open={viewDrawerVisible}
|
||||
onClose={() => {
|
||||
setViewDrawerVisible(false)
|
||||
setViewingSource(null)
|
||||
}}
|
||||
footer={
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<Button
|
||||
icon={<ExperimentOutlined />}
|
||||
loading={testing}
|
||||
onClick={handleTest}
|
||||
>
|
||||
测试连接
|
||||
</Button>
|
||||
<Space>
|
||||
<Button onClick={() => setViewDrawerVisible(false)}>关闭</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SyncOutlined />}
|
||||
onClick={handleUpdateSource}
|
||||
>
|
||||
更新
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{viewingSource && (
|
||||
<Form layout="vertical">
|
||||
<Form.Item label="名称">
|
||||
<Input value={viewingSource.name} disabled />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="采集器">
|
||||
<Input value={viewingSource.collector_class} disabled />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="模块">
|
||||
<Input value={viewingSource.module} disabled />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="优先级">
|
||||
<Input value={viewingSource.priority} disabled />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="频率">
|
||||
<Input value={viewingSource.frequency} disabled />
|
||||
</Form.Item>
|
||||
|
||||
<Collapse
|
||||
items={[
|
||||
{
|
||||
key: 'auth',
|
||||
label: '认证配置',
|
||||
children: (
|
||||
<Form.Item label="认证方式">
|
||||
<Input value={viewingSource.auth_type || 'none'} disabled />
|
||||
</Form.Item>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'headers',
|
||||
label: '请求头',
|
||||
children: viewingSource.headers && Object.keys(viewingSource.headers).length > 0 ? (
|
||||
<pre style={{ background: '#f5f5f5', padding: 12, borderRadius: 4, overflow: 'auto' }}>
|
||||
{JSON.stringify(viewingSource.headers, null, 2)}
|
||||
</pre>
|
||||
) : (
|
||||
<div style={{ color: '#999' }}>无</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'config',
|
||||
label: '高级配置',
|
||||
children: viewingSource.config && Object.keys(viewingSource.config).length > 0 ? (
|
||||
<pre style={{ background: '#f5f5f5', padding: 12, borderRadius: 4, overflow: 'auto' }}>
|
||||
{JSON.stringify(viewingSource.config, null, 2)}
|
||||
</pre>
|
||||
) : (
|
||||
<div style={{ color: '#999' }}>无</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Form>
|
||||
)}
|
||||
</Drawer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DataSources
|
||||
Reference in New Issue
Block a user