@zhin.js/client 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +117 -3
- package/app/src/components/PluginConfigForm/BasicFieldRenderers.tsx +253 -0
- package/app/src/components/PluginConfigForm/CollectionFieldRenderers.tsx +261 -0
- package/app/src/components/PluginConfigForm/CompositeFieldRenderers.tsx +105 -0
- package/app/src/components/PluginConfigForm/FieldRenderer.tsx +110 -0
- package/app/src/components/PluginConfigForm/NestedFieldRenderer.tsx +95 -0
- package/app/src/components/PluginConfigForm/index.tsx +237 -0
- package/app/src/components/PluginConfigForm/types.ts +46 -0
- package/app/src/main.tsx +1 -1
- package/app/src/pages/dashboard-plugin-detail.tsx +22 -22
- package/dist/index.js +0 -1
- package/package.json +6 -4
- package/src/index.ts +0 -1
- package/src/store/index.ts +22 -1
- package/src/store/reducers/config.ts +135 -0
- package/src/store/reducers/index.ts +4 -1
- package/src/websocket/hooks.ts +280 -0
- package/src/websocket/index.ts +45 -190
- package/src/websocket/instance.ts +46 -0
- package/src/websocket/manager.ts +412 -0
- package/src/websocket/messageHandler.ts +166 -0
- package/src/websocket/types.ts +208 -0
- package/src/websocket/useWebSocket.ts +0 -42
|
@@ -2,8 +2,9 @@ import { useEffect, useState } from 'react'
|
|
|
2
2
|
import { useParams, useNavigate } from 'react-router'
|
|
3
3
|
import * as Themes from '@radix-ui/themes'
|
|
4
4
|
import { Icons } from '@zhin.js/client'
|
|
5
|
+
import {PluginConfigForm} from '../components/PluginConfigForm'
|
|
5
6
|
|
|
6
|
-
const { Flex, Box, Spinner, Text, Callout, Heading, Badge, Grid, Card, Button, Code, Separator, ScrollArea } = Themes
|
|
7
|
+
const { Flex, Box, Spinner, Text, Callout, Heading, Badge, Grid, Card, Button, Code, Separator, ScrollArea, Dialog } = Themes
|
|
7
8
|
|
|
8
9
|
interface PluginDetail {
|
|
9
10
|
name: string
|
|
@@ -12,10 +13,6 @@ interface PluginDetail {
|
|
|
12
13
|
description: string
|
|
13
14
|
commands: Array<{
|
|
14
15
|
name: string
|
|
15
|
-
pattern: string
|
|
16
|
-
description: string
|
|
17
|
-
alias: string[]
|
|
18
|
-
examples: string[]
|
|
19
16
|
}>
|
|
20
17
|
components: Array<{
|
|
21
18
|
name: string
|
|
@@ -35,7 +32,7 @@ interface PluginDetail {
|
|
|
35
32
|
pattern: string
|
|
36
33
|
running: boolean
|
|
37
34
|
}>
|
|
38
|
-
|
|
35
|
+
definitions: Array<{
|
|
39
36
|
name: string
|
|
40
37
|
fields: string[]
|
|
41
38
|
}>
|
|
@@ -45,7 +42,7 @@ interface PluginDetail {
|
|
|
45
42
|
middlewareCount: number
|
|
46
43
|
contextCount: number
|
|
47
44
|
cronCount: number
|
|
48
|
-
|
|
45
|
+
definitionCount: number
|
|
49
46
|
}
|
|
50
47
|
}
|
|
51
48
|
|
|
@@ -81,6 +78,8 @@ export default function DashboardPluginDetail() {
|
|
|
81
78
|
}
|
|
82
79
|
}
|
|
83
80
|
|
|
81
|
+
|
|
82
|
+
|
|
84
83
|
if (loading) {
|
|
85
84
|
return (
|
|
86
85
|
<Flex align="center" justify="center" className="h-full">
|
|
@@ -115,7 +114,7 @@ export default function DashboardPluginDetail() {
|
|
|
115
114
|
<Box>
|
|
116
115
|
{/* 头部 */}
|
|
117
116
|
<Flex direction="column" gap="3" mb="4">
|
|
118
|
-
<Button variant="ghost" onClick={() => navigate('/plugins')} size="2"
|
|
117
|
+
<Button variant="ghost" onClick={() => navigate('/plugins')} size="2">
|
|
119
118
|
<Icons.ArrowLeft className="w-4 h-4" />
|
|
120
119
|
返回
|
|
121
120
|
</Button>
|
|
@@ -136,7 +135,15 @@ export default function DashboardPluginDetail() {
|
|
|
136
135
|
</Flex>
|
|
137
136
|
</Flex>
|
|
138
137
|
|
|
139
|
-
|
|
138
|
+
{/* 插件配置折叠面板 */}
|
|
139
|
+
<PluginConfigForm
|
|
140
|
+
pluginName={plugin.name}
|
|
141
|
+
onSuccess={() => {
|
|
142
|
+
// 配置更新会通过 WebSocket 自动同步
|
|
143
|
+
}}
|
|
144
|
+
/>
|
|
145
|
+
|
|
146
|
+
<Separator size="4" my="4" />
|
|
140
147
|
|
|
141
148
|
{/* 统计概览 - 紧凑卡片 */}
|
|
142
149
|
<Grid columns={{ initial: '2', sm: '3', md: '6' }} gap="2" mb="4">
|
|
@@ -183,7 +190,7 @@ export default function DashboardPluginDetail() {
|
|
|
183
190
|
<Card size="1">
|
|
184
191
|
<Flex direction="column" align="center" gap="1" p="2">
|
|
185
192
|
<Icons.FileText className="w-4 h-4 text-cyan-600 dark:text-cyan-400" />
|
|
186
|
-
<Text size="4" weight="bold">{plugin.statistics.
|
|
193
|
+
<Text size="4" weight="bold">{plugin.statistics.definitionCount}</Text>
|
|
187
194
|
<Text size="1" color="gray">数据模型</Text>
|
|
188
195
|
</Flex>
|
|
189
196
|
</Card>
|
|
@@ -207,14 +214,7 @@ export default function DashboardPluginDetail() {
|
|
|
207
214
|
<Flex direction="column" gap="1">
|
|
208
215
|
<Flex align="center" gap="2">
|
|
209
216
|
<Code size="2">{cmd.name}</Code>
|
|
210
|
-
{cmd.alias.length > 0 && (
|
|
211
|
-
<Text size="1" color="gray">别名: {cmd.alias.join(', ')}</Text>
|
|
212
|
-
)}
|
|
213
217
|
</Flex>
|
|
214
|
-
<Text size="1" color="gray">{cmd.description}</Text>
|
|
215
|
-
{cmd.examples.length > 0 && (
|
|
216
|
-
<Text size="1" color="gray" className="italic">示例: {cmd.examples[0]}</Text>
|
|
217
|
-
)}
|
|
218
218
|
</Flex>
|
|
219
219
|
</Box>
|
|
220
220
|
))}
|
|
@@ -330,22 +330,22 @@ export default function DashboardPluginDetail() {
|
|
|
330
330
|
)}
|
|
331
331
|
|
|
332
332
|
{/* 数据模型列表 */}
|
|
333
|
-
{plugin.
|
|
333
|
+
{plugin.definitions.length > 0 && (
|
|
334
334
|
<Card size="2">
|
|
335
335
|
<Flex direction="column" gap="2" p="3">
|
|
336
336
|
<Flex align="center" gap="2">
|
|
337
337
|
<Icons.FileText className="w-4 h-4 text-cyan-600 dark:text-cyan-400" />
|
|
338
338
|
<Heading size="3">数据模型</Heading>
|
|
339
|
-
<Badge size="1">{plugin.
|
|
339
|
+
<Badge size="1">{plugin.definitions.length}</Badge>
|
|
340
340
|
</Flex>
|
|
341
341
|
<Separator size="4" />
|
|
342
342
|
<Flex direction="column" gap="2" className="max-h-60 overflow-y-auto">
|
|
343
|
-
{plugin.
|
|
343
|
+
{plugin.definitions.map((definition, index) => (
|
|
344
344
|
<Box key={index} className="rounded-lg bg-gray-50 dark:bg-gray-900 p-2">
|
|
345
345
|
<Flex direction="column" gap="1">
|
|
346
|
-
<Code size="2">{
|
|
346
|
+
<Code size="2">{definition.name}</Code>
|
|
347
347
|
<Text size="1" color="gray">
|
|
348
|
-
字段: {
|
|
348
|
+
字段: {definition.fields.join(', ')}
|
|
349
349
|
</Text>
|
|
350
350
|
</Flex>
|
|
351
351
|
</Box>
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/client",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Zhin 客户端",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -29,13 +29,15 @@
|
|
|
29
29
|
"app"
|
|
30
30
|
],
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"radix-ui": "^1.4.3",
|
|
33
32
|
"@radix-ui/themes": "^3.2.1",
|
|
34
|
-
"lucide-react": "^0.469.0",
|
|
35
33
|
"@types/events": "^3.0.3",
|
|
36
34
|
"@types/node": "^24.7.1",
|
|
37
35
|
"@types/react": "^19.2.2",
|
|
38
|
-
"@types/react-dom": "^19.2.1"
|
|
36
|
+
"@types/react-dom": "^19.2.1",
|
|
37
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
38
|
+
"lucide-react": "^0.469.0",
|
|
39
|
+
"radix-ui": "^1.4.3",
|
|
40
|
+
"vite": "^7.0.6"
|
|
39
41
|
},
|
|
40
42
|
"scripts": {
|
|
41
43
|
"build": "tsc",
|
package/src/index.ts
CHANGED
package/src/store/index.ts
CHANGED
|
@@ -108,4 +108,25 @@ export {
|
|
|
108
108
|
unloadScript
|
|
109
109
|
} from './reducers/script'
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
// 导出 Config actions 和 selectors
|
|
112
|
+
export {
|
|
113
|
+
setConnected,
|
|
114
|
+
setLoading,
|
|
115
|
+
setError,
|
|
116
|
+
updateConfig,
|
|
117
|
+
updateSchema,
|
|
118
|
+
updateConfigs,
|
|
119
|
+
updateSchemas,
|
|
120
|
+
removeConfig,
|
|
121
|
+
clearConfigs,
|
|
122
|
+
selectConfig,
|
|
123
|
+
selectSchema,
|
|
124
|
+
selectConfigLoading,
|
|
125
|
+
selectConfigError,
|
|
126
|
+
selectConfigConnected,
|
|
127
|
+
selectAllConfigs,
|
|
128
|
+
selectAllSchemas
|
|
129
|
+
} from './reducers/config'
|
|
130
|
+
|
|
131
|
+
export type { RouteMenuItem } from './reducers/route'
|
|
132
|
+
export type { ConfigMessage } from './reducers/config'
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 配置管理 Redux Slice
|
|
3
|
+
* 通过 WebSocket 管理插件配置
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'
|
|
7
|
+
|
|
8
|
+
export interface ConfigState {
|
|
9
|
+
// 所有插件的配置,按插件名分发
|
|
10
|
+
configs: Record<string, any>
|
|
11
|
+
// 所有插件的 Schema,按插件名分发
|
|
12
|
+
schemas: Record<string, any>
|
|
13
|
+
// 加载状态
|
|
14
|
+
loading: Record<string, boolean>
|
|
15
|
+
// 错误信息
|
|
16
|
+
errors: Record<string, string | null>
|
|
17
|
+
// WebSocket 连接状态
|
|
18
|
+
connected: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const initialState: ConfigState = {
|
|
22
|
+
configs: {},
|
|
23
|
+
schemas: {},
|
|
24
|
+
loading: {},
|
|
25
|
+
errors: {},
|
|
26
|
+
connected: false
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// WebSocket 消息类型
|
|
30
|
+
export interface ConfigMessage {
|
|
31
|
+
type: 'config:get' | 'config:set' | 'config:updated' | 'schema:get' | 'schema:updated'
|
|
32
|
+
pluginName: string
|
|
33
|
+
data?: any
|
|
34
|
+
error?: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const configSlice = createSlice({
|
|
38
|
+
name: 'config',
|
|
39
|
+
initialState,
|
|
40
|
+
reducers: {
|
|
41
|
+
// WebSocket 连接状态
|
|
42
|
+
setConnected: (state, action: PayloadAction<boolean>) => {
|
|
43
|
+
state.connected = action.payload
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
// 设置配置加载状态
|
|
47
|
+
setLoading: (state, action: PayloadAction<{ pluginName: string; loading: boolean }>) => {
|
|
48
|
+
const { pluginName, loading } = action.payload
|
|
49
|
+
state.loading[pluginName] = loading
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
// 设置错误信息
|
|
53
|
+
setError: (state, action: PayloadAction<{ pluginName: string; error: string | null }>) => {
|
|
54
|
+
const { pluginName, error } = action.payload
|
|
55
|
+
state.errors[pluginName] = error
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
// 更新插件配置
|
|
59
|
+
updateConfig: (state, action: PayloadAction<{ pluginName: string; config: any }>) => {
|
|
60
|
+
const { pluginName, config } = action.payload
|
|
61
|
+
state.configs[pluginName] = config
|
|
62
|
+
state.loading[pluginName] = false
|
|
63
|
+
state.errors[pluginName] = null
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
// 更新插件 Schema
|
|
67
|
+
updateSchema: (state, action: PayloadAction<{ pluginName: string; schema: any }>) => {
|
|
68
|
+
const { pluginName, schema } = action.payload
|
|
69
|
+
state.schemas[pluginName] = schema
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
// 批量更新配置
|
|
73
|
+
updateConfigs: (state, action: PayloadAction<Record<string, any>>) => {
|
|
74
|
+
state.configs = { ...state.configs, ...action.payload }
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
// 批量更新 Schema
|
|
78
|
+
updateSchemas: (state, action: PayloadAction<Record<string, any>>) => {
|
|
79
|
+
state.schemas = { ...state.schemas, ...action.payload }
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
// 清除插件配置
|
|
83
|
+
removeConfig: (state, action: PayloadAction<string>) => {
|
|
84
|
+
const pluginName = action.payload
|
|
85
|
+
delete state.configs[pluginName]
|
|
86
|
+
delete state.schemas[pluginName]
|
|
87
|
+
delete state.loading[pluginName]
|
|
88
|
+
delete state.errors[pluginName]
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
// 清除所有配置
|
|
92
|
+
clearConfigs: (state) => {
|
|
93
|
+
state.configs = {}
|
|
94
|
+
state.schemas = {}
|
|
95
|
+
state.loading = {}
|
|
96
|
+
state.errors = {}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
export const {
|
|
102
|
+
setConnected,
|
|
103
|
+
setLoading,
|
|
104
|
+
setError,
|
|
105
|
+
updateConfig,
|
|
106
|
+
updateSchema,
|
|
107
|
+
updateConfigs,
|
|
108
|
+
updateSchemas,
|
|
109
|
+
removeConfig,
|
|
110
|
+
clearConfigs
|
|
111
|
+
} = configSlice.actions
|
|
112
|
+
|
|
113
|
+
export default configSlice.reducer
|
|
114
|
+
|
|
115
|
+
// Selectors
|
|
116
|
+
export const selectConfig = (state: { config: ConfigState }, pluginName: string) =>
|
|
117
|
+
state.config.configs[pluginName]
|
|
118
|
+
|
|
119
|
+
export const selectSchema = (state: { config: ConfigState }, pluginName: string) =>
|
|
120
|
+
state.config.schemas[pluginName]
|
|
121
|
+
|
|
122
|
+
export const selectConfigLoading = (state: { config: ConfigState }, pluginName: string) =>
|
|
123
|
+
state.config.loading[pluginName] || false
|
|
124
|
+
|
|
125
|
+
export const selectConfigError = (state: { config: ConfigState }, pluginName: string) =>
|
|
126
|
+
state.config.errors[pluginName] || null
|
|
127
|
+
|
|
128
|
+
export const selectConfigConnected = (state: { config: ConfigState }) =>
|
|
129
|
+
state.config.connected
|
|
130
|
+
|
|
131
|
+
export const selectAllConfigs = (state: { config: ConfigState }) =>
|
|
132
|
+
state.config.configs
|
|
133
|
+
|
|
134
|
+
export const selectAllSchemas = (state: { config: ConfigState }) =>
|
|
135
|
+
state.config.schemas
|
|
@@ -2,15 +2,18 @@ import { Reducer } from "@reduxjs/toolkit"
|
|
|
2
2
|
import ui from "./ui"
|
|
3
3
|
import route from "./route"
|
|
4
4
|
import script from "./script"
|
|
5
|
+
import config from "./config"
|
|
5
6
|
|
|
6
7
|
export interface Reducers {
|
|
7
8
|
ui: ReturnType<typeof ui>
|
|
8
9
|
route: ReturnType<typeof route>
|
|
9
10
|
script: ReturnType<typeof script>
|
|
11
|
+
config: ReturnType<typeof config>
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
export const reducers: Record<keyof Reducers, Reducer<any, any>> = {
|
|
13
15
|
ui,
|
|
14
16
|
route,
|
|
15
|
-
script
|
|
17
|
+
script,
|
|
18
|
+
config
|
|
16
19
|
}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket React Hooks
|
|
3
|
+
* 提供在 React 组件中使用 WebSocket 功能的便捷接口
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useCallback, useEffect, useMemo, useState } from 'react'
|
|
7
|
+
import {
|
|
8
|
+
useSelector,
|
|
9
|
+
useDispatch,
|
|
10
|
+
selectConfig,
|
|
11
|
+
selectSchema,
|
|
12
|
+
selectConfigLoading,
|
|
13
|
+
selectConfigError,
|
|
14
|
+
selectConfigConnected,
|
|
15
|
+
selectAllConfigs,
|
|
16
|
+
selectAllSchemas,
|
|
17
|
+
setLoading,
|
|
18
|
+
setError
|
|
19
|
+
} from '../store'
|
|
20
|
+
import { getWebSocketManager } from './instance'
|
|
21
|
+
import type { UseConfigOptions, UseWebSocketOptions, ConnectionState } from './types'
|
|
22
|
+
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// WebSocket 连接 Hook
|
|
25
|
+
// ============================================================================
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* WebSocket 连接状态和基础功能 Hook
|
|
29
|
+
*/
|
|
30
|
+
export function useWebSocket(options: UseWebSocketOptions = {}) {
|
|
31
|
+
const { autoConnect = true } = options
|
|
32
|
+
|
|
33
|
+
const wsManager = getWebSocketManager()
|
|
34
|
+
|
|
35
|
+
// 从 Redux 获取状态
|
|
36
|
+
const connected = useSelector(selectConfigConnected)
|
|
37
|
+
const entries = useSelector((state: any) => state.script?.entries || [])
|
|
38
|
+
const loadedScripts = useSelector((state: any) => state.script?.loadedScripts || [])
|
|
39
|
+
|
|
40
|
+
// 连接控制
|
|
41
|
+
const connect = useCallback(() => {
|
|
42
|
+
wsManager.connect()
|
|
43
|
+
}, [wsManager])
|
|
44
|
+
|
|
45
|
+
const disconnect = useCallback(() => {
|
|
46
|
+
wsManager.disconnect()
|
|
47
|
+
}, [wsManager])
|
|
48
|
+
|
|
49
|
+
// 消息发送
|
|
50
|
+
const send = useCallback((message: any) => {
|
|
51
|
+
wsManager.send(message)
|
|
52
|
+
}, [wsManager])
|
|
53
|
+
|
|
54
|
+
const sendRequest = useCallback(async <T = any>(message: any): Promise<T> => {
|
|
55
|
+
// 临时使用旧方法,后续完善
|
|
56
|
+
return (wsManager as any).sendRequest(message)
|
|
57
|
+
}, [wsManager])
|
|
58
|
+
|
|
59
|
+
// 状态查询
|
|
60
|
+
const isConnected = useCallback(() => {
|
|
61
|
+
return wsManager.isConnected()
|
|
62
|
+
}, [wsManager])
|
|
63
|
+
|
|
64
|
+
const getState = useCallback(() => {
|
|
65
|
+
// 临时返回连接状态,后续完善
|
|
66
|
+
return wsManager.isConnected() ? 'connected' : 'disconnected'
|
|
67
|
+
}, [wsManager])
|
|
68
|
+
|
|
69
|
+
// 自动连接
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (autoConnect && !connected) {
|
|
72
|
+
connect()
|
|
73
|
+
}
|
|
74
|
+
}, [autoConnect, connected, connect])
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
// 状态
|
|
78
|
+
connected,
|
|
79
|
+
entries,
|
|
80
|
+
loadedScripts,
|
|
81
|
+
|
|
82
|
+
// 方法
|
|
83
|
+
connect,
|
|
84
|
+
disconnect,
|
|
85
|
+
send,
|
|
86
|
+
sendRequest,
|
|
87
|
+
isConnected,
|
|
88
|
+
getState,
|
|
89
|
+
|
|
90
|
+
// WebSocket 实例(高级用法)
|
|
91
|
+
manager: wsManager
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ============================================================================
|
|
96
|
+
// 配置管理 Hook
|
|
97
|
+
// ============================================================================
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 插件配置管理 Hook
|
|
101
|
+
*/
|
|
102
|
+
export function useConfig(pluginName: string, options: UseConfigOptions = {}) {
|
|
103
|
+
const { autoLoad = true, autoLoadSchema = true } = options
|
|
104
|
+
|
|
105
|
+
const dispatch = useDispatch()
|
|
106
|
+
const wsManager = getWebSocketManager()
|
|
107
|
+
|
|
108
|
+
// 状态选择器
|
|
109
|
+
const config = useSelector((state: any) => selectConfig(state, pluginName))
|
|
110
|
+
const schema = useSelector((state: any) => selectSchema(state, pluginName))
|
|
111
|
+
const loading = useSelector((state: any) => selectConfigLoading(state, pluginName))
|
|
112
|
+
const error = useSelector((state: any) => selectConfigError(state, pluginName))
|
|
113
|
+
const connected = useSelector(selectConfigConnected)
|
|
114
|
+
|
|
115
|
+
// 配置操作
|
|
116
|
+
const getConfig = useCallback(async () => {
|
|
117
|
+
dispatch(setLoading({ pluginName, loading: true }))
|
|
118
|
+
dispatch(setError({ pluginName, error: null }))
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const result = await wsManager.getConfig(pluginName)
|
|
122
|
+
return result
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.log('getConfig',error)
|
|
125
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
126
|
+
dispatch(setError({ pluginName, error: errorMessage }))
|
|
127
|
+
throw error
|
|
128
|
+
} finally {
|
|
129
|
+
dispatch(setLoading({ pluginName, loading: false }))
|
|
130
|
+
}
|
|
131
|
+
}, [pluginName, wsManager, dispatch])
|
|
132
|
+
|
|
133
|
+
const setConfig = useCallback(async (newConfig: any) => {
|
|
134
|
+
dispatch(setLoading({ pluginName, loading: true }))
|
|
135
|
+
dispatch(setError({ pluginName, error: null }))
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
await wsManager.setConfig(pluginName, newConfig)
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.log('setConfig',error)
|
|
141
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
142
|
+
dispatch(setError({ pluginName, error: errorMessage }))
|
|
143
|
+
throw error
|
|
144
|
+
} finally {
|
|
145
|
+
dispatch(setLoading({ pluginName, loading: false }))
|
|
146
|
+
}
|
|
147
|
+
}, [pluginName, wsManager, dispatch])
|
|
148
|
+
|
|
149
|
+
const getSchema = useCallback(async () => {
|
|
150
|
+
try {
|
|
151
|
+
return await wsManager.getSchema(pluginName)
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.error(`Failed to get schema for plugin ${pluginName}:`, error)
|
|
154
|
+
throw error
|
|
155
|
+
}
|
|
156
|
+
}, [pluginName, wsManager])
|
|
157
|
+
|
|
158
|
+
// 重新加载配置和 Schema
|
|
159
|
+
const reload = useCallback(async () => {
|
|
160
|
+
const promises = []
|
|
161
|
+
|
|
162
|
+
if (autoLoad) {
|
|
163
|
+
promises.push(getConfig())
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (autoLoadSchema) {
|
|
167
|
+
promises.push(getSchema())
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
await Promise.all(promises)
|
|
171
|
+
}, [autoLoad, autoLoadSchema, getConfig, getSchema])
|
|
172
|
+
|
|
173
|
+
// 自动加载
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
if (connected && autoLoad && !config && !loading) {
|
|
176
|
+
getConfig().catch(console.error)
|
|
177
|
+
}
|
|
178
|
+
}, [connected, autoLoad, config, loading, getConfig])
|
|
179
|
+
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
if (connected && autoLoadSchema && !schema) {
|
|
182
|
+
getSchema().catch(console.error)
|
|
183
|
+
}
|
|
184
|
+
}, [connected, autoLoadSchema, schema, getSchema])
|
|
185
|
+
|
|
186
|
+
return useMemo(() => ({
|
|
187
|
+
// 状态
|
|
188
|
+
config,
|
|
189
|
+
schema,
|
|
190
|
+
loading,
|
|
191
|
+
error,
|
|
192
|
+
connected,
|
|
193
|
+
|
|
194
|
+
// 方法
|
|
195
|
+
getConfig,
|
|
196
|
+
setConfig,
|
|
197
|
+
getSchema,
|
|
198
|
+
reload
|
|
199
|
+
}), [config, schema, loading, error, connected, getConfig, setConfig, getSchema, reload])
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ============================================================================
|
|
203
|
+
// 批量配置管理 Hook
|
|
204
|
+
// ============================================================================
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* 所有配置管理 Hook
|
|
208
|
+
*/
|
|
209
|
+
export function useAllConfigs() {
|
|
210
|
+
const wsManager = getWebSocketManager()
|
|
211
|
+
|
|
212
|
+
const allConfigs = useSelector(selectAllConfigs)
|
|
213
|
+
const allSchemas = useSelector(selectAllSchemas)
|
|
214
|
+
const connected = useSelector(selectConfigConnected)
|
|
215
|
+
|
|
216
|
+
const refreshAll = useCallback(async () => {
|
|
217
|
+
try {
|
|
218
|
+
const [configs, schemas] = await Promise.all([
|
|
219
|
+
(wsManager as any).getAllConfigs(),
|
|
220
|
+
(wsManager as any).getAllSchemas()
|
|
221
|
+
])
|
|
222
|
+
|
|
223
|
+
return { configs, schemas }
|
|
224
|
+
} catch (error) {
|
|
225
|
+
console.error('Failed to refresh all configs:', error)
|
|
226
|
+
throw error
|
|
227
|
+
}
|
|
228
|
+
}, [wsManager])
|
|
229
|
+
|
|
230
|
+
return useMemo(() => ({
|
|
231
|
+
configs: allConfigs,
|
|
232
|
+
schemas: allSchemas,
|
|
233
|
+
connected,
|
|
234
|
+
refreshAll
|
|
235
|
+
}), [allConfigs, allSchemas, connected, refreshAll])
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ============================================================================
|
|
239
|
+
// 高级 Hook
|
|
240
|
+
// ============================================================================
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* WebSocket 连接状态监听 Hook
|
|
244
|
+
*/
|
|
245
|
+
export function useWebSocketState() {
|
|
246
|
+
const wsManager = getWebSocketManager()
|
|
247
|
+
const [state, setState] = useState(wsManager.getState())
|
|
248
|
+
|
|
249
|
+
useEffect(() => {
|
|
250
|
+
const checkState = () => {
|
|
251
|
+
const newState = wsManager.getState()
|
|
252
|
+
if (newState !== state) {
|
|
253
|
+
setState(newState)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const interval = setInterval(checkState, 1000)
|
|
258
|
+
return () => clearInterval(interval)
|
|
259
|
+
}, [wsManager, state])
|
|
260
|
+
|
|
261
|
+
return state
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* WebSocket 消息监听 Hook
|
|
266
|
+
*/
|
|
267
|
+
export function useWebSocketMessages<T = any>(
|
|
268
|
+
filter?: (message: any) => boolean,
|
|
269
|
+
handler?: (message: T) => void
|
|
270
|
+
) {
|
|
271
|
+
const [messages, setMessages] = useState<T[]>([])
|
|
272
|
+
|
|
273
|
+
useEffect(() => {
|
|
274
|
+
const wsManager = getWebSocketManager()
|
|
275
|
+
// 这里需要扩展 WebSocketManager 来支持消息监听
|
|
276
|
+
// 暂时留空,后续可以扩展
|
|
277
|
+
}, [filter, handler])
|
|
278
|
+
|
|
279
|
+
return messages
|
|
280
|
+
}
|