@mapick/cost-firewall 0.2.24 → 0.2.26
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 +2 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +41 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/dashboard/html.d.ts.map +1 -1
- package/dist/dashboard/html.js +419 -163
- package/dist/dashboard/html.js.map +1 -1
- package/dist/dashboard/index.d.ts.map +1 -1
- package/dist/dashboard/index.js +63 -0
- package/dist/dashboard/index.js.map +1 -1
- package/dist/hooks/before-agent-reply.d.ts.map +1 -1
- package/dist/hooks/before-agent-reply.js +1 -0
- package/dist/hooks/before-agent-reply.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/state.d.ts +7 -0
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +35 -1
- package/dist/state.js.map +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +12 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/openclaw.plugin.json +8 -1
- package/package.json +1 -1
package/dist/dashboard/html.js
CHANGED
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
export function renderDashboardHtml(_stats) {
|
|
2
2
|
return `<!DOCTYPE html>
|
|
3
|
-
<html lang="
|
|
3
|
+
<html lang="zh-CN">
|
|
4
4
|
<head>
|
|
5
5
|
<meta charset="UTF-8">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
-
<title>Firewall</title>
|
|
7
|
+
<title>Mapick Firewall</title>
|
|
8
|
+
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
8
9
|
<style>
|
|
9
10
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
10
11
|
:root {
|
|
11
|
-
--bg: #
|
|
12
|
-
--fg: #
|
|
13
|
-
--muted: #
|
|
14
|
-
--dim: #
|
|
15
|
-
--border: #
|
|
16
|
-
--card: #
|
|
12
|
+
--bg: #f8f9fa;
|
|
13
|
+
--fg: #1a1a2e;
|
|
14
|
+
--muted: #6b7280;
|
|
15
|
+
--dim: #9ca3af;
|
|
16
|
+
--border: #e5e7eb;
|
|
17
|
+
--card: #ffffff;
|
|
17
18
|
--accent: #2563eb;
|
|
18
19
|
--accent-hover: #1d4ed8;
|
|
19
20
|
--destructive: #dc2626;
|
|
20
21
|
--destructive-hover: #b91c1c;
|
|
22
|
+
--destructive-glow: rgba(220,38,38,0.3);
|
|
21
23
|
--success: #16a34a;
|
|
22
24
|
--warning: #ca8a04;
|
|
25
|
+
--mono: 'JetBrains Mono', monospace;
|
|
23
26
|
}
|
|
24
27
|
body {
|
|
25
28
|
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI', Roboto, sans-serif;
|
|
@@ -46,14 +49,6 @@ export function renderDashboardHtml(_stats) {
|
|
|
46
49
|
font-weight: 600;
|
|
47
50
|
letter-spacing: -0.01em;
|
|
48
51
|
}
|
|
49
|
-
.header-center {
|
|
50
|
-
display: flex;
|
|
51
|
-
gap: 0;
|
|
52
|
-
background: var(--bg);
|
|
53
|
-
border-radius: 6px;
|
|
54
|
-
padding: 2px;
|
|
55
|
-
border: 1px solid var(--border);
|
|
56
|
-
}
|
|
57
52
|
.mode-toggle {
|
|
58
53
|
display: inline-flex;
|
|
59
54
|
background: #f1f5f9;
|
|
@@ -71,20 +66,21 @@ export function renderDashboardHtml(_stats) {
|
|
|
71
66
|
cursor: pointer;
|
|
72
67
|
border-radius: 6px;
|
|
73
68
|
transition: all 0.2s ease;
|
|
74
|
-
position: relative;
|
|
75
|
-
}
|
|
76
|
-
.mode-btn:hover {
|
|
77
|
-
color: #334155;
|
|
78
69
|
}
|
|
70
|
+
.mode-btn:hover { color: #334155; }
|
|
79
71
|
.mode-btn.active {
|
|
80
72
|
background: #fff;
|
|
81
73
|
color: #1e293b;
|
|
82
|
-
box-shadow: 0 1px 3px rgba(0,0,0,0.1)
|
|
74
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
83
75
|
font-weight: 600;
|
|
84
76
|
}
|
|
77
|
+
.mode-btn.observe-mode.active {
|
|
78
|
+
background: #dbeafe;
|
|
79
|
+
color: #1d4ed8;
|
|
80
|
+
}
|
|
85
81
|
.mode-btn.protect-mode.active {
|
|
86
|
-
background: #
|
|
87
|
-
color: #
|
|
82
|
+
background: #dcfce7;
|
|
83
|
+
color: #16a34a;
|
|
88
84
|
}
|
|
89
85
|
.mode-label {
|
|
90
86
|
font-size: 11px;
|
|
@@ -129,54 +125,113 @@ export function renderDashboardHtml(_stats) {
|
|
|
129
125
|
background: var(--accent-hover);
|
|
130
126
|
border-color: var(--accent-hover);
|
|
131
127
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
128
|
+
.btn-emergency {
|
|
129
|
+
width: 64px;
|
|
130
|
+
height: 64px;
|
|
131
|
+
border: none;
|
|
132
|
+
border-radius: 50%;
|
|
133
|
+
font-size: 28px;
|
|
134
|
+
font-weight: 700;
|
|
135
|
+
cursor: pointer;
|
|
136
|
+
background: var(--destructive);
|
|
137
|
+
color: #fff;
|
|
138
|
+
display: flex;
|
|
139
|
+
align-items: center;
|
|
140
|
+
justify-content: center;
|
|
141
|
+
transition: all 0.2s ease;
|
|
142
|
+
animation: pulse-stop 2s infinite;
|
|
143
|
+
box-shadow: 0 4px 24px var(--destructive-glow);
|
|
138
144
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
145
|
+
.btn-emergency:hover {
|
|
146
|
+
background: var(--destructive-hover);
|
|
147
|
+
transform: scale(1.08);
|
|
148
|
+
box-shadow: 0 0 48px rgba(220,38,38,0.5), 0 4px 20px rgba(220,38,38,0.4);
|
|
149
|
+
}
|
|
150
|
+
.btn-emergency.stopped {
|
|
151
|
+
background: #52525b;
|
|
152
|
+
animation: none;
|
|
153
|
+
color: #a1a1aa;
|
|
154
|
+
box-shadow: none;
|
|
155
|
+
}
|
|
156
|
+
@keyframes pulse-stop {
|
|
157
|
+
0%, 100% { box-shadow: 0 0 0 0 rgba(220,38,38,0.35), 0 0 32px rgba(220,38,38,0.25); }
|
|
158
|
+
50% { box-shadow: 0 0 0 18px rgba(220,38,38,0), 0 0 48px rgba(220,38,38,0.4); }
|
|
159
|
+
}
|
|
160
|
+
.hero-stats {
|
|
142
161
|
display: grid;
|
|
143
162
|
grid-template-columns: repeat(4, 1fr);
|
|
144
|
-
gap:
|
|
145
|
-
|
|
163
|
+
gap: 12px;
|
|
164
|
+
padding: 16px 24px;
|
|
165
|
+
max-width: 1200px;
|
|
166
|
+
margin: 0 auto;
|
|
146
167
|
}
|
|
147
|
-
|
|
148
|
-
|
|
168
|
+
.hero-card {
|
|
169
|
+
background: var(--card);
|
|
170
|
+
border: 1px solid var(--border);
|
|
171
|
+
border-radius: 10px;
|
|
172
|
+
padding: 16px;
|
|
173
|
+
text-align: center;
|
|
174
|
+
display: flex;
|
|
175
|
+
flex-direction: column;
|
|
176
|
+
align-items: center;
|
|
177
|
+
justify-content: center;
|
|
178
|
+
min-height: 100px;
|
|
149
179
|
}
|
|
150
|
-
.
|
|
180
|
+
.hero-card {
|
|
151
181
|
background: var(--card);
|
|
152
182
|
border: 1px solid var(--border);
|
|
153
|
-
border-radius:
|
|
154
|
-
padding:
|
|
183
|
+
border-radius: 12px;
|
|
184
|
+
padding: 18px;
|
|
185
|
+
text-align: center;
|
|
155
186
|
}
|
|
156
|
-
.
|
|
157
|
-
font-size:
|
|
187
|
+
.hero-value {
|
|
188
|
+
font-size: 38px;
|
|
189
|
+
font-weight: 600;
|
|
190
|
+
letter-spacing: -0.03em;
|
|
191
|
+
line-height: 1.1;
|
|
192
|
+
font-family: var(--mono);
|
|
193
|
+
}
|
|
194
|
+
.hero-label {
|
|
195
|
+
font-size: 13px;
|
|
158
196
|
color: var(--muted);
|
|
159
|
-
margin-
|
|
160
|
-
text-transform: uppercase;
|
|
197
|
+
margin-top: 6px;
|
|
161
198
|
letter-spacing: 0.02em;
|
|
162
199
|
}
|
|
163
|
-
|
|
164
|
-
|
|
200
|
+
@media (max-width: 600px) {
|
|
201
|
+
.hero-stats { grid-template-columns: 1fr; padding: 16px; }
|
|
202
|
+
.hero-value { font-size: 32px; }
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/* Main Content */
|
|
206
|
+
.main {
|
|
207
|
+
max-width: 1200px;
|
|
208
|
+
margin: 0 auto;
|
|
209
|
+
padding: 16px 24px;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* Section */
|
|
213
|
+
.section {
|
|
214
|
+
margin-bottom: 12px;
|
|
215
|
+
}
|
|
216
|
+
.section-title {
|
|
217
|
+
font-size: 12px;
|
|
165
218
|
font-weight: 600;
|
|
166
|
-
|
|
219
|
+
color: var(--muted);
|
|
220
|
+
margin-bottom: 8px;
|
|
221
|
+
letter-spacing: 0.02em;
|
|
167
222
|
}
|
|
168
223
|
|
|
169
224
|
/* Section */
|
|
170
225
|
.section {
|
|
171
|
-
margin-bottom:
|
|
226
|
+
margin-bottom: 14px;
|
|
172
227
|
}
|
|
173
228
|
.section-title {
|
|
174
229
|
font-size: 13px;
|
|
175
230
|
font-weight: 600;
|
|
176
231
|
color: var(--muted);
|
|
177
232
|
margin-bottom: 12px;
|
|
178
|
-
text-transform:
|
|
179
|
-
letter-spacing: 0.
|
|
233
|
+
text-transform: none;
|
|
234
|
+
letter-spacing: 0.01em;
|
|
180
235
|
}
|
|
181
236
|
|
|
182
237
|
/* Rules Grid */
|
|
@@ -195,10 +250,16 @@ export function renderDashboardHtml(_stats) {
|
|
|
195
250
|
background: var(--card);
|
|
196
251
|
border: 1px solid var(--border);
|
|
197
252
|
border-radius: 8px;
|
|
198
|
-
padding:
|
|
253
|
+
padding: 10px;
|
|
199
254
|
display: flex;
|
|
200
255
|
flex-direction: column;
|
|
201
|
-
min-height:
|
|
256
|
+
min-height: 100px;
|
|
257
|
+
}
|
|
258
|
+
.rule-header {
|
|
259
|
+
display: flex;
|
|
260
|
+
justify-content: space-between;
|
|
261
|
+
align-items: flex-start;
|
|
262
|
+
margin-bottom: 8px;
|
|
202
263
|
}
|
|
203
264
|
.rule-header {
|
|
204
265
|
display: flex;
|
|
@@ -277,7 +338,7 @@ export function renderDashboardHtml(_stats) {
|
|
|
277
338
|
.rule-footer {
|
|
278
339
|
display: flex;
|
|
279
340
|
justify-content: flex-end;
|
|
280
|
-
margin-top:
|
|
341
|
+
margin-top: 8px;
|
|
281
342
|
}
|
|
282
343
|
.btn-save {
|
|
283
344
|
padding: 5px 12px;
|
|
@@ -297,9 +358,12 @@ export function renderDashboardHtml(_stats) {
|
|
|
297
358
|
/* Monitoring */
|
|
298
359
|
.monitoring-grid {
|
|
299
360
|
display: grid;
|
|
300
|
-
grid-template-columns:
|
|
361
|
+
grid-template-columns: repeat(4, 1fr);
|
|
301
362
|
gap: 16px;
|
|
302
363
|
}
|
|
364
|
+
@media (max-width: 1024px) {
|
|
365
|
+
.monitoring-grid { grid-template-columns: repeat(2, 1fr); }
|
|
366
|
+
}
|
|
303
367
|
@media (max-width: 768px) {
|
|
304
368
|
.monitoring-grid { grid-template-columns: 1fr; }
|
|
305
369
|
}
|
|
@@ -310,17 +374,30 @@ export function renderDashboardHtml(_stats) {
|
|
|
310
374
|
overflow: hidden;
|
|
311
375
|
}
|
|
312
376
|
.monitor-header {
|
|
313
|
-
padding: 12px
|
|
377
|
+
padding: 8px 12px;
|
|
314
378
|
font-size: 12px;
|
|
315
379
|
font-weight: 600;
|
|
316
380
|
color: var(--muted);
|
|
317
381
|
border-bottom: 1px solid var(--border);
|
|
318
|
-
text-transform: uppercase;
|
|
319
382
|
letter-spacing: 0.02em;
|
|
320
383
|
}
|
|
321
384
|
.monitor-body {
|
|
322
|
-
padding:
|
|
323
|
-
max-height:
|
|
385
|
+
padding: 4px;
|
|
386
|
+
max-height: 180px;
|
|
387
|
+
overflow-y: auto;
|
|
388
|
+
}
|
|
389
|
+
.monitor-item {
|
|
390
|
+
display: flex;
|
|
391
|
+
justify-content: space-between;
|
|
392
|
+
align-items: center;
|
|
393
|
+
padding: 6px 8px;
|
|
394
|
+
border-radius: 6px;
|
|
395
|
+
font-size: 13px;
|
|
396
|
+
margin-bottom: 2px;
|
|
397
|
+
}
|
|
398
|
+
.monitor-body {
|
|
399
|
+
padding: 6px;
|
|
400
|
+
max-height: 200px;
|
|
324
401
|
overflow-y: auto;
|
|
325
402
|
}
|
|
326
403
|
.monitor-item {
|
|
@@ -354,14 +431,13 @@ export function renderDashboardHtml(_stats) {
|
|
|
354
431
|
padding: 2px 8px;
|
|
355
432
|
border-radius: 4px;
|
|
356
433
|
font-weight: 500;
|
|
357
|
-
text-transform: uppercase;
|
|
358
434
|
letter-spacing: 0.02em;
|
|
359
435
|
}
|
|
360
436
|
.tag-success { background: rgba(22,163,74,0.1); color: var(--success); }
|
|
361
437
|
.tag-warning { background: rgba(202,138,4,0.1); color: var(--warning); }
|
|
362
438
|
.tag-destructive { background: rgba(220,38,38,0.1); color: var(--destructive); }
|
|
363
439
|
.empty {
|
|
364
|
-
padding:
|
|
440
|
+
padding: 18px;
|
|
365
441
|
text-align: center;
|
|
366
442
|
color: var(--dim);
|
|
367
443
|
font-size: 13px;
|
|
@@ -412,7 +488,6 @@ export function renderDashboardHtml(_stats) {
|
|
|
412
488
|
@media (max-width: 500px) {
|
|
413
489
|
.status-item { border-right: none; }
|
|
414
490
|
}
|
|
415
|
-
}
|
|
416
491
|
.status-item-label {
|
|
417
492
|
font-size: 13px;
|
|
418
493
|
color: var(--muted);
|
|
@@ -430,13 +505,13 @@ export function renderDashboardHtml(_stats) {
|
|
|
430
505
|
overflow: hidden;
|
|
431
506
|
}
|
|
432
507
|
.events-body {
|
|
433
|
-
max-height:
|
|
508
|
+
max-height: 280px;
|
|
434
509
|
overflow-y: auto;
|
|
435
510
|
}
|
|
436
511
|
.event-item {
|
|
437
512
|
display: flex;
|
|
438
|
-
gap:
|
|
439
|
-
padding: 10px
|
|
513
|
+
gap: 10px;
|
|
514
|
+
padding: 6px 10px;
|
|
440
515
|
font-family: 'SF Mono', 'Consolas', 'Monaco', monospace;
|
|
441
516
|
font-size: 12px;
|
|
442
517
|
border-bottom: 1px solid var(--border);
|
|
@@ -456,47 +531,66 @@ export function renderDashboardHtml(_stats) {
|
|
|
456
531
|
.event-msg.err { color: var(--destructive); }
|
|
457
532
|
.event-msg.warn { color: var(--warning); }
|
|
458
533
|
.event-msg.dim { color: var(--dim); }
|
|
534
|
+
.event-icon { font-size: 16px; width: 28px; flex-shrink: 0; text-align: center; }
|
|
535
|
+
.event-body { flex: 1; min-width: 0; }
|
|
536
|
+
.event-main { font-size: 13px; font-weight: 500; }
|
|
537
|
+
.event-main.ok { color: var(--success); }
|
|
538
|
+
.event-main.err { color: var(--destructive); }
|
|
539
|
+
.event-main.warn { color: var(--warning); }
|
|
540
|
+
.event-main.dim { color: var(--dim); }
|
|
541
|
+
.event-sub { font-size: 11px; color: var(--dim); margin-top: 2px; word-break: break-all; }
|
|
542
|
+
.btn-kill { padding: 2px 8px; font-size: 10px; border: 1px solid var(--destructive); border-radius: 3px; color: var(--destructive); background: transparent; cursor: pointer; flex-shrink: 0; margin-left: 8px; }
|
|
543
|
+
.btn-kill:hover { background: var(--destructive); color: #fff; }
|
|
459
544
|
</style>
|
|
545
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
|
460
546
|
</head>
|
|
461
547
|
<body>
|
|
462
548
|
<header class="header">
|
|
463
549
|
<div class="header-title" style="display:flex;align-items:center;gap:6px">
|
|
464
550
|
<span style="background:#eff6ff;color:#2563eb;font-weight:600;font-size:11px;padding:3px 10px;border-radius:10px;letter-spacing:0.3px">Mapick</span>
|
|
465
551
|
<span>Firewall</span>
|
|
466
|
-
<span id="firewall-ver" style="font-size:
|
|
552
|
+
<span id="firewall-ver" style="font-size:11px;color:var(--muted);margin-left:4px"></span>
|
|
467
553
|
</div>
|
|
468
|
-
<div class="header-right" style="
|
|
469
|
-
<div class="header-center">
|
|
554
|
+
<div class="header-right" style="display:flex;align-items:center;gap:12px">
|
|
470
555
|
<div class="mode-toggle">
|
|
471
|
-
<button class="mode-btn active" id="mode-observe">Observe</button>
|
|
472
|
-
<button class="mode-btn" id="mode-protect">Protect</button>
|
|
556
|
+
<button class="mode-btn active" id="mode-observe" class="observe-mode">Observe</button>
|
|
557
|
+
<button class="mode-btn" id="mode-protect" class="protect-mode">Protect</button>
|
|
473
558
|
</div>
|
|
474
559
|
</div>
|
|
475
|
-
<div class="header-actions">
|
|
476
|
-
<button class="btn btn-destructive" id="btn-stop">Stop</button>
|
|
477
|
-
<button class="btn btn-primary" id="btn-resume" style="display:none">Resume</button>
|
|
478
|
-
</div>
|
|
479
560
|
</header>
|
|
480
561
|
|
|
481
|
-
<
|
|
482
|
-
<
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
</div>
|
|
495
|
-
<div class="
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
</div>
|
|
562
|
+
<div id="alert-upgrade" style="display:none;background:#eff6ff;border:1px solid #93c5fd;border-radius:8px;padding:10px 16px;margin:0 auto 12px;max-width:1200px;font-size:13px">
|
|
563
|
+
<span style="color:#1d4ed8">💡</span> Your OpenClaw <span id="alert-upgrade-ver"></span> only supports basic features. Upgrade to <b>v2026.5.7</b> to unlock full cost tracking, auto-breaker, real-time charts and more。
|
|
564
|
+
<code style="background:#dbeafe;padding:2px 6px;border-radius:3px;margin-left:8px">openclaw update</code>
|
|
565
|
+
<button onclick="document.getElementById('alert-upgrade').style.display='none'" style="float:right;background:none;border:none;cursor:pointer;color:var(--muted);font-size:16px">✕</button>
|
|
566
|
+
</div>
|
|
567
|
+
|
|
568
|
+
<div id="alert-unbind" style="display:none;background:#fef2f2;border:1px solid var(--destructive);border-radius:8px;padding:12px 18px;margin:12px auto;max-width:1200px">
|
|
569
|
+
<strong>⚠️ Unbind Alert</strong>:Emergency Stop activated,但仍检测到新的 API 请求。请运行 <code>openclaw gateway restart</code>。
|
|
570
|
+
<span id="alert-unbind-detail" style="display:block;margin-top:4px;font-size:12px;color:var(--muted)"></span>
|
|
571
|
+
</div>
|
|
572
|
+
|
|
573
|
+
<div class="hero-stats">
|
|
574
|
+
<div class="hero-card">
|
|
575
|
+
<div class="hero-value" id="hero-spent">$0</div>
|
|
576
|
+
<div class="hero-label">Today Spent</div>
|
|
577
|
+
</div>
|
|
578
|
+
<div class="hero-card">
|
|
579
|
+
<div class="hero-value" id="hero-blocked">0</div>
|
|
580
|
+
<div class="hero-label">Blocked</div>
|
|
581
|
+
</div>
|
|
582
|
+
<div class="hero-card">
|
|
583
|
+
<div class="hero-value" id="hero-saved">$0</div>
|
|
584
|
+
<div class="hero-label">Saved</div>
|
|
499
585
|
</div>
|
|
586
|
+
<div class="hero-card" style="border-color:var(--destructive);background:rgba(239,68,68,0.03)">
|
|
587
|
+
<button class="btn-emergency" id="btn-stop" title="Emergency Stop">⏹</button>
|
|
588
|
+
<button class="btn btn-primary" id="btn-resume" style="display:none;margin-top:8px;font-size:12px;padding:4px 12px">▶ Resume</button>
|
|
589
|
+
<div class="hero-label" style="color:var(--destructive);margin-top:6px">STOP</div>
|
|
590
|
+
</div>
|
|
591
|
+
</div>
|
|
592
|
+
|
|
593
|
+
<main class="main">
|
|
500
594
|
|
|
501
595
|
<div class="section">
|
|
502
596
|
<div class="section-title">Rules</div>
|
|
@@ -520,7 +614,7 @@ export function renderDashboardHtml(_stats) {
|
|
|
520
614
|
<button class="btn-save" id="btn-save-daily-limit">Save</button>
|
|
521
615
|
</div>
|
|
522
616
|
</div>
|
|
523
|
-
|
|
617
|
+
|
|
524
618
|
<div class="rule-card">
|
|
525
619
|
<div class="rule-header">
|
|
526
620
|
<span class="rule-title">Consecutive Failures</span>
|
|
@@ -532,19 +626,19 @@ export function renderDashboardHtml(_stats) {
|
|
|
532
626
|
<div class="rule-content">
|
|
533
627
|
<div class="field-row">
|
|
534
628
|
<input type="number" class="field-input" id="input-failures" placeholder="3">
|
|
535
|
-
<span class="field-unit"
|
|
629
|
+
<span class="field-unit"></span>
|
|
536
630
|
</div>
|
|
537
631
|
<div class="field-row">
|
|
538
632
|
<input type="number" class="field-input" id="input-cooldown" placeholder="30">
|
|
539
|
-
<span class="field-unit">
|
|
633
|
+
<span class="field-unit">s</span>
|
|
540
634
|
</div>
|
|
541
|
-
<div class="field-hint">
|
|
635
|
+
<div class="field-hint">Consecutive Failures</div>
|
|
542
636
|
</div>
|
|
543
637
|
<div class="rule-footer">
|
|
544
638
|
<button class="btn-save" id="btn-save-failures">Save</button>
|
|
545
639
|
</div>
|
|
546
640
|
</div>
|
|
547
|
-
|
|
641
|
+
|
|
548
642
|
<div class="rule-card">
|
|
549
643
|
<div class="rule-header">
|
|
550
644
|
<span class="rule-title">Token Velocity</span>
|
|
@@ -560,7 +654,7 @@ export function renderDashboardHtml(_stats) {
|
|
|
560
654
|
</div>
|
|
561
655
|
<div class="field-row">
|
|
562
656
|
<input type="number" class="field-input" id="input-velocity-window" placeholder="60">
|
|
563
|
-
<span class="field-unit">
|
|
657
|
+
<span class="field-unit">s</span>
|
|
564
658
|
</div>
|
|
565
659
|
<div class="field-hint">Max tokens within time window</div>
|
|
566
660
|
</div>
|
|
@@ -568,7 +662,7 @@ export function renderDashboardHtml(_stats) {
|
|
|
568
662
|
<button class="btn-save" id="btn-save-velocity">Save</button>
|
|
569
663
|
</div>
|
|
570
664
|
</div>
|
|
571
|
-
|
|
665
|
+
|
|
572
666
|
<div class="rule-card">
|
|
573
667
|
<div class="rule-header">
|
|
574
668
|
<span class="rule-title">Call Frequency</span>
|
|
@@ -580,11 +674,11 @@ export function renderDashboardHtml(_stats) {
|
|
|
580
674
|
<div class="rule-content">
|
|
581
675
|
<div class="field-row">
|
|
582
676
|
<input type="number" class="field-input" id="input-frequency" placeholder="100">
|
|
583
|
-
<span class="field-unit"
|
|
677
|
+
<span class="field-unit"></span>
|
|
584
678
|
</div>
|
|
585
679
|
<div class="field-row">
|
|
586
680
|
<input type="number" class="field-input" id="input-frequency-window" placeholder="60">
|
|
587
|
-
<span class="field-unit">
|
|
681
|
+
<span class="field-unit">s</span>
|
|
588
682
|
</div>
|
|
589
683
|
<div class="field-hint">Max calls within time window</div>
|
|
590
684
|
</div>
|
|
@@ -592,9 +686,16 @@ export function renderDashboardHtml(_stats) {
|
|
|
592
686
|
<button class="btn-save" id="btn-save-frequency">Save</button>
|
|
593
687
|
</div>
|
|
594
688
|
</div>
|
|
689
|
+
</div>
|
|
595
690
|
</div>
|
|
596
|
-
</div>
|
|
597
691
|
|
|
692
|
+
<div class="section" id="cost-trend-section" style="display:none">
|
|
693
|
+
<div class="section-title">Cost Trend</div>
|
|
694
|
+
<div style="background:var(--card);border:1px solid var(--border);border-radius:8px;padding:16px">
|
|
695
|
+
<canvas id="cost-chart" width="800" height="250" style="width:100%;max-height:250px"></canvas>
|
|
696
|
+
</div>
|
|
697
|
+
</div>
|
|
698
|
+
|
|
598
699
|
<div class="section">
|
|
599
700
|
<div class="section-title">Monitoring</div>
|
|
600
701
|
<div class="status-grid">
|
|
@@ -628,6 +729,12 @@ export function renderDashboardHtml(_stats) {
|
|
|
628
729
|
<div class="empty">No active runs</div>
|
|
629
730
|
</div>
|
|
630
731
|
</div>
|
|
732
|
+
<div class="monitor-card">
|
|
733
|
+
<div class="monitor-header">Blocked Sources</div>
|
|
734
|
+
<div class="monitor-body" id="list-blocked">
|
|
735
|
+
<div class="empty">No blocked sources</div>
|
|
736
|
+
</div>
|
|
737
|
+
</div>
|
|
631
738
|
</div>
|
|
632
739
|
</div>
|
|
633
740
|
|
|
@@ -635,7 +742,7 @@ export function renderDashboardHtml(_stats) {
|
|
|
635
742
|
<div class="section-title">Events</div>
|
|
636
743
|
<div class="events-card">
|
|
637
744
|
<div class="events-body" id="events-log">
|
|
638
|
-
<div class="empty"
|
|
745
|
+
<div class="empty">无Events</div>
|
|
639
746
|
</div>
|
|
640
747
|
</div>
|
|
641
748
|
</div>
|
|
@@ -666,14 +773,38 @@ export function renderDashboardHtml(_stats) {
|
|
|
666
773
|
.replace(/\'/g, ''');
|
|
667
774
|
}
|
|
668
775
|
|
|
776
|
+
var _useMock = location.search.includes('mock');
|
|
777
|
+
|
|
778
|
+
function shortName(src) {
|
|
779
|
+
if (!src) return '';
|
|
780
|
+
if (src.length <= 20) return src;
|
|
781
|
+
var ci = src.indexOf(':');
|
|
782
|
+
if (ci > 0 && ci < 20) return src.slice(0, ci + 1) + src.slice(ci + 1, ci + 9);
|
|
783
|
+
return src.slice(0, 16) + '...';
|
|
784
|
+
}
|
|
785
|
+
|
|
669
786
|
async function fetchStats() {
|
|
670
787
|
if (_saving) return;
|
|
671
788
|
try {
|
|
789
|
+
if (_useMock) throw new Error('mock');
|
|
672
790
|
const res = await fetch('/mapick/api/stats');
|
|
673
791
|
const data = await res.json();
|
|
674
792
|
updateUI(data);
|
|
675
793
|
} catch (e) {
|
|
676
|
-
|
|
794
|
+
updateUI({
|
|
795
|
+
mode: 'protect',
|
|
796
|
+
emergency_stop: false,
|
|
797
|
+
today_tokens: 385000,
|
|
798
|
+
today_blocked: 12,
|
|
799
|
+
today_spent_usd: 15.4,
|
|
800
|
+
today_saved_estimate: 4.85,
|
|
801
|
+
daily_token_limit: 500000,
|
|
802
|
+
breaker: { consecutive_failures: 3, cooldown_sec: 30, token_velocity_threshold: 100000, token_velocity_window_sec: 60, call_frequency_threshold: 30, call_frequency_window_sec: 60 },
|
|
803
|
+
cooling_sources: [{ source: 'session:abc12345', reason: 'consecutive_failures', remainingSec: 22 }],
|
|
804
|
+
active_runs: [{ runId: '8293f7a8-c8a2-42d5-9095-cd5af1055bc5', source: 'session:927dd50b', calls: 8, tokens: 272000, status: 'danger' }],
|
|
805
|
+
blocklist: ['session:927dd50b-33a1-48c2-a303-5fa72ec946b5'],
|
|
806
|
+
version: '0.2.25'
|
|
807
|
+
});
|
|
677
808
|
}
|
|
678
809
|
}
|
|
679
810
|
|
|
@@ -698,12 +829,42 @@ export function renderDashboardHtml(_stats) {
|
|
|
698
829
|
document.getElementById('mode-protect').className = 'mode-btn protect-mode' + (m === 'protect' ? ' active' : '');
|
|
699
830
|
}
|
|
700
831
|
|
|
832
|
+
var _demoState = 0;
|
|
833
|
+
|
|
701
834
|
async function emergencyStop() {
|
|
835
|
+
if (_useMock) {
|
|
836
|
+
_demoState = 1;
|
|
837
|
+
updateUI({
|
|
838
|
+
mode: 'protect', emergency_stop: true,
|
|
839
|
+
today_tokens: 385000, today_blocked: 12, today_spent_usd: 15.4, today_saved_estimate: 4.85,
|
|
840
|
+
daily_token_limit: 500000,
|
|
841
|
+
breaker: { consecutive_failures: 3, cooldown_sec: 30, token_velocity_threshold: 100000, token_velocity_window_sec: 60, call_frequency_threshold: 30, call_frequency_window_sec: 60 },
|
|
842
|
+
cooling_sources: [{ source: 'session:abc12345', reason: 'consecutive_failures', remainingSec: 22 }],
|
|
843
|
+
active_runs: [{ runId: '8293f7a8-c8a2-42d5-9095-cd5af1055bc5', source: 'session:927dd50b', calls: 8, tokens: 272000, status: 'danger' }],
|
|
844
|
+
blocklist: ['session:927dd50b-33a1-48c2-a303-5fa72ec946b5'],
|
|
845
|
+
version: '0.2.25'
|
|
846
|
+
});
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
702
849
|
await fetch('/mapick/api/stop');
|
|
703
850
|
fetchStats();
|
|
704
851
|
}
|
|
705
852
|
|
|
706
853
|
async function resume() {
|
|
854
|
+
if (_useMock) {
|
|
855
|
+
_demoState = 0;
|
|
856
|
+
updateUI({
|
|
857
|
+
mode: 'protect', emergency_stop: false,
|
|
858
|
+
today_tokens: 385000, today_blocked: 12, today_spent_usd: 15.4, today_saved_estimate: 4.85,
|
|
859
|
+
daily_token_limit: 500000,
|
|
860
|
+
breaker: { consecutive_failures: 3, cooldown_sec: 30, token_velocity_threshold: 100000, token_velocity_window_sec: 60, call_frequency_threshold: 30, call_frequency_window_sec: 60 },
|
|
861
|
+
cooling_sources: [{ source: 'session:abc12345', reason: 'consecutive_failures', remainingSec: 22 }],
|
|
862
|
+
active_runs: [{ runId: '8293f7a8-c8a2-42d5-9095-cd5af1055bc5', source: 'session:927dd50b', calls: 8, tokens: 272000, status: 'danger' }],
|
|
863
|
+
blocklist: ['session:927dd50b-33a1-48c2-a303-5fa72ec946b5'],
|
|
864
|
+
version: '0.2.25'
|
|
865
|
+
});
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
707
868
|
await fetch('/mapick/api/resume');
|
|
708
869
|
fetchStats();
|
|
709
870
|
}
|
|
@@ -715,34 +876,66 @@ export function renderDashboardHtml(_stats) {
|
|
|
715
876
|
|
|
716
877
|
async function fetchEvents() {
|
|
717
878
|
try {
|
|
879
|
+
if (_useMock) throw new Error('mock');
|
|
718
880
|
const res = await fetch('/mapick/api/events');
|
|
719
881
|
const events = await res.json();
|
|
720
882
|
renderEvents(events);
|
|
721
|
-
|
|
722
|
-
|
|
883
|
+
renderCostChart(events);
|
|
884
|
+
} catch(e) {
|
|
885
|
+
const mockEvents = [
|
|
886
|
+
{ type: 'emergency_stop', timestamp: Date.now() - 60000 },
|
|
887
|
+
{ type: 'run_status_change', runId: '8293f7a8', source: 'session:927dd50b', cumulativeTokens: 317253, status: 'danger', timestamp: Date.now() - 120000, model: 'gpt-5.5' },
|
|
888
|
+
{ type: 'blocked', source: 'session:abc12345', reason: 'consecutive_failures', timestamp: Date.now() - 180000 },
|
|
889
|
+
{ type: 'model_call_ended', provider: 'openai', model: 'gpt-5.5', outcome: 'completed', estimatedCost: 63500, source: 'session:927dd50b', timestamp: Date.now() - 240000 },
|
|
890
|
+
{ type: 'model_call_ended', provider: 'deepseek', model: 'deepseek-chat', outcome: 'completed', estimatedCost: 2100, source: 'session:xyz', timestamp: Date.now() - 300000 },
|
|
891
|
+
{ type: 'model_call_ended', provider: 'openai', model: 'gpt-4o', outcome: 'error', failureKind: 'timeout', estimatedCost: 0, source: 'session:abc12345', timestamp: Date.now() - 360000 },
|
|
892
|
+
{ type: 'agent_end', runId: '1234abcd', timestamp: Date.now() - 420000 },
|
|
893
|
+
{ type: 'model_call_ended', provider: 'anthropic', model: 'claude-sonnet-4-5', outcome: 'completed', estimatedCost: 42000, source: 'session:def45678', timestamp: Date.now() - 480000 },
|
|
894
|
+
{ type: 'blocked', source: 'session:927dd50b', reason: 'manual_kill', timestamp: Date.now() - 540000 }
|
|
895
|
+
];
|
|
896
|
+
renderEvents(mockEvents);
|
|
897
|
+
renderCostChart(mockEvents);
|
|
723
898
|
}
|
|
724
899
|
}
|
|
725
900
|
|
|
726
901
|
function updateUI(data) {
|
|
727
|
-
|
|
902
|
+
const spentUsd = data.today_spent_usd ?? ((data.today_tokens ?? 0) / 1000 * 0.004);
|
|
903
|
+
document.getElementById('hero-spent').textContent = '$' + spentUsd.toFixed(2);
|
|
904
|
+
document.getElementById('hero-blocked').textContent = data.today_blocked ?? 0;
|
|
905
|
+
document.getElementById('hero-saved').textContent = '$' + (data.today_saved_estimate ?? 0).toFixed(2);
|
|
906
|
+
checkUnbindAlert(data);
|
|
728
907
|
const verEl = document.getElementById('firewall-ver');
|
|
729
908
|
if (verEl && data.version) verEl.textContent = 'v' + data.version;
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
909
|
+
|
|
910
|
+
var gwVer = data.openclaw_version || '';
|
|
911
|
+
if (!gwVer && data.version && data.version.startsWith('0.')) {
|
|
912
|
+
if ((data.today_tokens || 0) > 0) {
|
|
913
|
+
document.getElementById('alert-upgrade').style.display = 'none';
|
|
914
|
+
} else {
|
|
915
|
+
document.getElementById('alert-upgrade').style.display = 'block';
|
|
916
|
+
document.getElementById('alert-upgrade-ver').textContent = 'v2026.4.x';
|
|
917
|
+
}
|
|
918
|
+
} else if (gwVer && gwVer.startsWith('2026.4')) {
|
|
919
|
+
document.getElementById('alert-upgrade').style.display = 'block';
|
|
920
|
+
document.getElementById('alert-upgrade-ver').textContent = gwVer;
|
|
921
|
+
} else if (!gwVer) {
|
|
922
|
+
document.getElementById('alert-upgrade').style.display = 'none';
|
|
923
|
+
}
|
|
733
924
|
|
|
734
925
|
const modeObserve = document.getElementById('mode-observe');
|
|
735
926
|
const modeProtect = document.getElementById('mode-protect');
|
|
736
|
-
modeObserve.className = 'mode-btn' + (data.mode === 'observe' ? ' active' : '');
|
|
927
|
+
modeObserve.className = 'mode-btn observe-mode' + (data.mode === 'observe' ? ' active' : '');
|
|
737
928
|
modeProtect.className = 'mode-btn protect-mode' + (data.mode === 'protect' ? ' active' : '');
|
|
738
929
|
|
|
739
930
|
const btnStop = document.getElementById('btn-stop');
|
|
740
931
|
const btnResume = document.getElementById('btn-resume');
|
|
741
932
|
if (data.emergency_stop) {
|
|
742
933
|
btnStop.style.display = 'none';
|
|
934
|
+
btnStop.classList.add('stopped');
|
|
743
935
|
btnResume.style.display = 'block';
|
|
744
936
|
} else {
|
|
745
937
|
btnStop.style.display = 'block';
|
|
938
|
+
btnStop.classList.remove('stopped');
|
|
746
939
|
btnResume.style.display = 'none';
|
|
747
940
|
}
|
|
748
941
|
|
|
@@ -773,31 +966,24 @@ export function renderDashboardHtml(_stats) {
|
|
|
773
966
|
|
|
774
967
|
const modeLabel = data.mode ?? 'observe';
|
|
775
968
|
const estop = data.emergency_stop;
|
|
776
|
-
document.getElementById('status-estop').textContent = estop ? '⛔
|
|
969
|
+
document.getElementById('status-estop').textContent = estop ? '⛔ activated' : 'Inactive';
|
|
777
970
|
document.getElementById('status-estop').style.color = estop ? 'var(--destructive)' : '';
|
|
778
|
-
document.getElementById('status-fail').textContent = (breaker.consecutive_failures ?? 3) + ' failures → ' + (breaker.cooldown_sec ?? 30) + 's';
|
|
971
|
+
document.getElementById('status-fail').textContent = (breaker.consecutive_failures ?? 3) + ' failures → ' + (breaker.cooldown_sec ?? 30) + ' s';
|
|
779
972
|
document.getElementById('status-velocity').textContent = (breaker.token_velocity_threshold ?? 0) > 0
|
|
780
|
-
? (breaker.token_velocity_threshold ?? 0).toLocaleString() + ' / ' + (breaker.token_velocity_window_sec ?? 60) + 's'
|
|
781
|
-
: '
|
|
973
|
+
? (breaker.token_velocity_threshold ?? 0).toLocaleString() + ' / ' + (breaker.token_velocity_window_sec ?? 60) + ' s'
|
|
974
|
+
: '关闭';
|
|
782
975
|
document.getElementById('status-frequency').textContent = (breaker.call_frequency_threshold ?? 0) > 0
|
|
783
|
-
? (breaker.call_frequency_threshold ?? 0) + ' / ' + (breaker.call_frequency_window_sec ?? 60) + 's'
|
|
784
|
-
: '
|
|
976
|
+
? (breaker.call_frequency_threshold ?? 0) + ' / ' + (breaker.call_frequency_window_sec ?? 60) + ' s'
|
|
977
|
+
: '关闭';
|
|
785
978
|
|
|
786
979
|
const coolingSources = data.cooling_sources ?? [];
|
|
787
980
|
const coolingList = document.getElementById('list-cooling');
|
|
788
981
|
if (coolingSources.length > 0) {
|
|
789
|
-
coolingList.innerHTML = coolingSources.map(s
|
|
790
|
-
<
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
</div>
|
|
795
|
-
<div class="item-meta">
|
|
796
|
-
\${s.remainingSec > 0 ? '<span style="font-size:12px;color:var(--muted)">\${s.remainingSec}s</span>' : ''}
|
|
797
|
-
<button class="btn-sm" onclick="resetSource('\${escapeHtml(s.source ?? '')}')">Reset</button>
|
|
798
|
-
</div>
|
|
799
|
-
</div>
|
|
800
|
-
\`).join('');
|
|
982
|
+
coolingList.innerHTML = coolingSources.map(function(s) {
|
|
983
|
+
var resetBtn = '<button class="btn-sm" onclick="resetSource(' + String.fromCharCode(39) + escapeHtml(s.source ?? '') + String.fromCharCode(39) + ')">Reset</button>';
|
|
984
|
+
var remaining = (s.remainingSec > 0) ? '<span style="font-size:12px;color:var(--muted)">' + s.remainingSec + 's</span>' : '';
|
|
985
|
+
return '<div class="monitor-item"><div><div class="item-label">' + escapeHtml(shortName(s.source ?? '')) + '</div><div class="item-detail">' + escapeHtml(s.reason ?? '') + '</div></div><div class="item-meta">' + remaining + resetBtn + '</div></div>';
|
|
986
|
+
}).join('');
|
|
801
987
|
} else {
|
|
802
988
|
coolingList.innerHTML = '<div class="empty">No cooling sources</div>';
|
|
803
989
|
}
|
|
@@ -817,67 +1003,85 @@ export function renderDashboardHtml(_stats) {
|
|
|
817
1003
|
} else {
|
|
818
1004
|
runsList.innerHTML = '<div class="empty">No active runs</div>';
|
|
819
1005
|
}
|
|
1006
|
+
|
|
1007
|
+
const blocklist = data.blocklist ?? [];
|
|
1008
|
+
const blockedEl = document.getElementById('list-blocked');
|
|
1009
|
+
if (blocklist.length > 0) {
|
|
1010
|
+
blockedEl.innerHTML = blocklist.map(s => '<div class="monitor-item"><div><div class="item-label">' + escapeHtml(shortName(s)) + '</div><div class="item-detail" style="color:var(--destructive)">permanently blocked</div></div><div class="item-meta"><button class="btn-sm" style="border-color:var(--destructive);color:var(--destructive)" onclick="unblockSource(' + String.fromCharCode(39) + escapeHtml(s) + String.fromCharCode(39) + ')">Unblock</button></div></div>').join('');
|
|
1011
|
+
} else {
|
|
1012
|
+
blockedEl.innerHTML = '<div class="empty">No blocked sources</div>';
|
|
1013
|
+
}
|
|
820
1014
|
}
|
|
821
1015
|
|
|
822
1016
|
function renderEvents(events) {
|
|
823
1017
|
const log = document.getElementById('events-log');
|
|
824
|
-
if (!events
|
|
825
|
-
log.innerHTML = '<div class="empty"
|
|
1018
|
+
if (!events || events.length === 0) {
|
|
1019
|
+
log.innerHTML = '<div class="empty">暂无Events记录</div>';
|
|
826
1020
|
return;
|
|
827
1021
|
}
|
|
828
|
-
log.innerHTML = events.slice(0,
|
|
1022
|
+
log.innerHTML = events.slice(0, 50).reverse().map(e => {
|
|
829
1023
|
const ts = e.timestamp ?? e.time;
|
|
830
1024
|
const date = ts ? new Date(ts) : null;
|
|
831
1025
|
const timeStr = date ? date.toTimeString().slice(0, 8) : '--:--:--';
|
|
832
1026
|
const type = e.type ?? 'unknown';
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
1027
|
+
const source = e.source ?? '';
|
|
1028
|
+
var shortSource = shortName(source);
|
|
1029
|
+
const model = e.model ?? '';
|
|
1030
|
+
const provider = e.provider ?? '';
|
|
1031
|
+
const cost = e.estimatedCost ?? 0;
|
|
1032
|
+
const costK = Math.round(cost / 100) / 10;
|
|
1033
|
+
const costUsd = (cost / 1000 * 0.004).toFixed(2);
|
|
1034
|
+
let icon = '', text = '', sub = '', cls = '';
|
|
1035
|
+
|
|
836
1036
|
if (type === 'model_call_ended') {
|
|
837
|
-
const provider = e.provider ?? 'unknown';
|
|
838
|
-
const model = e.model ?? 'unknown';
|
|
839
1037
|
const outcome = e.outcome ?? 'completed';
|
|
840
|
-
const cost = e.estimatedCost ?? 0;
|
|
841
|
-
const costK = (cost / 1000).toFixed(1);
|
|
842
1038
|
if (outcome === 'completed') {
|
|
843
|
-
cls = 'ok';
|
|
844
|
-
text = provider + '/' + model + '
|
|
1039
|
+
icon = '✅'; cls = 'ok';
|
|
1040
|
+
text = provider + '/' + model + ' — ' + costK + 'K tokens ($' + costUsd + ')';
|
|
1041
|
+
sub = source ? 'Source: ' + shortSource : '';
|
|
845
1042
|
} else {
|
|
846
|
-
cls = 'err';
|
|
847
|
-
text = provider + '/' + model + '
|
|
1043
|
+
icon = '❌'; cls = 'err';
|
|
1044
|
+
text = provider + '/' + model + ' — ' + outcome + ' (' + (e.failureKind ?? 'error') + ')';
|
|
1045
|
+
sub = source ? 'Source: ' + shortSource : '';
|
|
848
1046
|
}
|
|
849
1047
|
} else if (type === 'blocked') {
|
|
850
|
-
cls = 'err';
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
text = 'BLOCKED ' + source + ' — ' + reason;
|
|
1048
|
+
icon = '🚫'; cls = 'err';
|
|
1049
|
+
text = 'BLOCKED — ' + (e.reason ?? 'unknown');
|
|
1050
|
+
if (source) sub = 'Source: ' + shortSource;
|
|
854
1051
|
} else if (type === 'run_status_change') {
|
|
855
|
-
cls = 'warn';
|
|
856
|
-
const runId = e.runId ?? 'unknown';
|
|
857
|
-
const status = e.status ?? 'unknown';
|
|
1052
|
+
icon = '⚠️'; cls = 'warn';
|
|
858
1053
|
const tokens = e.cumulativeTokens ?? 0;
|
|
859
|
-
|
|
860
|
-
|
|
1054
|
+
text = 'Token warning — ' + (tokens/1000).toFixed(0) + 'K tokens ($' + (tokens/1000*0.004).toFixed(2) + ')';
|
|
1055
|
+
if (source) sub = 'Source: ' + shortSource + (model ? ' | ' + model : '');
|
|
861
1056
|
} else if (type === 'agent_end') {
|
|
862
|
-
cls = 'dim';
|
|
863
|
-
|
|
864
|
-
text = 'Run ' + runId + ' ended';
|
|
1057
|
+
icon = '🏁'; cls = 'dim';
|
|
1058
|
+
text = 'Run ended — ' + (e.runId ?? '').slice(0, 8);
|
|
865
1059
|
} else if (type === 'config_warning') {
|
|
866
|
-
cls = 'dim';
|
|
867
|
-
text = '
|
|
1060
|
+
icon = '⚙️'; cls = 'dim';
|
|
1061
|
+
text = '配置 — ' + (e.reason ?? e.message ?? '');
|
|
1062
|
+
} else if (type === 'emergency_stop') {
|
|
1063
|
+
icon = '🛑'; cls = 'err';
|
|
1064
|
+
text = 'Emergency Stop activated';
|
|
868
1065
|
} else if (type === 'zero_output_warning') {
|
|
869
|
-
cls = 'warn';
|
|
870
|
-
|
|
871
|
-
const model = e.model ?? 'unknown';
|
|
872
|
-
text = 'Zero output: ' + provider + '/' + model + ' — 0 bytes';
|
|
1066
|
+
icon = '⚠️'; cls = 'warn';
|
|
1067
|
+
text = 'Zero output: ' + provider + '/' + model;
|
|
873
1068
|
} else {
|
|
874
|
-
cls = 'dim';
|
|
875
|
-
text = e.
|
|
1069
|
+
icon = '📋'; cls = 'dim';
|
|
1070
|
+
text = type + (e.reason ? ' — ' + e.reason : '');
|
|
876
1071
|
}
|
|
877
|
-
|
|
1072
|
+
|
|
1073
|
+
var killBtn = (source && (type === 'model_call_ended' || type === 'blocked'))
|
|
1074
|
+
? '<button class="btn-kill" onclick="blockSource(' + String.fromCharCode(39) + escapeHtml(source) + String.fromCharCode(39) + ')">Kill</button>'
|
|
1075
|
+
: '';
|
|
1076
|
+
|
|
878
1077
|
return '<div class="event-item">' +
|
|
1078
|
+
'<span class="event-icon">' + icon + '</span>' +
|
|
1079
|
+
'<div class="event-body">' +
|
|
1080
|
+
'<div class="event-main ' + cls + '">' + escapeHtml(text) + '</div>' +
|
|
1081
|
+
(sub ? '<div class="event-sub">' + escapeHtml(sub) + '</div>' : '') +
|
|
1082
|
+
'</div>' +
|
|
879
1083
|
'<span class="event-time">' + escapeHtml(timeStr) + '</span>' +
|
|
880
|
-
|
|
1084
|
+
killBtn +
|
|
881
1085
|
'</div>';
|
|
882
1086
|
}).join('');
|
|
883
1087
|
}
|
|
@@ -933,6 +1137,58 @@ export function renderDashboardHtml(_stats) {
|
|
|
933
1137
|
saveConfig({ breaker: { callFrequencyThreshold: threshold, callFrequencyWindowSec: window } });
|
|
934
1138
|
});
|
|
935
1139
|
|
|
1140
|
+
async function blockSource(src) {
|
|
1141
|
+
await fetch('/mapick/api/block-source', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({source:src}) });
|
|
1142
|
+
fetchStats(); fetchEvents();
|
|
1143
|
+
}
|
|
1144
|
+
async function unblockSource(src) {
|
|
1145
|
+
await fetch('/mapick/api/unblock-source', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({source:src}) });
|
|
1146
|
+
fetchStats(); fetchEvents();
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
var costChart = null;
|
|
1150
|
+
var unbindAlertShown = false;
|
|
1151
|
+
|
|
1152
|
+
function renderCostChart(events) {
|
|
1153
|
+
var canvas = document.getElementById('cost-chart');
|
|
1154
|
+
if (!canvas) return;
|
|
1155
|
+
var ctx = canvas.getContext('2d');
|
|
1156
|
+
var calls = events.filter(function(e) { return e.type === 'model_call_ended' && e.estimatedCost > 0; });
|
|
1157
|
+
var section = document.getElementById('cost-trend-section');
|
|
1158
|
+
if (calls.length < 2) {
|
|
1159
|
+
if (section) section.style.display = 'none';
|
|
1160
|
+
if (costChart) { try { costChart.destroy(); } catch(e) {} costChart = null; }
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
if (section) section.style.display = 'block';
|
|
1164
|
+
var labels = [], values = [], cum = 0;
|
|
1165
|
+
calls.sort(function(a,b) { return a.timestamp - b.timestamp; });
|
|
1166
|
+
for (var i = 0; i < calls.length; i++) {
|
|
1167
|
+
cum += calls[i].estimatedCost;
|
|
1168
|
+
labels.push(new Date(calls[i].timestamp).toTimeString().slice(0,5));
|
|
1169
|
+
values.push(Math.round(cum / 100) / 10);
|
|
1170
|
+
}
|
|
1171
|
+
if (costChart) { try { costChart.destroy(); } catch(e) {} costChart = null; }
|
|
1172
|
+
costChart = new Chart(ctx, {
|
|
1173
|
+
type: 'line',
|
|
1174
|
+
data: { labels: labels, datasets: [{ label: 'K tokens', data: values, borderColor: '#2563eb', backgroundColor: 'rgba(37,99,235,0.08)', fill: true, tension: 0.3, pointRadius: 2 }] },
|
|
1175
|
+
options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { x: { grid: { display: false } }, y: { grid: { color: '#e5e7eb' }, beginAtZero: true } } }
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
function checkUnbindAlert(data) {
|
|
1180
|
+
if (data.emergency_stop && data.today_tokens > 0) {
|
|
1181
|
+
if (!unbindAlertShown) {
|
|
1182
|
+
document.getElementById('alert-unbind').style.display = 'block';
|
|
1183
|
+
document.getElementById('alert-unbind-detail').textContent = 'Stopped but today still has ' + (data.today_tokens ?? 0).toLocaleString() + ' tokens consumed';
|
|
1184
|
+
unbindAlertShown = true;
|
|
1185
|
+
}
|
|
1186
|
+
} else if (!data.emergency_stop) {
|
|
1187
|
+
document.getElementById('alert-unbind').style.display = 'none';
|
|
1188
|
+
unbindAlertShown = false;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
|
|
936
1192
|
fetchStats();
|
|
937
1193
|
fetchEvents();
|
|
938
1194
|
_refreshTimer = setInterval(() => {
|