release: bump version to 0.33.0

This commit is contained in:
rayd1o
2026-04-22 05:28:54 +08:00
parent 3ae4acdff8
commit 0082cf3fbd
18 changed files with 1069 additions and 91 deletions

View File

@@ -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',