@zhin.js/console 1.0.20 → 1.0.22

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 (56) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +4 -4
  3. package/client/components.json +17 -0
  4. package/client/index.html +1 -1
  5. package/client/src/components/PluginConfigForm/BasicFieldRenderers.tsx +89 -180
  6. package/client/src/components/PluginConfigForm/CollectionFieldRenderers.tsx +97 -200
  7. package/client/src/components/PluginConfigForm/CompositeFieldRenderers.tsx +31 -70
  8. package/client/src/components/PluginConfigForm/FieldRenderer.tsx +27 -77
  9. package/client/src/components/PluginConfigForm/NestedFieldRenderer.tsx +33 -53
  10. package/client/src/components/PluginConfigForm/index.tsx +71 -173
  11. package/client/src/components/ui/accordion.tsx +54 -0
  12. package/client/src/components/ui/alert.tsx +62 -0
  13. package/client/src/components/ui/avatar.tsx +41 -0
  14. package/client/src/components/ui/badge.tsx +32 -0
  15. package/client/src/components/ui/button.tsx +50 -0
  16. package/client/src/components/ui/card.tsx +50 -0
  17. package/client/src/components/ui/checkbox.tsx +25 -0
  18. package/client/src/components/ui/dialog.tsx +87 -0
  19. package/client/src/components/ui/dropdown-menu.tsx +97 -0
  20. package/client/src/components/ui/input.tsx +21 -0
  21. package/client/src/components/ui/scroll-area.tsx +43 -0
  22. package/client/src/components/ui/select.tsx +127 -0
  23. package/client/src/components/ui/separator.tsx +23 -0
  24. package/client/src/components/ui/skeleton.tsx +12 -0
  25. package/client/src/components/ui/switch.tsx +26 -0
  26. package/client/src/components/ui/tabs.tsx +52 -0
  27. package/client/src/components/ui/textarea.tsx +20 -0
  28. package/client/src/components/ui/tooltip.tsx +27 -0
  29. package/client/src/layouts/dashboard.tsx +91 -221
  30. package/client/src/main.tsx +38 -42
  31. package/client/src/pages/dashboard-bots.tsx +91 -137
  32. package/client/src/pages/dashboard-home.tsx +133 -204
  33. package/client/src/pages/dashboard-logs.tsx +125 -196
  34. package/client/src/pages/dashboard-plugin-detail.tsx +261 -329
  35. package/client/src/pages/dashboard-plugins.tsx +108 -105
  36. package/client/src/style.css +156 -865
  37. package/client/src/theme/index.ts +60 -35
  38. package/client/tailwind.config.js +78 -69
  39. package/dist/client.js +1 -1
  40. package/dist/cva.js +47 -0
  41. package/dist/index.html +1 -1
  42. package/dist/index.js +6 -6
  43. package/dist/react-router.js +7121 -5585
  44. package/dist/react.js +192 -149
  45. package/dist/style.css +2 -2
  46. package/lib/bin.js +2 -2
  47. package/lib/build.js +2 -2
  48. package/lib/index.d.ts +0 -3
  49. package/lib/index.js +160 -205
  50. package/lib/transform.d.ts +26 -0
  51. package/lib/transform.js +78 -0
  52. package/lib/websocket.d.ts +0 -1
  53. package/package.json +8 -7
  54. package/dist/radix-ui-themes.js +0 -9305
  55. package/lib/dev.d.ts +0 -18
  56. package/lib/dev.js +0 -87
@@ -1,6 +1,13 @@
1
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
2
  import { Info, AlertTriangle, XCircle, Circle, Trash2, RefreshCw, FileText, AlertCircle } from 'lucide-react'
3
+ import { Card, CardContent } from '../components/ui/card'
4
+ import { Badge } from '../components/ui/badge'
5
+ import { Button } from '../components/ui/button'
6
+ import { Alert, AlertDescription } from '../components/ui/alert'
7
+ import { Skeleton } from '../components/ui/skeleton'
8
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../components/ui/select'
9
+ import { Checkbox } from '../components/ui/checkbox'
10
+ import { cn } from '@zhin.js/client'
4
11
 
5
12
  interface LogEntry {
6
13
  level: 'info' | 'warn' | 'error'
@@ -28,10 +35,7 @@ export default function DashboardLogs() {
28
35
  useEffect(() => {
29
36
  fetchLogs()
30
37
  fetchStats()
31
- const interval = setInterval(() => {
32
- fetchLogs()
33
- fetchStats()
34
- }, 3000)
38
+ const interval = setInterval(() => { fetchLogs(); fetchStats() }, 3000)
35
39
  return () => clearInterval(interval)
36
40
  }, [levelFilter])
37
41
 
@@ -44,18 +48,11 @@ export default function DashboardLogs() {
44
48
 
45
49
  const fetchLogs = async () => {
46
50
  try {
47
- const url = levelFilter === 'all'
48
- ? '/api/logs?limit=100'
49
- : `/api/logs?limit=100&level=${levelFilter}`
50
-
51
+ const url = levelFilter === 'all' ? '/api/logs?limit=100' : `/api/logs?limit=100&level=${levelFilter}`
51
52
  const res = await fetch(url, { credentials: 'include' })
52
53
  if (!res.ok) throw new Error('API 请求失败')
53
-
54
54
  const data = await res.json()
55
- if (data.success && Array.isArray(data.data)) {
56
- setLogs(data.data.reverse())
57
- setError(null)
58
- }
55
+ if (data.success && Array.isArray(data.data)) { setLogs(data.data.reverse()); setError(null) }
59
56
  } catch (err) {
60
57
  setError((err as Error).message)
61
58
  } finally {
@@ -67,232 +64,164 @@ export default function DashboardLogs() {
67
64
  try {
68
65
  const res = await fetch('/api/logs/stats', { credentials: 'include' })
69
66
  if (!res.ok) return
70
-
71
67
  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
- }
68
+ if (data.success) setStats(data.data)
69
+ } catch (err) { console.error('Failed to fetch stats:', err) }
78
70
  }
79
71
 
80
72
  const handleCleanup = async (days?: number, maxRecords?: number) => {
81
- const message = days
82
- ? `确定清理 ${days} 天前的日志吗?`
83
- : `确定只保留最近 ${maxRecords} 条日志吗?`
84
-
73
+ const message = days ? `确定清理 ${days} 天前的日志吗?` : `确定只保留最近 ${maxRecords} 条日志吗?`
85
74
  if (!confirm(message)) return
86
-
87
75
  try {
88
76
  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'
77
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
78
+ body: JSON.stringify({ days, maxRecords }), credentials: 'include'
93
79
  })
94
-
95
80
  if (!res.ok) throw new Error('清理失败')
96
-
97
81
  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
- }
82
+ if (data.success) { alert(`成功清理 ${data.data.deletedCount} 条日志`); fetchLogs(); fetchStats() }
83
+ } catch (err) { alert((err as Error).message) }
115
84
  }
116
85
 
117
- const getLevelIcon = (level: string) => {
86
+ const getLevelStyle = (level: string) => {
118
87
  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} />
88
+ case 'info': return { border: 'border-l-blue-500', badge: 'default' as const, icon: <Info className="w-3 h-3" /> }
89
+ case 'warn': return { border: 'border-l-amber-500', badge: 'warning' as const, icon: <AlertTriangle className="w-3 h-3" /> }
90
+ case 'error': return { border: 'border-l-red-500', badge: 'destructive' as const, icon: <XCircle className="w-3 h-3" /> }
91
+ default: return { border: 'border-l-gray-500', badge: 'secondary' as const, icon: <Circle className="w-3 h-3" /> }
123
92
  }
124
93
  }
125
94
 
126
95
  if (loading) {
127
96
  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>
97
+ <div className="space-y-4">
98
+ <Skeleton className="h-8 w-48" />
99
+ <div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
100
+ {[...Array(4)].map((_, i) => <Skeleton key={i} className="h-20" />)}
101
+ </div>
102
+ <Skeleton className="h-96" />
103
+ </div>
134
104
  )
135
105
  }
136
106
 
137
107
  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
- {/* 日志统计 */}
108
+ <div className="space-y-4">
109
+ {/* Header */}
110
+ <div>
111
+ <h1 className="text-2xl font-bold tracking-tight">系统日志</h1>
112
+ <p className="text-sm text-muted-foreground">实时查看系统运行日志</p>
113
+ </div>
114
+
115
+ {/* Stats */}
146
116
  {stats && (
147
- <Grid columns={{ initial: '2', sm: '4' }} gap="3" mb="4">
117
+ <div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
148
118
  <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>
119
+ <CardContent className="flex flex-col items-center gap-1 p-4">
120
+ <span className="text-2xl font-bold">{stats.total}</span>
121
+ <span className="text-xs text-muted-foreground">总日志数</span>
122
+ </CardContent>
153
123
  </Card>
154
124
  <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>
125
+ <CardContent className="flex flex-col items-center gap-1 p-4">
126
+ <span className="text-2xl font-bold text-blue-600 dark:text-blue-400">{stats.byLevel.info || 0}</span>
127
+ <span className="text-xs text-muted-foreground">Info</span>
128
+ </CardContent>
159
129
  </Card>
160
130
  <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>
131
+ <CardContent className="flex flex-col items-center gap-1 p-4">
132
+ <span className="text-2xl font-bold text-amber-600 dark:text-amber-400">{stats.byLevel.warn || 0}</span>
133
+ <span className="text-xs text-muted-foreground">Warn</span>
134
+ </CardContent>
165
135
  </Card>
166
136
  <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>
137
+ <CardContent className="flex flex-col items-center gap-1 p-4">
138
+ <span className="text-2xl font-bold text-red-600 dark:text-red-400">{stats.byLevel.error || 0}</span>
139
+ <span className="text-xs text-muted-foreground">Error</span>
140
+ </CardContent>
171
141
  </Card>
172
- </Grid>
142
+ </div>
173
143
  )}
174
144
 
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}
145
+ {/* Toolbar */}
146
+ <Card>
147
+ <CardContent className="flex justify-between items-center p-3 flex-wrap gap-3">
148
+ <div className="flex items-center gap-3 flex-wrap">
149
+ <Select value={levelFilter} onValueChange={setLevelFilter}>
150
+ <SelectTrigger className="w-32">
151
+ <SelectValue placeholder="所有级别" />
152
+ </SelectTrigger>
153
+ <SelectContent>
154
+ <SelectItem value="all">所有级别</SelectItem>
155
+ <SelectItem value="info">Info</SelectItem>
156
+ <SelectItem value="warn">Warn</SelectItem>
157
+ <SelectItem value="error">Error</SelectItem>
158
+ </SelectContent>
159
+ </Select>
160
+
161
+ <div className="flex items-center gap-2">
162
+ <Checkbox
163
+ id="auto-scroll"
164
+ checked={autoScroll}
192
165
  onCheckedChange={(checked) => setAutoScroll(checked as boolean)}
193
166
  />
194
- <Text size="2">自动滚动</Text>
195
- </Flex>
196
- </Flex>
167
+ <label htmlFor="auto-scroll" className="text-sm cursor-pointer">自动滚动</label>
168
+ </div>
169
+ </div>
197
170
 
198
- <Flex gap="2">
199
- <Button
200
- variant="soft"
201
- onClick={() => handleCleanup(7)}
202
- size="2"
203
- >
204
- <Trash2 size={14} />
205
- 清理7天前
171
+ <div className="flex gap-2">
172
+ <Button variant="outline" size="sm" onClick={() => handleCleanup(7)}>
173
+ <Trash2 className="w-3.5 h-3.5 mr-1" />清理7天前
206
174
  </Button>
207
- <Button
208
- variant="soft"
209
- onClick={() => handleCleanup(undefined, 5000)}
210
- size="2"
211
- >
212
- <Trash2 size={14} />
213
- 保留5000条
175
+ <Button variant="outline" size="sm" onClick={() => handleCleanup(undefined, 5000)}>
176
+ <Trash2 className="w-3.5 h-3.5 mr-1" />保留5000条
214
177
  </Button>
215
- <Button
216
- variant="soft"
217
- onClick={() => fetchLogs()}
218
- size="2"
219
- >
220
- <RefreshCw size={14} />
221
- 刷新
178
+ <Button variant="outline" size="sm" onClick={() => fetchLogs()}>
179
+ <RefreshCw className="w-3.5 h-3.5 mr-1" />刷新
222
180
  </Button>
223
- </Flex>
224
- </Flex>
181
+ </div>
182
+ </CardContent>
225
183
  </Card>
226
184
 
227
- {/* 日志列表 */}
185
+ {/* Logs */}
228
186
  <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>
187
+ <CardContent className="p-4">
188
+ <div className="max-h-[600px] overflow-y-auto rounded-md bg-muted/30 p-2 space-y-1.5">
189
+ {error ? (
190
+ <Alert variant="destructive">
191
+ <AlertCircle className="h-4 w-4" />
192
+ <AlertDescription>加载失败: {error}</AlertDescription>
193
+ </Alert>
194
+ ) : logs.length === 0 ? (
195
+ <div className="flex flex-col items-center gap-3 py-12">
196
+ <FileText className="w-12 h-12 text-muted-foreground/30" />
197
+ <span className="text-sm text-muted-foreground">暂无日志</span>
198
+ </div>
199
+ ) : (
200
+ logs.map((log, index) => {
201
+ const style = getLevelStyle(log.level)
202
+ return (
203
+ <div
204
+ key={`${log.timestamp}-${index}`}
205
+ className={cn("p-3 rounded-md bg-background border-l-[3px]", style.border)}
206
+ >
207
+ <div className="flex items-center gap-2 mb-1">
208
+ <Badge variant={style.badge} className="gap-1 text-[10px] px-1.5 py-0">
209
+ {style.icon} {log.level.toUpperCase()}
270
210
  </Badge>
271
- <Text size="1" color="gray">
211
+ <span className="text-[11px] text-muted-foreground">
272
212
  {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>
213
+ </span>
214
+ {log.source && <Badge variant="outline" className="text-[10px] px-1.5 py-0">{log.source}</Badge>}
215
+ </div>
216
+ <p className="text-sm font-mono whitespace-pre-wrap break-words">{log.message}</p>
217
+ </div>
218
+ )
219
+ })
220
+ )}
221
+ <div ref={logsEndRef} />
222
+ </div>
223
+ </CardContent>
295
224
  </Card>
296
- </Box>
225
+ </div>
297
226
  )
298
227
  }