@zhin.js/console 1.0.21 → 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.
- package/CHANGELOG.md +11 -0
- package/README.md +4 -4
- package/client/components.json +17 -0
- package/client/index.html +1 -1
- package/client/src/components/PluginConfigForm/BasicFieldRenderers.tsx +89 -180
- package/client/src/components/PluginConfigForm/CollectionFieldRenderers.tsx +97 -200
- package/client/src/components/PluginConfigForm/CompositeFieldRenderers.tsx +31 -70
- package/client/src/components/PluginConfigForm/FieldRenderer.tsx +27 -77
- package/client/src/components/PluginConfigForm/NestedFieldRenderer.tsx +33 -53
- package/client/src/components/PluginConfigForm/index.tsx +71 -173
- package/client/src/components/ui/accordion.tsx +54 -0
- package/client/src/components/ui/alert.tsx +62 -0
- package/client/src/components/ui/avatar.tsx +41 -0
- package/client/src/components/ui/badge.tsx +32 -0
- package/client/src/components/ui/button.tsx +50 -0
- package/client/src/components/ui/card.tsx +50 -0
- package/client/src/components/ui/checkbox.tsx +25 -0
- package/client/src/components/ui/dialog.tsx +87 -0
- package/client/src/components/ui/dropdown-menu.tsx +97 -0
- package/client/src/components/ui/input.tsx +21 -0
- package/client/src/components/ui/scroll-area.tsx +43 -0
- package/client/src/components/ui/select.tsx +127 -0
- package/client/src/components/ui/separator.tsx +23 -0
- package/client/src/components/ui/skeleton.tsx +12 -0
- package/client/src/components/ui/switch.tsx +26 -0
- package/client/src/components/ui/tabs.tsx +52 -0
- package/client/src/components/ui/textarea.tsx +20 -0
- package/client/src/components/ui/tooltip.tsx +27 -0
- package/client/src/layouts/dashboard.tsx +91 -221
- package/client/src/main.tsx +38 -42
- package/client/src/pages/dashboard-bots.tsx +91 -137
- package/client/src/pages/dashboard-home.tsx +133 -204
- package/client/src/pages/dashboard-logs.tsx +125 -196
- package/client/src/pages/dashboard-plugin-detail.tsx +261 -329
- package/client/src/pages/dashboard-plugins.tsx +108 -105
- package/client/src/style.css +156 -865
- package/client/src/theme/index.ts +60 -35
- package/client/tailwind.config.js +78 -69
- package/dist/client.js +1 -1
- package/dist/cva.js +47 -0
- package/dist/index.html +1 -1
- package/dist/index.js +6 -6
- package/dist/react-router.js +7121 -5585
- package/dist/react.js +192 -149
- package/dist/style.css +2 -2
- package/lib/bin.js +2 -2
- package/lib/build.js +2 -2
- package/lib/index.d.ts +0 -3
- package/lib/index.js +160 -205
- package/lib/transform.d.ts +26 -0
- package/lib/transform.js +78 -0
- package/lib/websocket.d.ts +0 -1
- package/package.json +8 -7
- package/dist/radix-ui-themes.js +0 -9305
- package/lib/dev.d.ts +0 -18
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
86
|
+
const getLevelStyle = (level: string) => {
|
|
118
87
|
switch (level) {
|
|
119
|
-
case 'info': return <Info
|
|
120
|
-
case 'warn': return <AlertTriangle
|
|
121
|
-
case 'error': return <XCircle
|
|
122
|
-
default: return <Circle
|
|
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
|
-
<
|
|
129
|
-
<
|
|
130
|
-
|
|
131
|
-
<
|
|
132
|
-
</
|
|
133
|
-
|
|
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
|
-
<
|
|
139
|
-
{/*
|
|
140
|
-
<
|
|
141
|
-
<
|
|
142
|
-
<
|
|
143
|
-
</
|
|
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
|
-
<
|
|
117
|
+
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
|
148
118
|
<Card>
|
|
149
|
-
<
|
|
150
|
-
<
|
|
151
|
-
<
|
|
152
|
-
</
|
|
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
|
-
<
|
|
156
|
-
<
|
|
157
|
-
<
|
|
158
|
-
</
|
|
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
|
-
<
|
|
162
|
-
<
|
|
163
|
-
<
|
|
164
|
-
</
|
|
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
|
-
<
|
|
168
|
-
<
|
|
169
|
-
<
|
|
170
|
-
</
|
|
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
|
-
</
|
|
142
|
+
</div>
|
|
173
143
|
)}
|
|
174
144
|
|
|
175
|
-
{/*
|
|
176
|
-
<Card
|
|
177
|
-
<
|
|
178
|
-
<
|
|
179
|
-
<Select
|
|
180
|
-
<
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
<
|
|
185
|
-
<
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
<
|
|
195
|
-
</
|
|
196
|
-
</
|
|
167
|
+
<label htmlFor="auto-scroll" className="text-sm cursor-pointer">自动滚动</label>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
197
170
|
|
|
198
|
-
<
|
|
199
|
-
<Button
|
|
200
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
</
|
|
224
|
-
</
|
|
181
|
+
</div>
|
|
182
|
+
</CardContent>
|
|
225
183
|
</Card>
|
|
226
184
|
|
|
227
|
-
{/*
|
|
185
|
+
{/* Logs */}
|
|
228
186
|
<Card>
|
|
229
|
-
<
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
-
<
|
|
211
|
+
<span className="text-[11px] text-muted-foreground">
|
|
272
212
|
{new Date(log.timestamp).toLocaleString()}
|
|
273
|
-
</
|
|
274
|
-
{log.source &&
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
</
|
|
225
|
+
</div>
|
|
297
226
|
)
|
|
298
227
|
}
|