@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.
- package/README.md +23 -27
- package/{src → client}/index.ts +0 -1
- package/{src → client}/store/reducers/route.ts +1 -1
- package/{src → client}/store/reducers/script.ts +1 -1
- package/{src → client}/store/reducers/ui.ts +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -0
- package/dist/router/index.d.ts +25 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/router/index.js +49 -0
- package/dist/router/index.js.map +1 -0
- package/dist/store/index.d.ts +19 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +67 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/reducers/config.d.ts +54 -0
- package/dist/store/reducers/config.d.ts.map +1 -0
- package/dist/store/reducers/config.js +78 -0
- package/dist/store/reducers/config.js.map +1 -0
- package/dist/store/reducers/index.d.ts +13 -0
- package/dist/store/reducers/index.d.ts.map +1 -0
- package/dist/store/reducers/index.js +11 -0
- package/dist/store/reducers/index.js.map +1 -0
- package/dist/store/reducers/route.d.ts +26 -0
- package/dist/store/reducers/route.d.ts.map +1 -0
- package/dist/store/reducers/route.js +85 -0
- package/dist/store/reducers/route.js.map +1 -0
- package/dist/store/reducers/script.d.ts +11 -0
- package/dist/store/reducers/script.d.ts.map +1 -0
- package/dist/store/reducers/script.js +74 -0
- package/dist/store/reducers/script.js.map +1 -0
- package/dist/store/reducers/ui.d.ts +8 -0
- package/dist/store/reducers/ui.d.ts.map +1 -0
- package/dist/store/reducers/ui.js +23 -0
- package/dist/store/reducers/ui.js.map +1 -0
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/websocket/hooks.d.ts +55 -0
- package/dist/websocket/hooks.d.ts.map +1 -0
- package/dist/websocket/hooks.js +225 -0
- package/dist/websocket/hooks.js.map +1 -0
- package/dist/websocket/index.d.ts +13 -0
- package/dist/websocket/index.d.ts.map +1 -0
- package/dist/websocket/index.js +31 -0
- package/dist/websocket/index.js.map +1 -0
- package/dist/websocket/instance.d.ts +18 -0
- package/dist/websocket/instance.d.ts.map +1 -0
- package/dist/websocket/instance.js +39 -0
- package/dist/websocket/instance.js.map +1 -0
- package/dist/websocket/manager.d.ts +110 -0
- package/dist/websocket/manager.d.ts.map +1 -0
- package/dist/websocket/manager.js +341 -0
- package/dist/websocket/manager.js.map +1 -0
- package/dist/websocket/messageHandler.d.ts +48 -0
- package/dist/websocket/messageHandler.d.ts.map +1 -0
- package/dist/websocket/messageHandler.js +140 -0
- package/dist/websocket/messageHandler.js.map +1 -0
- package/dist/websocket/types.d.ts +125 -0
- package/dist/websocket/types.d.ts.map +1 -0
- package/dist/websocket/types.js +43 -0
- package/dist/websocket/types.js.map +1 -0
- package/package.json +12 -18
- package/app/index.html +0 -13
- package/app/postcss.config.js +0 -5
- package/app/src/components/PluginConfigForm/BasicFieldRenderers.tsx +0 -253
- package/app/src/components/PluginConfigForm/CollectionFieldRenderers.tsx +0 -261
- package/app/src/components/PluginConfigForm/CompositeFieldRenderers.tsx +0 -105
- package/app/src/components/PluginConfigForm/FieldRenderer.tsx +0 -110
- package/app/src/components/PluginConfigForm/NestedFieldRenderer.tsx +0 -95
- package/app/src/components/PluginConfigForm/index.tsx +0 -237
- package/app/src/components/PluginConfigForm/types.ts +0 -46
- package/app/src/components/ThemeToggle.tsx +0 -21
- package/app/src/hooks/useTheme.ts +0 -17
- package/app/src/layouts/dashboard.tsx +0 -259
- package/app/src/main.tsx +0 -121
- package/app/src/pages/dashboard-bots.tsx +0 -198
- package/app/src/pages/dashboard-home.tsx +0 -301
- package/app/src/pages/dashboard-logs.tsx +0 -298
- package/app/src/pages/dashboard-plugin-detail.tsx +0 -360
- package/app/src/pages/dashboard-plugins.tsx +0 -166
- package/app/src/style.css +0 -1105
- package/app/src/theme/index.ts +0 -92
- package/app/tailwind.config.js +0 -70
- package/app/tsconfig.json +0 -16
- /package/{src → client}/router/index.tsx +0 -0
- /package/{src → client}/store/index.ts +0 -0
- /package/{src → client}/store/reducers/config.ts +0 -0
- /package/{src → client}/store/reducers/index.ts +0 -0
- /package/{src → client}/types.ts +0 -0
- /package/{src → client}/websocket/hooks.ts +0 -0
- /package/{src → client}/websocket/index.ts +0 -0
- /package/{src → client}/websocket/instance.ts +0 -0
- /package/{src → client}/websocket/manager.ts +0 -0
- /package/{src → client}/websocket/messageHandler.ts +0 -0
- /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
|
-
}
|