dsh-my-observability 0.3.0 → 0.3.2
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 +9 -79
- package/README.md +18 -76
- package/lib/ai.js +123 -99
- package/lib/audit-view.js +173 -172
- package/lib/audit.js +175 -166
- package/lib/client.js +1024 -1278
- package/lib/client.src.js +55 -29
- package/lib/constants.js +4 -4
- package/lib/diff.js +61 -56
- package/lib/git.js +97 -98
- package/lib/index.js +46 -47
- package/lib/parts/native-tabs.js +45 -0
- package/lib/resource-monitor.js +82 -134
- package/lib/resource-rules.js +9 -99
- package/lib/review.js +68 -77
- package/lib/routes.js +223 -200
- package/lib/store-persist.js +114 -111
- package/lib/store.js +215 -212
- package/package.json +15 -7
- package/lib/parts/git.js +0 -298
- package/lib/parts/i18n.js +0 -104
- package/lib/parts/replay-ext.js +0 -396
- package/lib/parts/replay.js +0 -386
- package/lib/parts/resource.js +0 -80
- package/lib/parts/styles.js +0 -156
package/lib/parts/replay.js
DELETED
|
@@ -1,386 +0,0 @@
|
|
|
1
|
-
// ── 轨迹回放面板(时间轴)──────────────────────────────────────────
|
|
2
|
-
// 拆分的审计视图组件(搜索/组合过滤/导出/统计/高亮/hook)位于
|
|
3
|
-
// replay-ext.js 片段(见 client.src.js 占位符顺序)。
|
|
4
|
-
|
|
5
|
-
/** 请求插件 API(非 2xx 抛错;返回响应 JSON 的 value 字段)。 */
|
|
6
|
-
function apiJson(path, options) {
|
|
7
|
-
return fetch(path, options).then(async (res) => {
|
|
8
|
-
const data = await res.json()
|
|
9
|
-
if (!res.ok) throw new Error(data.error?.message || `HTTP ${res.status}`)
|
|
10
|
-
return data.value
|
|
11
|
-
})
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/** 事件类型 → 中文标签。 */
|
|
15
|
-
function typeLabel(event) {
|
|
16
|
-
switch (event.type) {
|
|
17
|
-
case 'agent_status':
|
|
18
|
-
return strings.typeAgentStatus()
|
|
19
|
-
case 'llm_stream':
|
|
20
|
-
return strings.typeLlmStream()
|
|
21
|
-
case 'tool_call':
|
|
22
|
-
return strings.typeToolCall()
|
|
23
|
-
case 'tool_result':
|
|
24
|
-
return strings.typeToolResult()
|
|
25
|
-
case 'plugin_event':
|
|
26
|
-
return strings.typePluginEvent()
|
|
27
|
-
default:
|
|
28
|
-
return event.type
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** 事件类型 → 视觉类别(徽标/图标/节点共用,颜色语义一致):
|
|
33
|
-
* status=info / llm=warn / call=accent / result=success / fail=danger /
|
|
34
|
-
* plugin=info(插件事件复用 info 色,图标区分)。 */
|
|
35
|
-
function typeKind(event) {
|
|
36
|
-
if (event.type === 'agent_status') return 'status'
|
|
37
|
-
if (event.type === 'llm_stream') return 'llm'
|
|
38
|
-
if (event.type === 'tool_call') return 'call'
|
|
39
|
-
if (event.type === 'plugin_event') return 'plugin'
|
|
40
|
-
return event.data?.ok === false ? 'fail' : 'result'
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** 事件类型 → 类型图标(共享线性图标集,stroke=currentColor)。 */
|
|
44
|
-
function typeIcon(event) {
|
|
45
|
-
const kind = typeKind(event)
|
|
46
|
-
if (kind === 'status') return icon.clock(15)
|
|
47
|
-
if (kind === 'llm') return icon.file(15)
|
|
48
|
-
if (kind === 'call') return icon.external(15)
|
|
49
|
-
if (kind === 'plugin') return icon.alert(15)
|
|
50
|
-
if (kind === 'fail') return icon.close(15)
|
|
51
|
-
return icon.check(15)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** 时间戳 → HH:MM:SS。 */
|
|
55
|
-
function timeText(time) {
|
|
56
|
-
try {
|
|
57
|
-
const date = new Date(time)
|
|
58
|
-
const pad = (n) => String(n).padStart(2, '0')
|
|
59
|
-
return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
|
|
60
|
-
} catch {
|
|
61
|
-
return ''
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** agent 类型标记 → 中文。 */
|
|
66
|
-
function agentTypeText(agentType) {
|
|
67
|
-
if (agentType === 'top') return strings.agentTop()
|
|
68
|
-
if (agentType === 'subagent') return strings.agentSub()
|
|
69
|
-
return strings.agentUnknown()
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** 模型流阶段 → 中文。 */
|
|
73
|
-
function phaseText(phase) {
|
|
74
|
-
if (phase === 'start') return strings.phaseStart()
|
|
75
|
-
if (phase === 'end') return strings.phaseEnd()
|
|
76
|
-
if (phase === 'error') return strings.phaseError()
|
|
77
|
-
return phase
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/** agent 状态事件摘要。 */
|
|
81
|
-
function agentMeta(data) {
|
|
82
|
-
return `状态 ${data.status} · ${agentTypeText(data.agentType)}`
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/** 模型流事件摘要(开始/结束/错误 + 统计)。 */
|
|
86
|
-
function llmMeta(data) {
|
|
87
|
-
const stats = data.phase === 'start' ? '' : ` · ${data.chunks} chunks / ${data.chars} chars / ${data.ms}ms`
|
|
88
|
-
const error = data.message !== undefined ? `:${data.message}` : ''
|
|
89
|
-
return `${phaseText(data.phase)}${stats}${error}`
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/** 工具调用事件摘要(名称 + 参数摘要)。 */
|
|
93
|
-
function toolCallMeta(data) {
|
|
94
|
-
const args = data.args && data.args.summary !== undefined ? ` — ${data.args.summary}` : ''
|
|
95
|
-
return `${data.name}${args}`
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/** 工具结果事件摘要(名称 + 成败 + 耗时)。 */
|
|
99
|
-
function toolResultMeta(data) {
|
|
100
|
-
const result = data.ok === false ? strings.toolFail() : strings.toolOk()
|
|
101
|
-
return `${data.name} · ${result} · ${data.ms}ms`
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/** 插件事件摘要(插件名 · 事件名 · 动作;issue #154)。 */
|
|
105
|
-
function pluginEventMeta(data) {
|
|
106
|
-
const action = typeof data.action === 'string' && data.action !== '' ? data.action : data.event
|
|
107
|
-
return `${data.plugin} · ${data.event} · ${action}`
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/** 事件 → 摘要文本(单行,尽力而为)。 */
|
|
111
|
-
function eventMeta(event) {
|
|
112
|
-
const data = event.data || {}
|
|
113
|
-
if (event.type === 'agent_status') return agentMeta(data)
|
|
114
|
-
if (event.type === 'llm_stream') return llmMeta(data)
|
|
115
|
-
if (event.type === 'tool_call') return toolCallMeta(data)
|
|
116
|
-
if (event.type === 'tool_result') return toolResultMeta(data)
|
|
117
|
-
if (event.type === 'plugin_event') return pluginEventMeta(data)
|
|
118
|
-
return ''
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/** 插件事件详情行(原因 + 参数键值对;非插件事件返回 null 不可展开)。 */
|
|
122
|
-
function eventDetail(event) {
|
|
123
|
-
if (event?.type !== 'plugin_event') return null
|
|
124
|
-
const data = event.data || {}
|
|
125
|
-
const rows = []
|
|
126
|
-
if (typeof data.reason === 'string' && data.reason !== '') {
|
|
127
|
-
rows.push(
|
|
128
|
-
createElement(
|
|
129
|
-
'div',
|
|
130
|
-
{ key: 'reason', className: 'dsh-my-observability-detail-row' },
|
|
131
|
-
createElement('span', { className: 'dsh-my-observability-detail-key' }, strings.detailReason()),
|
|
132
|
-
createElement('span', { className: 'dsh-my-observability-detail-value' }, data.reason),
|
|
133
|
-
),
|
|
134
|
-
)
|
|
135
|
-
}
|
|
136
|
-
if (data.params !== null && typeof data.params === 'object') {
|
|
137
|
-
for (const [key, value] of Object.entries(data.params)) {
|
|
138
|
-
rows.push(
|
|
139
|
-
createElement(
|
|
140
|
-
'div',
|
|
141
|
-
{ key, className: 'dsh-my-observability-detail-row' },
|
|
142
|
-
createElement('span', { className: 'dsh-my-observability-detail-key' }, key),
|
|
143
|
-
createElement('span', { className: 'dsh-my-observability-detail-value' }, String(value)),
|
|
144
|
-
),
|
|
145
|
-
)
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
return rows.length > 0 ? rows : null
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/** 单条事件行:节点圆点 + 类型图标 + 徽标/时间 + 摘要(hover/active 反馈)。
|
|
152
|
-
* 摘要命中关键词时以 mark 高亮;插件事件可点击展开详情(原因/参数)。 */
|
|
153
|
-
function EventRow({ event, keyword }) {
|
|
154
|
-
const meta = eventMeta(event)
|
|
155
|
-
const kind = typeKind(event)
|
|
156
|
-
const detail = eventDetail(event)
|
|
157
|
-
const [open, setOpen] = useState(false)
|
|
158
|
-
return createElement(
|
|
159
|
-
'button',
|
|
160
|
-
{
|
|
161
|
-
className: 'dsh-my-observability-event',
|
|
162
|
-
type: 'button',
|
|
163
|
-
'aria-expanded': detail !== null ? open : undefined,
|
|
164
|
-
onClick: detail !== null ? () => setOpen(!open) : undefined,
|
|
165
|
-
},
|
|
166
|
-
createElement('span', { className: `dsh-my-observability-node dsh-my-observability-node-${kind}` }),
|
|
167
|
-
createElement(
|
|
168
|
-
'span',
|
|
169
|
-
{ className: `dsh-my-observability-event-icon dsh-my-observability-icon-${kind}` },
|
|
170
|
-
typeIcon(event),
|
|
171
|
-
),
|
|
172
|
-
createElement(
|
|
173
|
-
'span',
|
|
174
|
-
{ className: 'dsh-my-observability-event-body' },
|
|
175
|
-
createElement(
|
|
176
|
-
'span',
|
|
177
|
-
{ className: 'dsh-my-observability-event-head' },
|
|
178
|
-
createElement(
|
|
179
|
-
'span',
|
|
180
|
-
{ className: `dsh-my-observability-badge dsh-my-observability-badge-${kind}` },
|
|
181
|
-
typeLabel(event),
|
|
182
|
-
),
|
|
183
|
-
createElement('span', { className: 'dsh-my-observability-time' }, timeText(event.time)),
|
|
184
|
-
),
|
|
185
|
-
meta !== ''
|
|
186
|
-
? createElement(
|
|
187
|
-
'span',
|
|
188
|
-
{ className: 'dsh-my-observability-event-meta' },
|
|
189
|
-
createElement(HighlightText, { text: meta, keyword }),
|
|
190
|
-
)
|
|
191
|
-
: null,
|
|
192
|
-
detail !== null && open ? createElement('div', { className: 'dsh-my-observability-event-detail' }, detail) : null,
|
|
193
|
-
),
|
|
194
|
-
)
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/** 类型过滤按钮组(aria-pressed 选中态;plugin 过滤插件事件,issue #154)。 */
|
|
198
|
-
function TypeFilter({ filter, onFilter }) {
|
|
199
|
-
const options = [
|
|
200
|
-
['', strings.filterAll()],
|
|
201
|
-
['agent_status', strings.filterStatus()],
|
|
202
|
-
['llm_stream', strings.filterLlm()],
|
|
203
|
-
['tool', strings.filterTools()],
|
|
204
|
-
['plugin', strings.filterPlugin()],
|
|
205
|
-
]
|
|
206
|
-
return createElement(
|
|
207
|
-
'div',
|
|
208
|
-
{ className: 'dsh-my-observability-filters' },
|
|
209
|
-
options.map(([value, label]) =>
|
|
210
|
-
createElement(
|
|
211
|
-
'button',
|
|
212
|
-
{
|
|
213
|
-
key: value,
|
|
214
|
-
type: 'button',
|
|
215
|
-
className: `dsh-my-observability-chip${filter === value ? ' dsh-my-observability-chip-active' : ''}`,
|
|
216
|
-
'aria-pressed': filter === value,
|
|
217
|
-
onClick: () => onFilter(value),
|
|
218
|
-
},
|
|
219
|
-
label,
|
|
220
|
-
),
|
|
221
|
-
),
|
|
222
|
-
)
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/** 拉取会话列表与事件(选中为空时自动选当前/首个会话)。 */
|
|
226
|
-
async function loadReplayData(selected, currentSession, setters) {
|
|
227
|
-
try {
|
|
228
|
-
const list = await apiJson('/observability/api/sessions')
|
|
229
|
-
setters.setSessions(list)
|
|
230
|
-
if (selected === '' && list.length > 0) {
|
|
231
|
-
const preferred = list.some((s) => s.sessionId === currentSession) ? currentSession : list[0].sessionId
|
|
232
|
-
setters.setSelected(preferred)
|
|
233
|
-
return
|
|
234
|
-
}
|
|
235
|
-
const query =
|
|
236
|
-
selected !== ''
|
|
237
|
-
? `/observability/api/events?sessionId=${encodeURIComponent(selected)}&limit=300`
|
|
238
|
-
: '/observability/api/events?limit=0'
|
|
239
|
-
setters.setEvents(await apiJson(query))
|
|
240
|
-
setters.setError('')
|
|
241
|
-
} catch (err) {
|
|
242
|
-
setters.setError(err instanceof Error ? err.message : String(err))
|
|
243
|
-
} finally {
|
|
244
|
-
setters.setLoading(false)
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/** 下拉选项文案:可读标题(首条用户消息)优先,无标题回退 UUID 短显;
|
|
249
|
-
* 附加事件数与时间,用户一眼看出"哪个对话"。 */
|
|
250
|
-
function sessionOptionLabel(s) {
|
|
251
|
-
const title = typeof s.title === 'string' && s.title !== '' ? s.title : strings.sessionFallback(shortId(s.sessionId))
|
|
252
|
-
return `${title} · ${strings.eventCount(s.count)}`
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/** 会话 id 短显示(UUID 取前 8 位)。 */
|
|
256
|
-
function shortId(sessionId) {
|
|
257
|
-
return typeof sessionId === 'string' && sessionId.length > 8 ? `${sessionId.slice(0, 8)}…` : sessionId || ''
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/** 工具栏:会话选择 + 手动刷新 + 类型过滤。 */
|
|
261
|
-
function ReplayToolbar({ sessions, selected, onSelect, filter, onFilter, onRefresh }) {
|
|
262
|
-
return createElement(
|
|
263
|
-
'div',
|
|
264
|
-
{ className: 'dsh-my-observability-toolbar' },
|
|
265
|
-
createElement(
|
|
266
|
-
'div',
|
|
267
|
-
{ className: 'dsh-my-observability-toolbar-row' },
|
|
268
|
-
createElement(
|
|
269
|
-
'select',
|
|
270
|
-
{
|
|
271
|
-
className: 'dsh-my-observability-select',
|
|
272
|
-
value: selected,
|
|
273
|
-
disabled: sessions.length === 0,
|
|
274
|
-
onChange: (e) => onSelect(e.target.value),
|
|
275
|
-
},
|
|
276
|
-
sessions.length === 0
|
|
277
|
-
? createElement('option', { value: '' }, strings.allSessions())
|
|
278
|
-
: sessions.map((s) =>
|
|
279
|
-
createElement('option', { key: s.sessionId, value: s.sessionId }, sessionOptionLabel(s)),
|
|
280
|
-
),
|
|
281
|
-
),
|
|
282
|
-
createElement(
|
|
283
|
-
'button',
|
|
284
|
-
{
|
|
285
|
-
type: 'button',
|
|
286
|
-
className: 'dsh-my-observability-iconbtn',
|
|
287
|
-
'aria-label': strings.refresh(),
|
|
288
|
-
title: strings.refresh(),
|
|
289
|
-
onClick: onRefresh,
|
|
290
|
-
},
|
|
291
|
-
icon.refresh(15),
|
|
292
|
-
),
|
|
293
|
-
),
|
|
294
|
-
createElement(TypeFilter, { filter, onFilter }),
|
|
295
|
-
)
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/** 加载中状态(旋转刷新图标 + 次级色文案,不阻塞布局)。 */
|
|
299
|
-
function LoadingState() {
|
|
300
|
-
return createElement(
|
|
301
|
-
'div',
|
|
302
|
-
{ className: 'dsh-my-observability-state' },
|
|
303
|
-
icon.refresh(14),
|
|
304
|
-
createElement('span', null, strings.loading()),
|
|
305
|
-
)
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
/** 空状态(图标 + 主文案 + hint 两行结构)。 */
|
|
309
|
-
function EmptyState() {
|
|
310
|
-
return createElement(
|
|
311
|
-
'div',
|
|
312
|
-
{ className: 'dsh-my-observability-empty' },
|
|
313
|
-
createElement('span', { className: 'dsh-my-observability-empty-icon' }, icon.clock(20)),
|
|
314
|
-
createElement('span', null, strings.emptyEvents()),
|
|
315
|
-
createElement('span', { className: 'dsh-my-observability-empty-hint' }, strings.emptyEventsHint()),
|
|
316
|
-
)
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/** 错误状态(错误色文案 + 重试按钮)。 */
|
|
320
|
-
function ErrorState({ message, onRetry }) {
|
|
321
|
-
return createElement(
|
|
322
|
-
'div',
|
|
323
|
-
{ className: 'dsh-my-observability-error' },
|
|
324
|
-
createElement('span', { className: 'dsh-my-observability-error-text' }, `${strings.loadError()}:${message}`),
|
|
325
|
-
createElement(
|
|
326
|
-
'button',
|
|
327
|
-
{
|
|
328
|
-
type: 'button',
|
|
329
|
-
className: 'dsh-my-observability-iconbtn',
|
|
330
|
-
'aria-label': strings.retry(),
|
|
331
|
-
title: strings.retry(),
|
|
332
|
-
onClick: onRetry,
|
|
333
|
-
},
|
|
334
|
-
icon.refresh(15),
|
|
335
|
-
),
|
|
336
|
-
)
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/** 轨迹回放主面板:会话选择 + 类型过滤 + 搜索/组合过滤 + 导出 + 统计 + 时间轴。
|
|
340
|
-
* 状态与派生值集中在 replay-ext.js 的 useReplayState;本组件只拼装视图。
|
|
341
|
-
* 面板可见时轮询、隐藏时暂停。 */
|
|
342
|
-
function ReplayPanel(props) {
|
|
343
|
-
const s = useReplayState(props)
|
|
344
|
-
return createElement(
|
|
345
|
-
'div',
|
|
346
|
-
{ className: 'dsh-my-observability-panel' },
|
|
347
|
-
createElement(ResourcePanel, { resource: s.resource }),
|
|
348
|
-
createElement(ReplayToolbar, {
|
|
349
|
-
sessions: s.sessions,
|
|
350
|
-
selected: s.selected,
|
|
351
|
-
onSelect: s.setSelected,
|
|
352
|
-
filter: s.filter,
|
|
353
|
-
onFilter: s.setFilter,
|
|
354
|
-
onRefresh: s.retry,
|
|
355
|
-
}),
|
|
356
|
-
createElement(SearchFilterBar, {
|
|
357
|
-
keyword: s.keyword,
|
|
358
|
-
onKeyword: s.setKeyword,
|
|
359
|
-
timeStart: s.timeStart,
|
|
360
|
-
onTimeStart: s.setTimeStart,
|
|
361
|
-
timeEnd: s.timeEnd,
|
|
362
|
-
onTimeEnd: s.setTimeEnd,
|
|
363
|
-
result: s.result,
|
|
364
|
-
onResult: s.setResult,
|
|
365
|
-
onClear: s.clearFilters,
|
|
366
|
-
}),
|
|
367
|
-
createElement(ExportBar, {
|
|
368
|
-
scope: s.scope,
|
|
369
|
-
onScope: s.setScope,
|
|
370
|
-
onExportJson: () => void s.onExport('json'),
|
|
371
|
-
onExportCsv: () => void s.onExport('csv'),
|
|
372
|
-
showStats: s.showStats,
|
|
373
|
-
onToggleStats: () => s.setShowStats((value) => !value),
|
|
374
|
-
disabled: !s.canExport,
|
|
375
|
-
}),
|
|
376
|
-
s.error !== '' ? createElement(ErrorState, { message: s.error, onRetry: s.retry }) : null,
|
|
377
|
-
s.loading && s.error === '' ? createElement(LoadingState, null) : null,
|
|
378
|
-
!s.loading && s.error === '' && s.filtered.length === 0
|
|
379
|
-
? s.hasFilter
|
|
380
|
-
? createElement(NoMatchesState, null)
|
|
381
|
-
: createElement(EmptyState, null)
|
|
382
|
-
: null,
|
|
383
|
-
s.showStats ? createElement(StatsPanel, { events: s.filtered }) : null,
|
|
384
|
-
createElement('div', { className: 'dsh-my-observability-timeline' }, s.rows),
|
|
385
|
-
)
|
|
386
|
-
}
|
package/lib/parts/resource.js
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
// ── 资源监控区块(写放大/资源超限预警,见 lib/resource-monitor.js)──────
|
|
2
|
-
// 依赖 replay.js 先拼接(apiJson)与 i18n.js(strings)。纯函数声明文本。
|
|
3
|
-
|
|
4
|
-
const RESOURCE_POLL_MS = 15000
|
|
5
|
-
|
|
6
|
-
function fmtResourceBytes(bytes) {
|
|
7
|
-
if (!Number.isFinite(bytes)) return '-'
|
|
8
|
-
return `${(bytes / 1048576).toFixed(1)} MB`
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/** 资源采样状态:可见时每 15s 轮询 /observability/api/resources。 */
|
|
12
|
-
function useResourceState(visible) {
|
|
13
|
-
const [resource, setResource] = useState(null)
|
|
14
|
-
useEffect(() => {
|
|
15
|
-
if (!visible) return undefined
|
|
16
|
-
let alive = true
|
|
17
|
-
const tick = () => {
|
|
18
|
-
if (alive)
|
|
19
|
-
apiJson('/observability/api/resources')
|
|
20
|
-
.then(setResource)
|
|
21
|
-
.catch(() => {})
|
|
22
|
-
}
|
|
23
|
-
tick()
|
|
24
|
-
const timer = setInterval(tick, RESOURCE_POLL_MS)
|
|
25
|
-
return () => {
|
|
26
|
-
alive = false
|
|
27
|
-
clearInterval(timer)
|
|
28
|
-
}
|
|
29
|
-
}, [visible])
|
|
30
|
-
return resource
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function ResourceMetric({ label, value }) {
|
|
34
|
-
return createElement(
|
|
35
|
-
'div',
|
|
36
|
-
{ className: 'dsh-my-observability-resource-metric' },
|
|
37
|
-
createElement('span', { className: 'dsh-my-observability-resource-label' }, label),
|
|
38
|
-
createElement('span', { className: 'dsh-my-observability-resource-value' }, value),
|
|
39
|
-
)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** 资源面板:四指标 + 告警列表(write-rate/file-size level=error 红色,cpu/memory warn 黄色)。 */
|
|
43
|
-
function ResourcePanel({ resource }) {
|
|
44
|
-
if (resource === null || resource === undefined) {
|
|
45
|
-
return createElement('div', { className: 'dsh-my-observability-resource' }, strings.resourceLoading())
|
|
46
|
-
}
|
|
47
|
-
const alerts = Array.isArray(resource.alerts) ? resource.alerts : []
|
|
48
|
-
return createElement(
|
|
49
|
-
'div',
|
|
50
|
-
{ className: 'dsh-my-observability-resource' },
|
|
51
|
-
createElement('div', { className: 'dsh-my-observability-resource-head' }, strings.resourceTitle()),
|
|
52
|
-
createElement(
|
|
53
|
-
'div',
|
|
54
|
-
{ className: 'dsh-my-observability-resource-grid' },
|
|
55
|
-
createElement(ResourceMetric, { label: strings.resourceFile(), value: fmtResourceBytes(resource.fileBytes) }),
|
|
56
|
-
createElement(ResourceMetric, {
|
|
57
|
-
label: strings.resourceRate(),
|
|
58
|
-
value: `${fmtResourceBytes(resource.writeRateBytesPerHour)}/h`,
|
|
59
|
-
}),
|
|
60
|
-
createElement(ResourceMetric, {
|
|
61
|
-
label: strings.resourceCpu(),
|
|
62
|
-
value: `${Math.round(resource.cpuPercent ?? 0)}%`,
|
|
63
|
-
}),
|
|
64
|
-
createElement(ResourceMetric, { label: strings.resourceMem(), value: fmtResourceBytes(resource.memoryBytes) }),
|
|
65
|
-
),
|
|
66
|
-
alerts.length > 0
|
|
67
|
-
? createElement(
|
|
68
|
-
'div',
|
|
69
|
-
{ className: 'dsh-my-observability-resource-alerts' },
|
|
70
|
-
alerts.map((alert) =>
|
|
71
|
-
createElement(
|
|
72
|
-
'div',
|
|
73
|
-
{ className: `dsh-my-observability-resource-alert dsh-my-observability-resource-alert-${alert.level}` },
|
|
74
|
-
alert.message,
|
|
75
|
-
),
|
|
76
|
-
),
|
|
77
|
-
)
|
|
78
|
-
: null,
|
|
79
|
-
)
|
|
80
|
-
}
|
package/lib/parts/styles.js
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
// ── 样式(DSH 语义 token,随 activation 注入 / teardown 卸载)──────
|
|
2
|
-
// 前缀 dsh-my-observability-(issue #54:与 dsh-my-guard 前缀分离,消除跨插件类名冲突)。
|
|
3
|
-
const STYLES = `
|
|
4
|
-
.dsh-my-observability-panel{display:flex;flex-direction:column;gap:10px;padding:2px 6px 8px;color:var(--dsw-alias-label-primary);font:var(--dsw-font-s-14)}
|
|
5
|
-
.dsh-my-observability-toolbar{display:flex;flex-direction:column;gap:8px}
|
|
6
|
-
.dsh-my-observability-toolbar-row{display:flex;align-items:center;gap:6px}
|
|
7
|
-
.dsh-my-observability-select{flex:1;min-width:0;font:var(--dsw-font-xxs-12);color:var(--dsw-alias-label-primary);
|
|
8
|
-
background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:4px 8px}
|
|
9
|
-
.dsh-my-observability-select:disabled{opacity:.4;cursor:default}
|
|
10
|
-
.dsh-my-observability-input{flex:1;min-width:0;font:var(--dsw-font-xxs-12);color:var(--dsw-alias-label-primary);
|
|
11
|
-
background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:4px 8px}
|
|
12
|
-
.dsh-my-observability-input::placeholder{color:var(--dsw-alias-label-tertiary)}
|
|
13
|
-
.dsh-my-observability-repo-row{display:flex;gap:8px;align-items:center}
|
|
14
|
-
.dsh-my-observability-repo-input{flex:1}
|
|
15
|
-
.dsh-my-observability-iconbtn{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;
|
|
16
|
-
border:none;border-radius:50%;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;flex:none;
|
|
17
|
-
transition:background var(--ds-transition-duration-slow) var(--ds-ease-in-out), color var(--ds-transition-duration-slow) var(--ds-ease-in-out)}
|
|
18
|
-
.dsh-my-observability-iconbtn svg{display:block}
|
|
19
|
-
.dsh-my-observability-iconbtn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
20
|
-
.dsh-my-observability-iconbtn:disabled{opacity:.4;cursor:default}
|
|
21
|
-
.dsh-my-observability-filters{display:flex;gap:6px;flex-wrap:wrap}
|
|
22
|
-
.dsh-my-observability-chip{font:var(--dsw-font-xxs-12);color:var(--dsw-alias-label-secondary);background:transparent;
|
|
23
|
-
border:1px solid var(--dsw-alias-border-l2);border-radius:999px;padding:2px 10px;cursor:pointer;
|
|
24
|
-
transition:background var(--ds-transition-duration-slow) var(--ds-ease-in-out), color var(--ds-transition-duration-slow) var(--ds-ease-in-out), border-color var(--ds-transition-duration-slow) var(--ds-ease-in-out)}
|
|
25
|
-
.dsh-my-observability-chip:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
26
|
-
.dsh-my-observability-chip-active{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-interactive-primary);
|
|
27
|
-
background:color-mix(in srgb, var(--dsw-alias-interactive-primary) 12%, transparent)}
|
|
28
|
-
/* ── 时间轴:左侧竖线 + 类型色节点圆点 + 类型图标行 ── */
|
|
29
|
-
.dsh-my-observability-timeline{display:flex;flex-direction:column;gap:2px;max-height:calc(100vh - 240px);overflow-y:auto;
|
|
30
|
-
padding-left:14px;position:relative}
|
|
31
|
-
.dsh-my-observability-timeline::before{content:'';position:absolute;left:5px;top:8px;bottom:8px;width:2px;border-radius:1px;
|
|
32
|
-
background:var(--dsw-alias-border-l2)}
|
|
33
|
-
.dsh-my-observability-event{position:relative;display:flex;align-items:flex-start;gap:8px;box-sizing:border-box;width:100%;
|
|
34
|
-
margin:0;padding:5px 8px 5px 0;border:none;background:transparent;border-radius:8px;cursor:pointer;text-align:left;
|
|
35
|
-
font:var(--dsw-font-s-14);color:var(--dsw-alias-label-primary);
|
|
36
|
-
animation:dsh-my-observability-row-in 150ms var(--ds-ease-in-out);
|
|
37
|
-
transition:background var(--ds-transition-duration-slow) var(--ds-ease-in-out)}
|
|
38
|
-
.dsh-my-observability-event:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
39
|
-
.dsh-my-observability-event:active{background:color-mix(in srgb, var(--dsw-alias-interactive-bg-hover) 55%, transparent)}
|
|
40
|
-
.dsh-my-observability-node{position:absolute;left:-14px;top:50%;transform:translateY(-50%);width:12px;height:12px;flex:none;
|
|
41
|
-
box-sizing:border-box;border-radius:50%;background:var(--dsw-alias-bg-layer-2);border:2px solid var(--dsw-alias-label-tertiary)}
|
|
42
|
-
.dsh-my-observability-node-status{border-color:var(--dsw-alias-state-info-primary)}
|
|
43
|
-
.dsh-my-observability-node-llm{border-color:var(--dsw-alias-state-warn-primary)}
|
|
44
|
-
.dsh-my-observability-node-call{border-color:var(--dsw-alias-accent)}
|
|
45
|
-
.dsh-my-observability-node-plugin{border-color:var(--dsw-alias-state-info-primary)}
|
|
46
|
-
.dsh-my-observability-node-result{border-color:var(--dsw-alias-state-success-primary)}
|
|
47
|
-
.dsh-my-observability-node-fail{border-color:var(--dsw-alias-state-error-primary)}
|
|
48
|
-
.dsh-my-observability-event-icon{flex:none;display:flex;align-items:center;margin-top:1px}
|
|
49
|
-
.dsh-my-observability-icon-status{color:var(--dsw-alias-state-info-primary)}
|
|
50
|
-
.dsh-my-observability-icon-llm{color:var(--dsw-alias-state-warn-primary)}
|
|
51
|
-
.dsh-my-observability-icon-call{color:var(--dsw-alias-accent)}
|
|
52
|
-
.dsh-my-observability-icon-plugin{color:var(--dsw-alias-state-info-primary)}
|
|
53
|
-
.dsh-my-observability-icon-result{color:var(--dsw-alias-state-success-primary)}
|
|
54
|
-
.dsh-my-observability-icon-fail{color:var(--dsw-alias-state-error-primary)}
|
|
55
|
-
.dsh-my-observability-event-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
|
|
56
|
-
.dsh-my-observability-event-head{display:flex;align-items:center;gap:8px;justify-content:space-between}
|
|
57
|
-
.dsh-my-observability-badge{flex:none;font:var(--dsw-font-xxxs-strong-11);border-radius:4px;padding:1px 6px}
|
|
58
|
-
.dsh-my-observability-badge-status{color:var(--dsw-alias-state-info-primary);background:color-mix(in srgb, var(--dsw-alias-state-info-primary) 14%, transparent)}
|
|
59
|
-
.dsh-my-observability-badge-llm{color:var(--dsw-alias-state-warn-primary);background:color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent)}
|
|
60
|
-
.dsh-my-observability-badge-call{color:var(--dsw-alias-accent);background:color-mix(in srgb, var(--dsw-alias-accent) 12%, transparent)}
|
|
61
|
-
.dsh-my-observability-badge-plugin{color:var(--dsw-alias-state-info-primary);background:color-mix(in srgb, var(--dsw-alias-state-info-primary) 14%, transparent)}
|
|
62
|
-
.dsh-my-observability-badge-result{color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 14%, transparent)}
|
|
63
|
-
.dsh-my-observability-badge-fail{color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 14%, transparent)}
|
|
64
|
-
.dsh-my-observability-time{flex:none;font:var(--dsw-font-xxxs-11);color:var(--dsw-alias-label-tertiary);white-space:nowrap}
|
|
65
|
-
.dsh-my-observability-event-meta{font:var(--dsw-font-xxs-12);color:var(--dsw-alias-label-secondary);line-height:1.6;word-break:break-word}
|
|
66
|
-
/* ── 插件事件详情展开(issue #154:原因/参数可查)── */
|
|
67
|
-
.dsh-my-observability-event-detail{display:flex;flex-direction:column;gap:2px;margin-top:4px;padding:6px 8px;
|
|
68
|
-
border:1px solid var(--dsw-alias-border-l2);border-radius:6px;background:var(--dsw-alias-bg-layer-1)}
|
|
69
|
-
.dsh-my-observability-detail-row{display:flex;gap:8px;font:var(--dsw-font-xxs-12);line-height:1.5;word-break:break-word}
|
|
70
|
-
.dsh-my-observability-detail-key{flex:none;font:var(--dsw-font-xxxs-strong-11);color:var(--dsw-alias-label-tertiary);min-width:56px}
|
|
71
|
-
.dsh-my-observability-detail-value{color:var(--dsw-alias-label-primary)}
|
|
72
|
-
/* ── 状态区:loading / 空 / 错误 ── */
|
|
73
|
-
.dsh-my-observability-state{display:flex;align-items:center;gap:6px;padding:8px 6px;font:var(--dsw-font-xxs-12);color:var(--dsw-alias-label-tertiary)}
|
|
74
|
-
.dsh-my-observability-state svg{flex:none;animation:dsh-my-observability-spin 1s linear infinite}
|
|
75
|
-
.dsh-my-observability-empty{display:flex;flex-direction:column;align-items:center;gap:4px;padding:16px 8px;text-align:center;
|
|
76
|
-
font:var(--dsw-font-xxs-12);color:var(--dsw-alias-label-tertiary);line-height:1.7}
|
|
77
|
-
.dsh-my-observability-empty-icon{display:flex;color:var(--dsw-alias-label-tertiary)}
|
|
78
|
-
.dsh-my-observability-empty-hint{display:block;color:var(--dsw-alias-label-dimmed);font:var(--dsw-font-xxxs-11)}
|
|
79
|
-
.dsh-my-observability-error{display:flex;align-items:center;gap:6px;padding:8px 6px;font:var(--dsw-font-xxs-12);
|
|
80
|
-
color:var(--dsw-alias-state-error-primary);white-space:pre-wrap;word-break:break-all;line-height:1.7}
|
|
81
|
-
.dsh-my-observability-error-text{flex:1;min-width:0}
|
|
82
|
-
@keyframes dsh-my-observability-row-in{from{opacity:0;transform:translateY(1px)}to{opacity:1;transform:none}}
|
|
83
|
-
@keyframes dsh-my-observability-spin{to{transform:rotate(360deg)}}
|
|
84
|
-
/* ── 审计视图:搜索 / 组合过滤 / 导出 / 统计 / 高亮 ── */
|
|
85
|
-
.dsh-my-observability-search-row{display:flex;align-items:center;gap:6px}
|
|
86
|
-
.dsh-my-observability-search-row .dsh-my-observability-input{flex:1}
|
|
87
|
-
.dsh-my-observability-time-row{display:flex;align-items:center;gap:6px}
|
|
88
|
-
.dsh-my-observability-time-label{flex:none;font:var(--dsw-font-xxxs-11);color:var(--dsw-alias-label-tertiary)}
|
|
89
|
-
.dsh-my-observability-time-input{flex:1;min-width:0}
|
|
90
|
-
.dsh-my-observability-mark{background:color-mix(in srgb, var(--dsw-alias-state-warn-primary) 30%, transparent);
|
|
91
|
-
color:var(--dsw-alias-label-primary);border-radius:2px;padding:0 1px}
|
|
92
|
-
.dsh-my-observability-export{display:flex;flex-direction:column;gap:6px}
|
|
93
|
-
.dsh-my-observability-stats{display:flex;flex-direction:column;gap:6px;border:1px solid var(--dsw-alias-border-l2);
|
|
94
|
-
border-radius:6px;padding:8px}
|
|
95
|
-
.dsh-my-observability-stats-title{font:var(--dsw-font-xs-strong-13);color:var(--dsw-alias-label-primary)}
|
|
96
|
-
.dsh-my-observability-stats-table{width:100%;border-collapse:collapse;font:var(--dsw-font-xxs-12)}
|
|
97
|
-
.dsh-my-observability-stats-table th,.dsh-my-observability-stats-table td{text-align:left;padding:3px 6px;
|
|
98
|
-
border-bottom:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary)}
|
|
99
|
-
.dsh-my-observability-stats-table th{font:var(--dsw-font-xxxs-strong-11);color:var(--dsw-alias-label-tertiary)}
|
|
100
|
-
.dsh-my-observability-stats-empty{font:var(--dsw-font-xxs-12);color:var(--dsw-alias-label-tertiary);padding:6px 2px}
|
|
101
|
-
/* ── Git 面板 ── */
|
|
102
|
-
.dsh-my-observability-status{font:var(--dsw-font-xxs-strong-12);color:var(--dsw-alias-label-secondary)}
|
|
103
|
-
.dsh-my-observability-actions{display:flex;gap:8px;flex-wrap:wrap}
|
|
104
|
-
.dsh-my-observability-btn{font:var(--dsw-font-xxs-strong-12);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);
|
|
105
|
-
border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:4px 12px;cursor:pointer;
|
|
106
|
-
transition:background var(--ds-transition-duration-slow) var(--ds-ease-in-out), color var(--ds-transition-duration-slow) var(--ds-ease-in-out), border-color var(--ds-transition-duration-slow) var(--ds-ease-in-out)}
|
|
107
|
-
.dsh-my-observability-btn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
|
|
108
|
-
.dsh-my-observability-btn:disabled{opacity:.5;cursor:default}
|
|
109
|
-
.dsh-my-observability-btn-primary{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-interactive-primary);
|
|
110
|
-
background:color-mix(in srgb, var(--dsw-alias-interactive-primary) 16%, transparent)}
|
|
111
|
-
.dsh-my-observability-section{display:flex;flex-direction:column;gap:6px;border-top:1px solid var(--dsw-alias-border-l2);padding-top:8px}
|
|
112
|
-
.dsh-my-observability-section-title{font:var(--dsw-font-xs-strong-13);color:var(--dsw-alias-label-primary)}
|
|
113
|
-
.dsh-my-observability-diff{max-height:240px;overflow:auto;font:var(--dsw-font-mono-xxs);font-size:11px;line-height:1.5;
|
|
114
|
-
color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);
|
|
115
|
-
border-radius:6px;padding:8px;white-space:pre-wrap;word-break:break-all}
|
|
116
|
-
.dsh-my-observability-form{display:flex;flex-direction:column;gap:6px}
|
|
117
|
-
.dsh-my-observability-type{flex:none;width:96px}
|
|
118
|
-
.dsh-my-observability-textarea{min-height:52px;resize:vertical;font:var(--dsw-font-xxs-12)}
|
|
119
|
-
.dsh-my-observability-feedback{font:var(--dsw-font-xxs-12);word-break:break-all;line-height:1.5}
|
|
120
|
-
.dsh-my-observability-feedback-ok{color:var(--dsw-alias-state-success-primary)}
|
|
121
|
-
.dsh-my-observability-feedback-error{color:var(--dsw-alias-state-error-primary)}
|
|
122
|
-
.dsh-my-observability-issue{display:flex;flex-direction:column;gap:2px;border-radius:6px;padding:6px 8px;font:var(--dsw-font-xxs-12)}
|
|
123
|
-
.dsh-my-observability-issue-error{background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent)}
|
|
124
|
-
.dsh-my-observability-issue-warning{background:color-mix(in srgb, var(--dsw-alias-state-warn-primary) 12%, transparent)}
|
|
125
|
-
.dsh-my-observability-issue-info{background:color-mix(in srgb, var(--dsw-alias-state-info-primary) 10%, transparent)}
|
|
126
|
-
.dsh-my-observability-issue-sev{font:var(--dsw-font-xxxs-strong-11);text-transform:uppercase}
|
|
127
|
-
.dsh-my-observability-issue-error .dsh-my-observability-issue-sev{color:var(--dsw-alias-state-error-primary)}
|
|
128
|
-
.dsh-my-observability-issue-warning .dsh-my-observability-issue-sev{color:var(--dsw-alias-state-warn-primary)}
|
|
129
|
-
.dsh-my-observability-issue-info .dsh-my-observability-issue-sev{color:var(--dsw-alias-state-info-primary)}
|
|
130
|
-
.dsh-my-observability-issue-rule{font:var(--dsw-font-mono-xxs);font-size:11px;color:var(--dsw-alias-label-secondary)}
|
|
131
|
-
.dsh-my-observability-issue-msg{color:var(--dsw-alias-label-primary);line-height:1.5}
|
|
132
|
-
.dsh-my-observability-review-ok{font:var(--dsw-font-xxs-strong-12);color:var(--dsw-alias-state-success-primary)}
|
|
133
|
-
.dsh-my-observability-ai{font:var(--dsw-font-xxs-12);color:var(--dsw-alias-label-secondary);line-height:1.5;
|
|
134
|
-
border:1px dashed var(--dsw-alias-border-l2);border-radius:6px;padding:6px 8px}
|
|
135
|
-
.dsh-my-observability-resource{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:8px;margin:0 0 8px}
|
|
136
|
-
.dsh-my-observability-resource-head{font:var(--dsw-font-xxs-strong-12);color:var(--dsw-alias-label-primary);margin-bottom:6px}
|
|
137
|
-
.dsh-my-observability-resource-grid{display:grid;grid-template-columns:1fr 1fr;gap:4px 10px}
|
|
138
|
-
.dsh-my-observability-resource-metric{display:flex;justify-content:space-between;gap:8px;font:var(--dsw-font-xxs-12)}
|
|
139
|
-
.dsh-my-observability-resource-label{color:var(--dsw-alias-label-secondary)}
|
|
140
|
-
.dsh-my-observability-resource-value{color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-mono-xxs)}
|
|
141
|
-
.dsh-my-observability-resource-alerts{margin-top:6px;display:flex;flex-direction:column;gap:4px}
|
|
142
|
-
.dsh-my-observability-resource-alert{font:var(--dsw-font-xxxs-11);border-radius:4px;padding:2px 6px}
|
|
143
|
-
.dsh-my-observability-resource-alert-error{color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 12%,transparent)}
|
|
144
|
-
.dsh-my-observability-resource-alert-warn{color:var(--dsw-alias-state-warn-primary);background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 12%,transparent)}
|
|
145
|
-
`
|
|
146
|
-
|
|
147
|
-
function injectStyles() {
|
|
148
|
-
if (typeof document === 'undefined' || typeof document.head === 'undefined') return () => {}
|
|
149
|
-
const style = document.createElement('style')
|
|
150
|
-
style.setAttribute('data-dsh-my-observability', 'styles')
|
|
151
|
-
style.textContent = STYLES
|
|
152
|
-
document.head.appendChild(style)
|
|
153
|
-
return () => {
|
|
154
|
-
if (style.parentNode !== null) style.parentNode.removeChild(style)
|
|
155
|
-
}
|
|
156
|
-
}
|