release: bump version to 0.33.0
This commit is contained in:
@@ -19,6 +19,7 @@ import { useWebSocket } from '../../hooks/useWebSocket'
|
||||
|
||||
interface BuiltInDataSource {
|
||||
id: number
|
||||
source: string
|
||||
name: string
|
||||
module: string
|
||||
priority: string
|
||||
@@ -180,6 +181,19 @@ interface CustomDataSource {
|
||||
updated_at: string | null
|
||||
}
|
||||
|
||||
interface EditableDataSourceConfig {
|
||||
id: number
|
||||
name: string
|
||||
description: string | null
|
||||
source_type: string
|
||||
endpoint: string
|
||||
auth_type: string
|
||||
auth_config: Record<string, any>
|
||||
headers: Record<string, string>
|
||||
config: Record<string, any>
|
||||
is_active?: boolean
|
||||
}
|
||||
|
||||
interface ViewDataSource {
|
||||
id: number
|
||||
name: string
|
||||
@@ -205,6 +219,7 @@ function DataSources() {
|
||||
const [drawerVisible, setDrawerVisible] = useState(false)
|
||||
const [viewDrawerVisible, setViewDrawerVisible] = useState(false)
|
||||
const [editingConfig, setEditingConfig] = useState<CustomDataSource | null>(null)
|
||||
const [builtinEditingSource, setBuiltinEditingSource] = useState<BuiltInDataSource | null>(null)
|
||||
const [viewingSource, setViewingSource] = useState<ViewDataSource | null>(null)
|
||||
const [recordCount, setRecordCount] = useState<number>(0)
|
||||
const [testing, setTesting] = useState(false)
|
||||
@@ -219,6 +234,81 @@ function DataSources() {
|
||||
const [customActionsCollapsed, customContainerRef] = useCollapsedActions()
|
||||
const [form] = Form.useForm()
|
||||
|
||||
const headersMapToList = useCallback((headers?: Record<string, string> | null) => {
|
||||
return Object.entries(headers || {})
|
||||
.filter(([key, value]) => key && value !== undefined && value !== null && String(value).trim() !== '')
|
||||
.map(([key, value]) => ({ key, value }))
|
||||
}, [])
|
||||
|
||||
const headersListToMap = useCallback((headers?: Array<{ key?: string; value?: string }> | Record<string, string>) => {
|
||||
if (!headers) return {}
|
||||
if (!Array.isArray(headers)) return headers
|
||||
|
||||
return headers.reduce<Record<string, string>>((acc, item) => {
|
||||
const key = item?.key?.trim()
|
||||
const value = item?.value?.trim()
|
||||
if (!key || value === undefined) return acc
|
||||
acc[key] = value
|
||||
return acc
|
||||
}, {})
|
||||
}, [])
|
||||
|
||||
const applyConfigToForm = useCallback((config?: Partial<EditableDataSourceConfig> | null) => {
|
||||
form.setFieldsValue({
|
||||
name: config?.name || '',
|
||||
description: config?.description || '',
|
||||
source_type: config?.source_type || 'http',
|
||||
endpoint: config?.endpoint || '',
|
||||
auth_type: config?.auth_type || 'none',
|
||||
auth_config: config?.auth_config || {},
|
||||
headers: headersMapToList(config?.headers || {}),
|
||||
config: config?.config || { timeout: 30, retry: 3 },
|
||||
})
|
||||
}, [form, headersMapToList])
|
||||
|
||||
const loadConfigDetail = useCallback(async (configId: number) => {
|
||||
const res = await axios.get<EditableDataSourceConfig>(`/api/v1/datasources/configs/${configId}`)
|
||||
return res.data
|
||||
}, [])
|
||||
|
||||
const createDefaultConfigDraft = useCallback((overrides?: Partial<EditableDataSourceConfig>) => ({
|
||||
source_type: 'http',
|
||||
auth_type: 'none',
|
||||
headers: {},
|
||||
config: { timeout: 30, retry: 3 },
|
||||
...overrides,
|
||||
}), [])
|
||||
|
||||
const getBuiltinOverrideDescription = useCallback(
|
||||
(source?: Pick<BuiltInDataSource, 'name'> | null) =>
|
||||
source ? `Built-in datasource override for ${source.name}` : undefined,
|
||||
[],
|
||||
)
|
||||
|
||||
const createFormPayload = useCallback((values: any) => ({
|
||||
...values,
|
||||
name: builtinEditingSource ? builtinEditingSource.source : values.name,
|
||||
description:
|
||||
values.description ||
|
||||
getBuiltinOverrideDescription(builtinEditingSource),
|
||||
source_type: builtinEditingSource ? 'http' : values.source_type,
|
||||
headers: headersListToMap(values.headers),
|
||||
}), [builtinEditingSource, getBuiltinOverrideDescription, headersListToMap])
|
||||
|
||||
const closeDrawerAfterLoadError = useCallback((
|
||||
errorMessage: string,
|
||||
options?: { clearBuiltin?: boolean; clearEditingConfig?: boolean },
|
||||
) => {
|
||||
messageApi.error(errorMessage)
|
||||
setDrawerVisible(false)
|
||||
if (options?.clearBuiltin) {
|
||||
setBuiltinEditingSource(null)
|
||||
}
|
||||
if (options?.clearEditingConfig) {
|
||||
setEditingConfig(null)
|
||||
}
|
||||
}, [messageApi])
|
||||
|
||||
const fetchData = useCallback(async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
@@ -711,9 +801,11 @@ function DataSources() {
|
||||
|
||||
const handleViewSource = async (source: BuiltInDataSource) => {
|
||||
try {
|
||||
const [res, statsRes] = await Promise.all([
|
||||
const existingOverride = customSources.find((item) => item.name === source.source)
|
||||
const [res, statsRes, overrideDetail] = await Promise.all([
|
||||
axios.get(`/api/v1/datasources/${source.id}`),
|
||||
axios.get(`/api/v1/datasources/${source.id}/stats`)
|
||||
axios.get(`/api/v1/datasources/${source.id}/stats`),
|
||||
existingOverride ? loadConfigDetail(existingOverride.id) : Promise.resolve(null),
|
||||
])
|
||||
const data = res.data
|
||||
setViewingSource({
|
||||
@@ -721,10 +813,10 @@ function DataSources() {
|
||||
name: data.name,
|
||||
description: null,
|
||||
source_type: data.collector_class,
|
||||
endpoint: data.endpoint || '',
|
||||
auth_type: 'none',
|
||||
headers: {},
|
||||
config: {},
|
||||
endpoint: overrideDetail?.endpoint || data.endpoint || '',
|
||||
auth_type: overrideDetail?.auth_type || 'none',
|
||||
headers: overrideDetail?.headers || {},
|
||||
config: overrideDetail?.config || {},
|
||||
collector_class: data.collector_class,
|
||||
module: data.module,
|
||||
priority: data.priority,
|
||||
@@ -753,7 +845,8 @@ function DataSources() {
|
||||
const values = await form.validateFields()
|
||||
setTesting(true)
|
||||
setTestResult(null)
|
||||
const res = await axios.post('/api/v1/datasources/configs/test', values)
|
||||
const payload = createFormPayload(values)
|
||||
const res = await axios.post('/api/v1/datasources/configs/test', payload)
|
||||
setTestResult(res.data)
|
||||
if (res.data.success) {
|
||||
messageApi.success('连接测试成功')
|
||||
@@ -771,16 +864,18 @@ function DataSources() {
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
const values = await form.validateFields()
|
||||
const payload = createFormPayload(values)
|
||||
if (editingConfig) {
|
||||
await axios.put(`/api/v1/datasources/configs/${editingConfig.id}`, values)
|
||||
await axios.put(`/api/v1/datasources/configs/${editingConfig.id}`, payload)
|
||||
messageApi.success('配置已更新')
|
||||
} else {
|
||||
await axios.post('/api/v1/datasources/configs', values)
|
||||
await axios.post('/api/v1/datasources/configs', payload)
|
||||
messageApi.success('配置已创建')
|
||||
}
|
||||
setDrawerVisible(false)
|
||||
form.resetFields()
|
||||
setEditingConfig(null)
|
||||
setBuiltinEditingSource(null)
|
||||
setTestResult(null)
|
||||
fetchData()
|
||||
} catch (error: unknown) {
|
||||
@@ -800,6 +895,23 @@ function DataSources() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleResetBuiltinOverride = async () => {
|
||||
if (!builtinEditingSource || !editingConfig) return
|
||||
try {
|
||||
await axios.delete(`/api/v1/datasources/configs/${editingConfig.id}`)
|
||||
messageApi.success(`已恢复 ${builtinEditingSource.name} 的默认配置`)
|
||||
setDrawerVisible(false)
|
||||
form.resetFields()
|
||||
setEditingConfig(null)
|
||||
setBuiltinEditingSource(null)
|
||||
setTestResult(null)
|
||||
fetchData()
|
||||
} catch (error: unknown) {
|
||||
const err = error as { response?: { data?: { detail?: string } } }
|
||||
messageApi.error(err.response?.data?.detail || '恢复默认失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleToggleCustom = async (id: number, current: boolean) => {
|
||||
try {
|
||||
await axios.put(`/api/v1/datasources/configs/${id}`, { is_active: !current })
|
||||
@@ -811,24 +923,53 @@ function DataSources() {
|
||||
}
|
||||
}
|
||||
|
||||
const openDrawer = (config?: CustomDataSource) => {
|
||||
const openDrawer = async (config?: CustomDataSource) => {
|
||||
setBuiltinEditingSource(null)
|
||||
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)
|
||||
setDrawerVisible(true)
|
||||
|
||||
if (config) {
|
||||
try {
|
||||
const detail = await loadConfigDetail(config.id)
|
||||
applyConfigToForm(detail)
|
||||
} catch {
|
||||
closeDrawerAfterLoadError('获取配置详情失败', { clearEditingConfig: true })
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
form.resetFields()
|
||||
applyConfigToForm(createDefaultConfigDraft())
|
||||
}
|
||||
|
||||
const openBuiltinConfigDrawer = async (source: BuiltInDataSource) => {
|
||||
setBuiltinEditingSource(source)
|
||||
setTestResult(null)
|
||||
setDrawerVisible(true)
|
||||
|
||||
const existingOverride = customSources.find((item) => item.name === source.source)
|
||||
setEditingConfig(existingOverride || null)
|
||||
|
||||
if (existingOverride) {
|
||||
try {
|
||||
const detail = await loadConfigDetail(existingOverride.id)
|
||||
applyConfigToForm(detail)
|
||||
} catch {
|
||||
closeDrawerAfterLoadError('获取内置数据源配置失败', {
|
||||
clearBuiltin: true,
|
||||
clearEditingConfig: true,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
form.resetFields()
|
||||
applyConfigToForm(createDefaultConfigDraft({
|
||||
name: source.source,
|
||||
description: getBuiltinOverrideDescription(source),
|
||||
endpoint: source.endpoint || '',
|
||||
}))
|
||||
}
|
||||
|
||||
const handleCopyLink = async (value: string, successText: string) => {
|
||||
@@ -945,12 +1086,18 @@ function DataSources() {
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right' as const,
|
||||
width: builtinActionsCollapsed ? 40 : 164,
|
||||
width: builtinActionsCollapsed ? 40 : 228,
|
||||
onCell: () => actionCellProps,
|
||||
render: (_: unknown, record: BuiltInDataSource) => (
|
||||
<TableActions
|
||||
collapsed={builtinActionsCollapsed}
|
||||
items={[
|
||||
{
|
||||
key: 'edit',
|
||||
label: '编辑',
|
||||
icon: <EditOutlined />,
|
||||
onClick: () => { void openBuiltinConfigDrawer(record) },
|
||||
},
|
||||
{
|
||||
key: 'trigger',
|
||||
label: '触发',
|
||||
@@ -967,6 +1114,14 @@ function DataSources() {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => { void openBuiltinConfigDrawer(record) }}
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
@@ -1035,7 +1190,7 @@ function DataSources() {
|
||||
key: 'edit',
|
||||
label: '编辑',
|
||||
icon: <EditOutlined />,
|
||||
onClick: () => openDrawer(record),
|
||||
onClick: () => { void openDrawer(record) },
|
||||
},
|
||||
{
|
||||
key: 'toggle',
|
||||
@@ -1059,7 +1214,7 @@ function DataSources() {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Button type="link" size="small" icon={<EditOutlined />} onClick={() => openDrawer(record)}>编辑</Button>
|
||||
<Button type="link" size="small" icon={<EditOutlined />} onClick={() => { void openDrawer(record) }}>编辑</Button>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
@@ -1171,7 +1326,7 @@ function DataSources() {
|
||||
children: (
|
||||
<div className="page-shell__body data-source-custom-tab" ref={customContainerRef}>
|
||||
<div className="data-source-custom-toolbar">
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => openDrawer()}>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => { void openDrawer() }}>
|
||||
添加数据源
|
||||
</Button>
|
||||
</div>
|
||||
@@ -1215,24 +1370,40 @@ function DataSources() {
|
||||
</div>
|
||||
|
||||
<Drawer
|
||||
title={editingConfig ? '编辑数据源' : '添加数据源'}
|
||||
title={builtinEditingSource ? `编辑内置数据源配置 · ${builtinEditingSource.name}` : editingConfig ? '编辑数据源' : '添加数据源'}
|
||||
width={600}
|
||||
open={drawerVisible}
|
||||
onClose={() => {
|
||||
setDrawerVisible(false)
|
||||
form.resetFields()
|
||||
setEditingConfig(null)
|
||||
setBuiltinEditingSource(null)
|
||||
setTestResult(null)
|
||||
}}
|
||||
footer={
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<Button
|
||||
icon={<ExperimentOutlined />}
|
||||
loading={testing}
|
||||
onClick={handleTest}
|
||||
>
|
||||
测试连接
|
||||
</Button>
|
||||
<Space>
|
||||
{builtinEditingSource && editingConfig ? (
|
||||
<Popconfirm
|
||||
title="恢复内置默认配置?"
|
||||
description="这会删除当前 override,并重新使用代码内置默认配置。"
|
||||
okText="恢复默认"
|
||||
cancelText="取消"
|
||||
onConfirm={handleResetBuiltinOverride}
|
||||
>
|
||||
<Button danger icon={<ClearOutlined />}>
|
||||
恢复默认
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
) : null}
|
||||
<Button
|
||||
icon={<ExperimentOutlined />}
|
||||
loading={testing}
|
||||
onClick={handleTest}
|
||||
>
|
||||
测试连接
|
||||
</Button>
|
||||
</Space>
|
||||
<Space>
|
||||
<Button onClick={() => setDrawerVisible(false)}>取消</Button>
|
||||
<Button type="primary" onClick={handleSave}>
|
||||
@@ -1243,29 +1414,46 @@ function DataSources() {
|
||||
}
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="名称"
|
||||
rules={[{ required: true, message: '请输入名称' }]}
|
||||
>
|
||||
<Input placeholder="My API Data Source" />
|
||||
</Form.Item>
|
||||
{builtinEditingSource ? (
|
||||
<Card size="small" bordered={false} style={{ marginBottom: 16, background: '#fafafa' }}>
|
||||
<Row gutter={[12, 12]}>
|
||||
<Col span={12}>
|
||||
<div style={{ marginBottom: 4, color: '#8c8c8c', fontSize: 12 }}>内置数据源</div>
|
||||
<Input value={builtinEditingSource.name} disabled />
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div style={{ marginBottom: 4, color: '#8c8c8c', fontSize: 12 }}>Collector Key</div>
|
||||
<Input value={builtinEditingSource.source} disabled />
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
) : (
|
||||
<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>
|
||||
{builtinEditingSource ? null : (
|
||||
<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"
|
||||
@@ -1276,6 +1464,7 @@ function DataSources() {
|
||||
</Form.Item>
|
||||
|
||||
<Collapse
|
||||
className="data-source-drawer-collapse"
|
||||
items={[
|
||||
{
|
||||
key: 'auth',
|
||||
@@ -1311,6 +1500,12 @@ function DataSources() {
|
||||
<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', 'in']} label="传递位置" initialValue="header">
|
||||
<Select>
|
||||
<Select.Option value="header">Header</Select.Option>
|
||||
<Select.Option value="query">Query Param</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item name={['auth_config', 'api_key']} label="API Key">
|
||||
<Input.Password placeholder="API Key" />
|
||||
</Form.Item>
|
||||
@@ -1345,6 +1540,7 @@ function DataSources() {
|
||||
/>
|
||||
|
||||
<Collapse
|
||||
className="data-source-drawer-collapse"
|
||||
items={[
|
||||
{
|
||||
key: 'headers',
|
||||
@@ -1376,6 +1572,7 @@ function DataSources() {
|
||||
/>
|
||||
|
||||
<Collapse
|
||||
className="data-source-drawer-collapse"
|
||||
items={[
|
||||
{
|
||||
key: 'config',
|
||||
|
||||
Reference in New Issue
Block a user