@zhin.js/client 1.0.4 → 1.0.6

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.
Files changed (99) hide show
  1. package/README.md +23 -27
  2. package/{src → client}/index.ts +0 -1
  3. package/{src → client}/store/reducers/route.ts +1 -1
  4. package/{src → client}/store/reducers/script.ts +1 -1
  5. package/{src → client}/store/reducers/ui.ts +1 -1
  6. package/dist/index.d.ts +7 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +0 -1
  9. package/dist/index.js.map +1 -0
  10. package/dist/router/index.d.ts +25 -0
  11. package/dist/router/index.d.ts.map +1 -0
  12. package/dist/router/index.js +49 -0
  13. package/dist/router/index.js.map +1 -0
  14. package/dist/store/index.d.ts +19 -0
  15. package/dist/store/index.d.ts.map +1 -0
  16. package/dist/store/index.js +67 -0
  17. package/dist/store/index.js.map +1 -0
  18. package/dist/store/reducers/config.d.ts +54 -0
  19. package/dist/store/reducers/config.d.ts.map +1 -0
  20. package/dist/store/reducers/config.js +78 -0
  21. package/dist/store/reducers/config.js.map +1 -0
  22. package/dist/store/reducers/index.d.ts +13 -0
  23. package/dist/store/reducers/index.d.ts.map +1 -0
  24. package/dist/store/reducers/index.js +11 -0
  25. package/dist/store/reducers/index.js.map +1 -0
  26. package/dist/store/reducers/route.d.ts +26 -0
  27. package/dist/store/reducers/route.d.ts.map +1 -0
  28. package/dist/store/reducers/route.js +85 -0
  29. package/dist/store/reducers/route.js.map +1 -0
  30. package/dist/store/reducers/script.d.ts +11 -0
  31. package/dist/store/reducers/script.d.ts.map +1 -0
  32. package/dist/store/reducers/script.js +74 -0
  33. package/dist/store/reducers/script.js.map +1 -0
  34. package/dist/store/reducers/ui.d.ts +8 -0
  35. package/dist/store/reducers/ui.d.ts.map +1 -0
  36. package/dist/store/reducers/ui.js +23 -0
  37. package/dist/store/reducers/ui.js.map +1 -0
  38. package/dist/types.d.ts +7 -0
  39. package/dist/types.d.ts.map +1 -0
  40. package/dist/types.js +2 -0
  41. package/dist/types.js.map +1 -0
  42. package/dist/websocket/hooks.d.ts +55 -0
  43. package/dist/websocket/hooks.d.ts.map +1 -0
  44. package/dist/websocket/hooks.js +225 -0
  45. package/dist/websocket/hooks.js.map +1 -0
  46. package/dist/websocket/index.d.ts +13 -0
  47. package/dist/websocket/index.d.ts.map +1 -0
  48. package/dist/websocket/index.js +31 -0
  49. package/dist/websocket/index.js.map +1 -0
  50. package/dist/websocket/instance.d.ts +18 -0
  51. package/dist/websocket/instance.d.ts.map +1 -0
  52. package/dist/websocket/instance.js +39 -0
  53. package/dist/websocket/instance.js.map +1 -0
  54. package/dist/websocket/manager.d.ts +110 -0
  55. package/dist/websocket/manager.d.ts.map +1 -0
  56. package/dist/websocket/manager.js +341 -0
  57. package/dist/websocket/manager.js.map +1 -0
  58. package/dist/websocket/messageHandler.d.ts +48 -0
  59. package/dist/websocket/messageHandler.d.ts.map +1 -0
  60. package/dist/websocket/messageHandler.js +140 -0
  61. package/dist/websocket/messageHandler.js.map +1 -0
  62. package/dist/websocket/types.d.ts +125 -0
  63. package/dist/websocket/types.d.ts.map +1 -0
  64. package/dist/websocket/types.js +43 -0
  65. package/dist/websocket/types.js.map +1 -0
  66. package/package.json +12 -18
  67. package/app/index.html +0 -13
  68. package/app/postcss.config.js +0 -5
  69. package/app/src/components/PluginConfigForm/BasicFieldRenderers.tsx +0 -253
  70. package/app/src/components/PluginConfigForm/CollectionFieldRenderers.tsx +0 -261
  71. package/app/src/components/PluginConfigForm/CompositeFieldRenderers.tsx +0 -105
  72. package/app/src/components/PluginConfigForm/FieldRenderer.tsx +0 -110
  73. package/app/src/components/PluginConfigForm/NestedFieldRenderer.tsx +0 -95
  74. package/app/src/components/PluginConfigForm/index.tsx +0 -237
  75. package/app/src/components/PluginConfigForm/types.ts +0 -46
  76. package/app/src/components/ThemeToggle.tsx +0 -21
  77. package/app/src/hooks/useTheme.ts +0 -17
  78. package/app/src/layouts/dashboard.tsx +0 -259
  79. package/app/src/main.tsx +0 -121
  80. package/app/src/pages/dashboard-bots.tsx +0 -198
  81. package/app/src/pages/dashboard-home.tsx +0 -301
  82. package/app/src/pages/dashboard-logs.tsx +0 -298
  83. package/app/src/pages/dashboard-plugin-detail.tsx +0 -360
  84. package/app/src/pages/dashboard-plugins.tsx +0 -166
  85. package/app/src/style.css +0 -1105
  86. package/app/src/theme/index.ts +0 -92
  87. package/app/tailwind.config.js +0 -70
  88. package/app/tsconfig.json +0 -16
  89. /package/{src → client}/router/index.tsx +0 -0
  90. /package/{src → client}/store/index.ts +0 -0
  91. /package/{src → client}/store/reducers/config.ts +0 -0
  92. /package/{src → client}/store/reducers/index.ts +0 -0
  93. /package/{src → client}/types.ts +0 -0
  94. /package/{src → client}/websocket/hooks.ts +0 -0
  95. /package/{src → client}/websocket/index.ts +0 -0
  96. /package/{src → client}/websocket/instance.ts +0 -0
  97. /package/{src → client}/websocket/manager.ts +0 -0
  98. /package/{src → client}/websocket/messageHandler.ts +0 -0
  99. /package/{src → client}/websocket/types.ts +0 -0
@@ -1,298 +0,0 @@
1
- import { useEffect, useState, useRef } from 'react'
2
- import { Flex, Box, Spinner, Text, Callout, Heading, Badge, Grid, Card, Button, Select, Checkbox } from '@radix-ui/themes'
3
- import { Info, AlertTriangle, XCircle, Circle, Trash2, RefreshCw, FileText, AlertCircle } from 'lucide-react'
4
-
5
- interface LogEntry {
6
- level: 'info' | 'warn' | 'error'
7
- message: string
8
- timestamp: string
9
- source: string
10
- }
11
-
12
- interface LogStats {
13
- total: number
14
- byLevel: Record<string, number>
15
- oldestTimestamp: string | null
16
- }
17
-
18
- export default function DashboardLogs() {
19
- const [logs, setLogs] = useState<LogEntry[]>([])
20
- const [stats, setStats] = useState<LogStats | null>(null)
21
- const [loading, setLoading] = useState(true)
22
- const [error, setError] = useState<string | null>(null)
23
- const [levelFilter, setLevelFilter] = useState<string>('all')
24
- const [autoScroll, setAutoScroll] = useState(true)
25
- const logsEndRef = useRef<HTMLDivElement>(null)
26
- const [prevLogCount, setPrevLogCount] = useState(0)
27
-
28
- useEffect(() => {
29
- fetchLogs()
30
- fetchStats()
31
- const interval = setInterval(() => {
32
- fetchLogs()
33
- fetchStats()
34
- }, 3000)
35
- return () => clearInterval(interval)
36
- }, [levelFilter])
37
-
38
- useEffect(() => {
39
- if (autoScroll && logs.length > prevLogCount) {
40
- logsEndRef.current?.scrollIntoView({ behavior: 'smooth' })
41
- }
42
- setPrevLogCount(logs.length)
43
- }, [logs, autoScroll])
44
-
45
- const fetchLogs = async () => {
46
- try {
47
- const url = levelFilter === 'all'
48
- ? '/api/logs?limit=100'
49
- : `/api/logs?limit=100&level=${levelFilter}`
50
-
51
- const res = await fetch(url, { credentials: 'include' })
52
- if (!res.ok) throw new Error('API 请求失败')
53
-
54
- const data = await res.json()
55
- if (data.success && Array.isArray(data.data)) {
56
- setLogs(data.data.reverse())
57
- setError(null)
58
- }
59
- } catch (err) {
60
- setError((err as Error).message)
61
- } finally {
62
- setLoading(false)
63
- }
64
- }
65
-
66
- const fetchStats = async () => {
67
- try {
68
- const res = await fetch('/api/logs/stats', { credentials: 'include' })
69
- if (!res.ok) return
70
-
71
- const data = await res.json()
72
- if (data.success) {
73
- setStats(data.data)
74
- }
75
- } catch (err) {
76
- console.error('Failed to fetch stats:', err)
77
- }
78
- }
79
-
80
- const handleCleanup = async (days?: number, maxRecords?: number) => {
81
- const message = days
82
- ? `确定清理 ${days} 天前的日志吗?`
83
- : `确定只保留最近 ${maxRecords} 条日志吗?`
84
-
85
- if (!confirm(message)) return
86
-
87
- try {
88
- const res = await fetch('/api/logs/cleanup', {
89
- method: 'POST',
90
- headers: { 'Content-Type': 'application/json' },
91
- body: JSON.stringify({ days, maxRecords }),
92
- credentials: 'include'
93
- })
94
-
95
- if (!res.ok) throw new Error('清理失败')
96
-
97
- const data = await res.json()
98
- if (data.success) {
99
- alert(`成功清理 ${data.data.deletedCount} 条日志`)
100
- fetchLogs()
101
- fetchStats()
102
- }
103
- } catch (err) {
104
- alert((err as Error).message)
105
- }
106
- }
107
-
108
- const getLevelColor = (level: string): 'blue' | 'amber' | 'red' | 'gray' => {
109
- switch (level) {
110
- case 'info': return 'blue'
111
- case 'warn': return 'amber'
112
- case 'error': return 'red'
113
- default: return 'gray'
114
- }
115
- }
116
-
117
- const getLevelIcon = (level: string) => {
118
- switch (level) {
119
- case 'info': return <Info size={14} />
120
- case 'warn': return <AlertTriangle size={14} />
121
- case 'error': return <XCircle size={14} />
122
- default: return <Circle size={14} />
123
- }
124
- }
125
-
126
- if (loading) {
127
- return (
128
- <Flex align="center" justify="center" style={{ height: '100%' }}>
129
- <Box>
130
- <Spinner size="3" />
131
- <Text size="2" color="gray" style={{ marginTop: '8px' }}>加载中...</Text>
132
- </Box>
133
- </Flex>
134
- )
135
- }
136
-
137
- return (
138
- <Box>
139
- {/* 页面标题 */}
140
- <Flex direction="column" gap="2" mb="4">
141
- <Heading size="8">系统日志</Heading>
142
- <Text color="gray">实时查看系统运行日志</Text>
143
- </Flex>
144
-
145
- {/* 日志统计 */}
146
- {stats && (
147
- <Grid columns={{ initial: '2', sm: '4' }} gap="3" mb="4">
148
- <Card>
149
- <Flex direction="column" align="center" gap="1" p="3">
150
- <Text size="5" weight="bold">{stats.total}</Text>
151
- <Text size="1" color="gray">总日志数</Text>
152
- </Flex>
153
- </Card>
154
- <Card>
155
- <Flex direction="column" align="center" gap="1" p="3">
156
- <Text size="5" weight="bold" color="blue">{stats.byLevel.info || 0}</Text>
157
- <Text size="1" color="gray">Info</Text>
158
- </Flex>
159
- </Card>
160
- <Card>
161
- <Flex direction="column" align="center" gap="1" p="3">
162
- <Text size="5" weight="bold" color="amber">{stats.byLevel.warn || 0}</Text>
163
- <Text size="1" color="gray">Warn</Text>
164
- </Flex>
165
- </Card>
166
- <Card>
167
- <Flex direction="column" align="center" gap="1" p="3">
168
- <Text size="5" weight="bold" color="red">{stats.byLevel.error || 0}</Text>
169
- <Text size="1" color="gray">Error</Text>
170
- </Flex>
171
- </Card>
172
- </Grid>
173
- )}
174
-
175
- {/* 工具栏 */}
176
- <Card mb="4">
177
- <Flex justify="between" align="center" p="3" wrap="wrap" gap="3">
178
- <Flex align="center" gap="3" wrap="wrap">
179
- <Select.Root value={levelFilter} onValueChange={setLevelFilter}>
180
- <Select.Trigger style={{ minWidth: '120px' }} />
181
- <Select.Content>
182
- <Select.Item value="all">所有级别</Select.Item>
183
- <Select.Item value="info">Info</Select.Item>
184
- <Select.Item value="warn">Warn</Select.Item>
185
- <Select.Item value="error">Error</Select.Item>
186
- </Select.Content>
187
- </Select.Root>
188
-
189
- <Flex as="span">
190
- <Checkbox
191
- checked={autoScroll}
192
- onCheckedChange={(checked) => setAutoScroll(checked as boolean)}
193
- />
194
- <Text size="2">自动滚动</Text>
195
- </Flex>
196
- </Flex>
197
-
198
- <Flex gap="2">
199
- <Button
200
- variant="soft"
201
- onClick={() => handleCleanup(7)}
202
- size="2"
203
- >
204
- <Trash2 size={14} />
205
- 清理7天前
206
- </Button>
207
- <Button
208
- variant="soft"
209
- onClick={() => handleCleanup(undefined, 5000)}
210
- size="2"
211
- >
212
- <Trash2 size={14} />
213
- 保留5000条
214
- </Button>
215
- <Button
216
- variant="soft"
217
- onClick={() => fetchLogs()}
218
- size="2"
219
- >
220
- <RefreshCw size={14} />
221
- 刷新
222
- </Button>
223
- </Flex>
224
- </Flex>
225
- </Card>
226
-
227
- {/* 日志列表 */}
228
- <Card>
229
- <Box
230
- p="4"
231
- style={{
232
- maxHeight: '600px',
233
- overflowY: 'auto',
234
- backgroundColor: 'var(--gray-1)'
235
- }}
236
- >
237
- {error ? (
238
- <Callout.Root color="red">
239
- <Callout.Icon>
240
- <AlertCircle />
241
- </Callout.Icon>
242
- <Callout.Text>
243
- 加载失败: {error}
244
- </Callout.Text>
245
- </Callout.Root>
246
- ) : logs.length === 0 ? (
247
- <Flex direction="column" align="center" gap="3" py="9">
248
- <FileText size={48} color="var(--gray-6)" />
249
- <Text color="gray">暂无日志</Text>
250
- </Flex>
251
- ) : (
252
- <Flex direction="column" gap="2">
253
- {logs.map((log, index) => (
254
- <Box
255
- key={`${log.timestamp}-${index}`}
256
- p="3"
257
- style={{
258
- borderRadius: '6px',
259
- backgroundColor: 'var(--gray-2)',
260
- borderLeft: `3px solid var(--${getLevelColor(log.level)}-9)`
261
- }}
262
- >
263
- <Flex direction="column" gap="1">
264
- <Flex align="center" gap="2">
265
- <Badge color={getLevelColor(log.level)} size="1">
266
- <Flex align="center" gap="1">
267
- {getLevelIcon(log.level)}
268
- {log.level.toUpperCase()}
269
- </Flex>
270
- </Badge>
271
- <Text size="1" color="gray">
272
- {new Date(log.timestamp).toLocaleString()}
273
- </Text>
274
- {log.source && (
275
- <Badge variant="soft" size="1">{log.source}</Badge>
276
- )}
277
- </Flex>
278
- <Text
279
- size="2"
280
- style={{
281
- fontFamily: 'monospace',
282
- whiteSpace: 'pre-wrap',
283
- wordBreak: 'break-word'
284
- }}
285
- >
286
- {log.message}
287
- </Text>
288
- </Flex>
289
- </Box>
290
- ))}
291
- <div ref={logsEndRef} />
292
- </Flex>
293
- )}
294
- </Box>
295
- </Card>
296
- </Box>
297
- )
298
- }
@@ -1,360 +0,0 @@
1
- import { useEffect, useState } from 'react'
2
- import { useParams, useNavigate } from 'react-router'
3
- import * as Themes from '@radix-ui/themes'
4
- import { Icons } from '@zhin.js/client'
5
- import {PluginConfigForm} from '../components/PluginConfigForm'
6
-
7
- const { Flex, Box, Spinner, Text, Callout, Heading, Badge, Grid, Card, Button, Code, Separator, ScrollArea, Dialog } = Themes
8
-
9
- interface PluginDetail {
10
- name: string
11
- filename: string
12
- status: 'active' | 'inactive'
13
- description: string
14
- commands: Array<{
15
- name: string
16
- }>
17
- components: Array<{
18
- name: string
19
- props: Record<string, any>
20
- type: string
21
- }>
22
- middlewares: Array<{
23
- id: string
24
- type: string
25
- }>
26
- contexts: Array<{
27
- name: string
28
- description: string
29
- }>
30
- crons: Array<{
31
- id: string
32
- pattern: string
33
- running: boolean
34
- }>
35
- definitions: Array<{
36
- name: string
37
- fields: string[]
38
- }>
39
- statistics: {
40
- commandCount: number
41
- componentCount: number
42
- middlewareCount: number
43
- contextCount: number
44
- cronCount: number
45
- definitionCount: number
46
- }
47
- }
48
-
49
- export default function DashboardPluginDetail() {
50
- const { name } = useParams<{ name: string }>()
51
- const navigate = useNavigate()
52
- const [plugin, setPlugin] = useState<PluginDetail | null>(null)
53
- const [loading, setLoading] = useState(true)
54
- const [error, setError] = useState<string | null>(null)
55
-
56
- useEffect(() => {
57
- if (name) {
58
- fetchPluginDetail(name)
59
- }
60
- }, [name])
61
-
62
- const fetchPluginDetail = async (pluginName: string) => {
63
- try {
64
- const res = await fetch(`/api/plugins/${encodeURIComponent(pluginName)}`, { credentials: 'include' })
65
- if (!res.ok) throw new Error('API 请求失败')
66
-
67
- const data = await res.json()
68
- if (data.success) {
69
- setPlugin(data.data)
70
- setError(null)
71
- } else {
72
- throw new Error('数据格式错误')
73
- }
74
- } catch (err) {
75
- setError((err as Error).message)
76
- } finally {
77
- setLoading(false)
78
- }
79
- }
80
-
81
-
82
-
83
- if (loading) {
84
- return (
85
- <Flex align="center" justify="center" className="h-full">
86
- <Flex direction="column" align="center" gap="3">
87
- <Spinner size="3" />
88
- <Text size="2" color="gray">加载中...</Text>
89
- </Flex>
90
- </Flex>
91
- )
92
- }
93
-
94
- if (error || !plugin) {
95
- return (
96
- <Box>
97
- <Button variant="ghost" onClick={() => navigate('/plugins')} mb="4" size="2">
98
- <Icons.ArrowLeft className="w-4 h-4" />
99
- 返回
100
- </Button>
101
- <Callout.Root color="red">
102
- <Callout.Icon>
103
- <Icons.AlertCircle />
104
- </Callout.Icon>
105
- <Callout.Text>
106
- 加载失败: {error || '插件不存在'}
107
- </Callout.Text>
108
- </Callout.Root>
109
- </Box>
110
- )
111
- }
112
-
113
- return (
114
- <Box>
115
- {/* 头部 */}
116
- <Flex direction="column" gap="3" mb="4">
117
- <Button variant="ghost" onClick={() => navigate('/plugins')} size="2">
118
- <Icons.ArrowLeft className="w-4 h-4" />
119
- 返回
120
- </Button>
121
-
122
- <Flex align="center" gap="3">
123
- <Flex align="center" justify="center" className="w-12 h-12 rounded-xl bg-blue-500/10 dark:bg-blue-400/10">
124
- <Icons.Package className="w-6 h-6 text-blue-600 dark:text-blue-400" />
125
- </Flex>
126
- <Flex direction="column" gap="1">
127
- <Flex align="center" gap="2">
128
- <Heading size="5">{plugin.name}</Heading>
129
- <Badge color={plugin.status === 'active' ? 'green' : 'gray'} size="1">
130
- {plugin.status === 'active' ? '运行中' : '已停止'}
131
- </Badge>
132
- </Flex>
133
- <Text size="2" color="gray">{plugin.description || '暂无描述'}</Text>
134
- </Flex>
135
- </Flex>
136
- </Flex>
137
-
138
- {/* 插件配置折叠面板 */}
139
- <PluginConfigForm
140
- pluginName={plugin.name}
141
- onSuccess={() => {
142
- // 配置更新会通过 WebSocket 自动同步
143
- }}
144
- />
145
-
146
- <Separator size="4" my="4" />
147
-
148
- {/* 统计概览 - 紧凑卡片 */}
149
- <Grid columns={{ initial: '2', sm: '3', md: '6' }} gap="2" mb="4">
150
- <Card size="1">
151
- <Flex direction="column" align="center" gap="1" p="2">
152
- <Icons.Terminal className="w-4 h-4 text-blue-600 dark:text-blue-400" />
153
- <Text size="4" weight="bold">{plugin.statistics.commandCount}</Text>
154
- <Text size="1" color="gray">命令</Text>
155
- </Flex>
156
- </Card>
157
-
158
- <Card size="1">
159
- <Flex direction="column" align="center" gap="1" p="2">
160
- <Icons.Box className="w-4 h-4 text-green-600 dark:text-green-400" />
161
- <Text size="4" weight="bold">{plugin.statistics.componentCount}</Text>
162
- <Text size="1" color="gray">组件</Text>
163
- </Flex>
164
- </Card>
165
-
166
- <Card size="1">
167
- <Flex direction="column" align="center" gap="1" p="2">
168
- <Icons.Layers className="w-4 h-4 text-purple-600 dark:text-purple-400" />
169
- <Text size="4" weight="bold">{plugin.statistics.middlewareCount}</Text>
170
- <Text size="1" color="gray">中间件</Text>
171
- </Flex>
172
- </Card>
173
-
174
- <Card size="1">
175
- <Flex direction="column" align="center" gap="1" p="2">
176
- <Icons.Database className="w-4 h-4 text-orange-600 dark:text-orange-400" />
177
- <Text size="4" weight="bold">{plugin.statistics.contextCount}</Text>
178
- <Text size="1" color="gray">上下文</Text>
179
- </Flex>
180
- </Card>
181
-
182
- <Card size="1">
183
- <Flex direction="column" align="center" gap="1" p="2">
184
- <Icons.Clock className="w-4 h-4 text-amber-600 dark:text-amber-400" />
185
- <Text size="4" weight="bold">{plugin.statistics.cronCount}</Text>
186
- <Text size="1" color="gray">定时任务</Text>
187
- </Flex>
188
- </Card>
189
-
190
- <Card size="1">
191
- <Flex direction="column" align="center" gap="1" p="2">
192
- <Icons.FileText className="w-4 h-4 text-cyan-600 dark:text-cyan-400" />
193
- <Text size="4" weight="bold">{plugin.statistics.definitionCount}</Text>
194
- <Text size="1" color="gray">数据模型</Text>
195
- </Flex>
196
- </Card>
197
- </Grid>
198
-
199
- {/* 详细信息 - 紧凑布局 */}
200
- <Grid columns={{ initial: '1', md: '2' }} gap="3">
201
- {/* 命令列表 */}
202
- {plugin.commands.length > 0 && (
203
- <Card size="2">
204
- <Flex direction="column" gap="2" p="3">
205
- <Flex align="center" gap="2">
206
- <Icons.Terminal className="w-4 h-4 text-blue-600 dark:text-blue-400" />
207
- <Heading size="3">命令</Heading>
208
- <Badge size="1">{plugin.commands.length}</Badge>
209
- </Flex>
210
- <Separator size="4" />
211
- <Flex direction="column" gap="2" className="max-h-60 overflow-y-auto">
212
- {plugin.commands.map((cmd, index) => (
213
- <Box key={index} className="rounded-lg bg-gray-50 dark:bg-gray-900 p-2">
214
- <Flex direction="column" gap="1">
215
- <Flex align="center" gap="2">
216
- <Code size="2">{cmd.name}</Code>
217
- </Flex>
218
- </Flex>
219
- </Box>
220
- ))}
221
- </Flex>
222
- </Flex>
223
- </Card>
224
- )}
225
-
226
- {/* 组件列表 */}
227
- {plugin.components.length > 0 && (
228
- <Card size="2">
229
- <Flex direction="column" gap="2" p="3">
230
- <Flex align="center" gap="2">
231
- <Icons.Box className="w-4 h-4 text-green-600 dark:text-green-400" />
232
- <Heading size="3">组件</Heading>
233
- <Badge size="1">{plugin.components.length}</Badge>
234
- </Flex>
235
- <Separator size="4" />
236
- <Flex direction="column" gap="2" className="max-h-60 overflow-y-auto">
237
- {plugin.components.map((comp, index) => (
238
- <Box key={index} className="rounded-lg bg-gray-50 dark:bg-gray-900 p-2">
239
- <Flex direction="column" gap="1">
240
- <Flex align="center" gap="2">
241
- <Code size="2">{comp.name}</Code>
242
- <Badge size="1" variant="soft">{comp.type}</Badge>
243
- </Flex>
244
- <Text size="1" color="gray">
245
- 属性数: {Object.keys(comp.props).length}
246
- </Text>
247
- </Flex>
248
- </Box>
249
- ))}
250
- </Flex>
251
- </Flex>
252
- </Card>
253
- )}
254
-
255
- {/* 中间件列表 */}
256
- {plugin.middlewares.length > 0 && (
257
- <Card size="2">
258
- <Flex direction="column" gap="2" p="3">
259
- <Flex align="center" gap="2">
260
- <Icons.Layers className="w-4 h-4 text-purple-600 dark:text-purple-400" />
261
- <Heading size="3">中间件</Heading>
262
- <Badge size="1">{plugin.middlewares.length}</Badge>
263
- </Flex>
264
- <Separator size="4" />
265
- <Flex direction="column" gap="2" className="max-h-60 overflow-y-auto">
266
- {plugin.middlewares.map((mw, index) => (
267
- <Box key={index} className="rounded-lg bg-gray-50 dark:bg-gray-900 p-2">
268
- <Flex align="center" gap="2">
269
- <Code size="2">{mw.id}</Code>
270
- <Badge size="1" variant="soft">{mw.type}</Badge>
271
- </Flex>
272
- </Box>
273
- ))}
274
- </Flex>
275
- </Flex>
276
- </Card>
277
- )}
278
-
279
- {/* 上下文列表 */}
280
- {plugin.contexts.length > 0 && (
281
- <Card size="2">
282
- <Flex direction="column" gap="2" p="3">
283
- <Flex align="center" gap="2">
284
- <Icons.Database className="w-4 h-4 text-orange-600 dark:text-orange-400" />
285
- <Heading size="3">上下文</Heading>
286
- <Badge size="1">{plugin.contexts.length}</Badge>
287
- </Flex>
288
- <Separator size="4" />
289
- <Flex direction="column" gap="2" className="max-h-60 overflow-y-auto">
290
- {plugin.contexts.map((ctx, index) => (
291
- <Box key={index} className="rounded-lg bg-gray-50 dark:bg-gray-900 p-2">
292
- <Flex direction="column" gap="1">
293
- <Code size="2">{ctx.name}</Code>
294
- <Text size="1" color="gray">{ctx.description}</Text>
295
- </Flex>
296
- </Box>
297
- ))}
298
- </Flex>
299
- </Flex>
300
- </Card>
301
- )}
302
-
303
- {/* 定时任务列表 */}
304
- {plugin.crons.length > 0 && (
305
- <Card size="2">
306
- <Flex direction="column" gap="2" p="3">
307
- <Flex align="center" gap="2">
308
- <Icons.Clock className="w-4 h-4 text-amber-600 dark:text-amber-400" />
309
- <Heading size="3">定时任务</Heading>
310
- <Badge size="1">{plugin.crons.length}</Badge>
311
- </Flex>
312
- <Separator size="4" />
313
- <Flex direction="column" gap="2" className="max-h-60 overflow-y-auto">
314
- {plugin.crons.map((cron, index) => (
315
- <Box key={index} className="rounded-lg bg-gray-50 dark:bg-gray-900 p-2">
316
- <Flex justify="between" align="center">
317
- <Flex direction="column" gap="1">
318
- <Code size="2">{cron.id}</Code>
319
- <Text size="1" color="gray">{cron.pattern}</Text>
320
- </Flex>
321
- <Badge color={cron.running ? 'green' : 'gray'} size="1">
322
- {cron.running ? '运行中' : '已停止'}
323
- </Badge>
324
- </Flex>
325
- </Box>
326
- ))}
327
- </Flex>
328
- </Flex>
329
- </Card>
330
- )}
331
-
332
- {/* 数据模型列表 */}
333
- {plugin.definitions.length > 0 && (
334
- <Card size="2">
335
- <Flex direction="column" gap="2" p="3">
336
- <Flex align="center" gap="2">
337
- <Icons.FileText className="w-4 h-4 text-cyan-600 dark:text-cyan-400" />
338
- <Heading size="3">数据模型</Heading>
339
- <Badge size="1">{plugin.definitions.length}</Badge>
340
- </Flex>
341
- <Separator size="4" />
342
- <Flex direction="column" gap="2" className="max-h-60 overflow-y-auto">
343
- {plugin.definitions.map((definition, index) => (
344
- <Box key={index} className="rounded-lg bg-gray-50 dark:bg-gray-900 p-2">
345
- <Flex direction="column" gap="1">
346
- <Code size="2">{definition.name}</Code>
347
- <Text size="1" color="gray">
348
- 字段: {definition.fields.join(', ')}
349
- </Text>
350
- </Flex>
351
- </Box>
352
- ))}
353
- </Flex>
354
- </Flex>
355
- </Card>
356
- )}
357
- </Grid>
358
- </Box>
359
- )
360
- }