@mapick/cost-firewall 0.1.0
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/LICENSE +21 -0
- package/README.md +161 -0
- package/dist/breaker.d.ts +30 -0
- package/dist/breaker.d.ts.map +1 -0
- package/dist/breaker.js +131 -0
- package/dist/breaker.js.map +1 -0
- package/dist/cli/index.d.ts +18 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +244 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/config-warn.d.ts +11 -0
- package/dist/config-warn.d.ts.map +1 -0
- package/dist/config-warn.js +26 -0
- package/dist/config-warn.js.map +1 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +32 -0
- package/dist/config.js.map +1 -0
- package/dist/dashboard/html.d.ts +2 -0
- package/dist/dashboard/html.d.ts.map +1 -0
- package/dist/dashboard/html.js +898 -0
- package/dist/dashboard/html.js.map +1 -0
- package/dist/dashboard/index.d.ts +8 -0
- package/dist/dashboard/index.d.ts.map +1 -0
- package/dist/dashboard/index.js +163 -0
- package/dist/dashboard/index.js.map +1 -0
- package/dist/dashboard/sse.d.ts +9 -0
- package/dist/dashboard/sse.d.ts.map +1 -0
- package/dist/dashboard/sse.js +22 -0
- package/dist/dashboard/sse.js.map +1 -0
- package/dist/hooks/agent-end.d.ts +18 -0
- package/dist/hooks/agent-end.d.ts.map +1 -0
- package/dist/hooks/agent-end.js +25 -0
- package/dist/hooks/agent-end.js.map +1 -0
- package/dist/hooks/before-agent-reply.d.ts +29 -0
- package/dist/hooks/before-agent-reply.d.ts.map +1 -0
- package/dist/hooks/before-agent-reply.js +42 -0
- package/dist/hooks/before-agent-reply.js.map +1 -0
- package/dist/hooks/index.d.ts +7 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +17 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/model-call.d.ts +39 -0
- package/dist/hooks/model-call.d.ts.map +1 -0
- package/dist/hooks/model-call.js +120 -0
- package/dist/hooks/model-call.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/pricing.d.ts +12 -0
- package/dist/pricing.d.ts.map +1 -0
- package/dist/pricing.js +43 -0
- package/dist/pricing.js.map +1 -0
- package/dist/provider/auth.d.ts +14 -0
- package/dist/provider/auth.d.ts.map +1 -0
- package/dist/provider/auth.js +53 -0
- package/dist/provider/auth.js.map +1 -0
- package/dist/provider/index.d.ts +12 -0
- package/dist/provider/index.d.ts.map +1 -0
- package/dist/provider/index.js +134 -0
- package/dist/provider/index.js.map +1 -0
- package/dist/provider/route.d.ts +10 -0
- package/dist/provider/route.d.ts.map +1 -0
- package/dist/provider/route.js +19 -0
- package/dist/provider/route.js.map +1 -0
- package/dist/provider/stream.d.ts +10 -0
- package/dist/provider/stream.d.ts.map +1 -0
- package/dist/provider/stream.js +120 -0
- package/dist/provider/stream.js.map +1 -0
- package/dist/provider/synthetic.d.ts +13 -0
- package/dist/provider/synthetic.d.ts.map +1 -0
- package/dist/provider/synthetic.js +59 -0
- package/dist/provider/synthetic.js.map +1 -0
- package/dist/provider/upstream/anthropic.d.ts +13 -0
- package/dist/provider/upstream/anthropic.d.ts.map +1 -0
- package/dist/provider/upstream/anthropic.js +62 -0
- package/dist/provider/upstream/anthropic.js.map +1 -0
- package/dist/provider/upstream/openai.d.ts +17 -0
- package/dist/provider/upstream/openai.d.ts.map +1 -0
- package/dist/provider/upstream/openai.js +75 -0
- package/dist/provider/upstream/openai.js.map +1 -0
- package/dist/source.d.ts +35 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +41 -0
- package/dist/source.js.map +1 -0
- package/dist/state.d.ts +56 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +178 -0
- package/dist/state.js.map +1 -0
- package/dist/store.d.ts +23 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +68 -0
- package/dist/store.js.map +1 -0
- package/dist/tools/index.d.ts +13 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +63 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/types.d.ts +98 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/openclaw.plugin.json +44 -0
- package/package.json +49 -0
|
@@ -0,0 +1,898 @@
|
|
|
1
|
+
export function renderDashboardHtml(_stats) {
|
|
2
|
+
return `<!DOCTYPE html>
|
|
3
|
+
<html lang="en">
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>Firewall</title>
|
|
8
|
+
<style>
|
|
9
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
10
|
+
:root {
|
|
11
|
+
--bg: #fafafa;
|
|
12
|
+
--fg: #111;
|
|
13
|
+
--muted: #525252;
|
|
14
|
+
--dim: #a1a1a1;
|
|
15
|
+
--border: #e5e5e7;
|
|
16
|
+
--card: #fff;
|
|
17
|
+
--accent: #2563eb;
|
|
18
|
+
--accent-hover: #1d4ed8;
|
|
19
|
+
--destructive: #dc2626;
|
|
20
|
+
--destructive-hover: #b91c1c;
|
|
21
|
+
--success: #16a34a;
|
|
22
|
+
--warning: #ca8a04;
|
|
23
|
+
}
|
|
24
|
+
body {
|
|
25
|
+
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI', Roboto, sans-serif;
|
|
26
|
+
background: var(--bg);
|
|
27
|
+
color: var(--fg);
|
|
28
|
+
line-height: 1.5;
|
|
29
|
+
min-height: 100vh;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Header */
|
|
33
|
+
.header {
|
|
34
|
+
background: var(--card);
|
|
35
|
+
border-bottom: 1px solid var(--border);
|
|
36
|
+
padding: 16px 24px;
|
|
37
|
+
display: flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
justify-content: space-between;
|
|
40
|
+
position: sticky;
|
|
41
|
+
top: 0;
|
|
42
|
+
z-index: 100;
|
|
43
|
+
}
|
|
44
|
+
.header-title {
|
|
45
|
+
font-size: 18px;
|
|
46
|
+
font-weight: 600;
|
|
47
|
+
letter-spacing: -0.01em;
|
|
48
|
+
}
|
|
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
|
+
.mode-btn {
|
|
58
|
+
padding: 6px 16px;
|
|
59
|
+
border: none;
|
|
60
|
+
background: transparent;
|
|
61
|
+
color: var(--muted);
|
|
62
|
+
font-size: 13px;
|
|
63
|
+
font-weight: 500;
|
|
64
|
+
cursor: pointer;
|
|
65
|
+
border-radius: 4px;
|
|
66
|
+
transition: all 0.15s ease;
|
|
67
|
+
}
|
|
68
|
+
.mode-btn.active {
|
|
69
|
+
background: var(--card);
|
|
70
|
+
color: var(--fg);
|
|
71
|
+
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
|
|
72
|
+
}
|
|
73
|
+
.header-actions {
|
|
74
|
+
display: flex;
|
|
75
|
+
gap: 8px;
|
|
76
|
+
}
|
|
77
|
+
.btn {
|
|
78
|
+
padding: 8px 14px;
|
|
79
|
+
border: 1px solid var(--border);
|
|
80
|
+
border-radius: 6px;
|
|
81
|
+
font-size: 13px;
|
|
82
|
+
font-weight: 500;
|
|
83
|
+
cursor: pointer;
|
|
84
|
+
transition: all 0.15s ease;
|
|
85
|
+
background: var(--card);
|
|
86
|
+
color: var(--fg);
|
|
87
|
+
}
|
|
88
|
+
.btn:hover {
|
|
89
|
+
background: var(--bg);
|
|
90
|
+
border-color: var(--dim);
|
|
91
|
+
}
|
|
92
|
+
.btn-destructive {
|
|
93
|
+
background: var(--destructive);
|
|
94
|
+
border-color: var(--destructive);
|
|
95
|
+
color: #fff;
|
|
96
|
+
}
|
|
97
|
+
.btn-destructive:hover {
|
|
98
|
+
background: var(--destructive-hover);
|
|
99
|
+
border-color: var(--destructive-hover);
|
|
100
|
+
}
|
|
101
|
+
.btn-primary {
|
|
102
|
+
background: var(--accent);
|
|
103
|
+
border-color: var(--accent);
|
|
104
|
+
color: #fff;
|
|
105
|
+
}
|
|
106
|
+
.btn-primary:hover {
|
|
107
|
+
background: var(--accent-hover);
|
|
108
|
+
border-color: var(--accent-hover);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* Main Content */
|
|
112
|
+
.main {
|
|
113
|
+
max-width: 1200px;
|
|
114
|
+
margin: 0 auto;
|
|
115
|
+
padding: 24px;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* Stats Row */
|
|
119
|
+
.stats-row {
|
|
120
|
+
display: grid;
|
|
121
|
+
grid-template-columns: repeat(4, 1fr);
|
|
122
|
+
gap: 16px;
|
|
123
|
+
margin-bottom: 24px;
|
|
124
|
+
}
|
|
125
|
+
@media (max-width: 768px) {
|
|
126
|
+
.stats-row { grid-template-columns: repeat(2, 1fr); }
|
|
127
|
+
}
|
|
128
|
+
.stat-card {
|
|
129
|
+
background: var(--card);
|
|
130
|
+
border: 1px solid var(--border);
|
|
131
|
+
border-radius: 8px;
|
|
132
|
+
padding: 16px 20px;
|
|
133
|
+
}
|
|
134
|
+
.stat-label {
|
|
135
|
+
font-size: 12px;
|
|
136
|
+
color: var(--muted);
|
|
137
|
+
margin-bottom: 4px;
|
|
138
|
+
text-transform: uppercase;
|
|
139
|
+
letter-spacing: 0.02em;
|
|
140
|
+
}
|
|
141
|
+
.stat-value {
|
|
142
|
+
font-size: 28px;
|
|
143
|
+
font-weight: 600;
|
|
144
|
+
letter-spacing: -0.02em;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* Section */
|
|
148
|
+
.section {
|
|
149
|
+
margin-bottom: 24px;
|
|
150
|
+
}
|
|
151
|
+
.section-title {
|
|
152
|
+
font-size: 13px;
|
|
153
|
+
font-weight: 600;
|
|
154
|
+
color: var(--muted);
|
|
155
|
+
margin-bottom: 12px;
|
|
156
|
+
text-transform: uppercase;
|
|
157
|
+
letter-spacing: 0.05em;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* Rules Grid */
|
|
161
|
+
.rules-grid {
|
|
162
|
+
display: grid;
|
|
163
|
+
grid-template-columns: repeat(4, 1fr);
|
|
164
|
+
gap: 16px;
|
|
165
|
+
}
|
|
166
|
+
@media (max-width: 1024px) {
|
|
167
|
+
.rules-grid { grid-template-columns: repeat(2, 1fr); }
|
|
168
|
+
}
|
|
169
|
+
@media (max-width: 640px) {
|
|
170
|
+
.rules-grid { grid-template-columns: 1fr; }
|
|
171
|
+
}
|
|
172
|
+
.rule-card {
|
|
173
|
+
background: var(--card);
|
|
174
|
+
border: 1px solid var(--border);
|
|
175
|
+
border-radius: 8px;
|
|
176
|
+
padding: 16px;
|
|
177
|
+
display: flex;
|
|
178
|
+
flex-direction: column;
|
|
179
|
+
min-height: 140px;
|
|
180
|
+
}
|
|
181
|
+
.rule-header {
|
|
182
|
+
display: flex;
|
|
183
|
+
justify-content: space-between;
|
|
184
|
+
align-items: flex-start;
|
|
185
|
+
margin-bottom: 12px;
|
|
186
|
+
}
|
|
187
|
+
.rule-title {
|
|
188
|
+
font-size: 14px;
|
|
189
|
+
font-weight: 600;
|
|
190
|
+
color: var(--fg);
|
|
191
|
+
}
|
|
192
|
+
.switch {
|
|
193
|
+
position: relative;
|
|
194
|
+
display: inline-block;
|
|
195
|
+
width: 36px;
|
|
196
|
+
height: 20px;
|
|
197
|
+
}
|
|
198
|
+
.switch input { opacity: 0; width: 0; height: 0; }
|
|
199
|
+
.slider {
|
|
200
|
+
position: absolute;
|
|
201
|
+
cursor: pointer;
|
|
202
|
+
top: 0; left: 0; right: 0; bottom: 0;
|
|
203
|
+
background-color: var(--dim);
|
|
204
|
+
transition: 0.15s ease;
|
|
205
|
+
border-radius: 10px;
|
|
206
|
+
}
|
|
207
|
+
.slider:before {
|
|
208
|
+
position: absolute;
|
|
209
|
+
content: "";
|
|
210
|
+
height: 16px;
|
|
211
|
+
width: 16px;
|
|
212
|
+
left: 2px;
|
|
213
|
+
bottom: 2px;
|
|
214
|
+
background-color: #fff;
|
|
215
|
+
transition: 0.15s ease;
|
|
216
|
+
border-radius: 50%;
|
|
217
|
+
}
|
|
218
|
+
input:checked + .slider { background-color: var(--accent); }
|
|
219
|
+
input:checked + .slider:before { transform: translateX(16px); }
|
|
220
|
+
.rule-content {
|
|
221
|
+
flex: 1;
|
|
222
|
+
display: flex;
|
|
223
|
+
flex-direction: column;
|
|
224
|
+
gap: 8px;
|
|
225
|
+
}
|
|
226
|
+
.field-row {
|
|
227
|
+
display: flex;
|
|
228
|
+
align-items: center;
|
|
229
|
+
gap: 8px;
|
|
230
|
+
}
|
|
231
|
+
.field-input {
|
|
232
|
+
flex: 1;
|
|
233
|
+
padding: 6px 10px;
|
|
234
|
+
border: 1px solid var(--border);
|
|
235
|
+
border-radius: 6px;
|
|
236
|
+
font-size: 13px;
|
|
237
|
+
font-family: inherit;
|
|
238
|
+
min-width: 0;
|
|
239
|
+
}
|
|
240
|
+
.field-input:focus {
|
|
241
|
+
outline: none;
|
|
242
|
+
border-color: var(--accent);
|
|
243
|
+
box-shadow: 0 0 0 2px rgba(37,99,235,0.1);
|
|
244
|
+
}
|
|
245
|
+
.field-unit {
|
|
246
|
+
font-size: 12px;
|
|
247
|
+
color: var(--muted);
|
|
248
|
+
min-width: 36px;
|
|
249
|
+
}
|
|
250
|
+
.field-hint {
|
|
251
|
+
font-size: 11px;
|
|
252
|
+
color: var(--dim);
|
|
253
|
+
margin-top: 4px;
|
|
254
|
+
}
|
|
255
|
+
.rule-footer {
|
|
256
|
+
display: flex;
|
|
257
|
+
justify-content: flex-end;
|
|
258
|
+
margin-top: 12px;
|
|
259
|
+
}
|
|
260
|
+
.btn-save {
|
|
261
|
+
padding: 5px 12px;
|
|
262
|
+
font-size: 12px;
|
|
263
|
+
border: 1px solid var(--border);
|
|
264
|
+
border-radius: 5px;
|
|
265
|
+
background: var(--bg);
|
|
266
|
+
color: var(--muted);
|
|
267
|
+
cursor: pointer;
|
|
268
|
+
transition: all 0.15s ease;
|
|
269
|
+
}
|
|
270
|
+
.btn-save:hover {
|
|
271
|
+
background: var(--border);
|
|
272
|
+
color: var(--fg);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/* Monitoring */
|
|
276
|
+
.monitoring-grid {
|
|
277
|
+
display: grid;
|
|
278
|
+
grid-template-columns: 1fr 1fr;
|
|
279
|
+
gap: 16px;
|
|
280
|
+
}
|
|
281
|
+
@media (max-width: 768px) {
|
|
282
|
+
.monitoring-grid { grid-template-columns: 1fr; }
|
|
283
|
+
}
|
|
284
|
+
.monitor-card {
|
|
285
|
+
background: var(--card);
|
|
286
|
+
border: 1px solid var(--border);
|
|
287
|
+
border-radius: 8px;
|
|
288
|
+
overflow: hidden;
|
|
289
|
+
}
|
|
290
|
+
.monitor-header {
|
|
291
|
+
padding: 12px 16px;
|
|
292
|
+
font-size: 12px;
|
|
293
|
+
font-weight: 600;
|
|
294
|
+
color: var(--muted);
|
|
295
|
+
border-bottom: 1px solid var(--border);
|
|
296
|
+
text-transform: uppercase;
|
|
297
|
+
letter-spacing: 0.02em;
|
|
298
|
+
}
|
|
299
|
+
.monitor-body {
|
|
300
|
+
padding: 8px;
|
|
301
|
+
max-height: 240px;
|
|
302
|
+
overflow-y: auto;
|
|
303
|
+
}
|
|
304
|
+
.monitor-item {
|
|
305
|
+
display: flex;
|
|
306
|
+
justify-content: space-between;
|
|
307
|
+
align-items: center;
|
|
308
|
+
padding: 10px 12px;
|
|
309
|
+
border-radius: 6px;
|
|
310
|
+
font-size: 13px;
|
|
311
|
+
margin-bottom: 2px;
|
|
312
|
+
}
|
|
313
|
+
.monitor-item:hover {
|
|
314
|
+
background: var(--bg);
|
|
315
|
+
}
|
|
316
|
+
.monitor-item:last-child { margin-bottom: 0; }
|
|
317
|
+
.item-label {
|
|
318
|
+
font-weight: 500;
|
|
319
|
+
color: var(--fg);
|
|
320
|
+
}
|
|
321
|
+
.item-detail {
|
|
322
|
+
font-size: 12px;
|
|
323
|
+
color: var(--muted);
|
|
324
|
+
}
|
|
325
|
+
.item-meta {
|
|
326
|
+
display: flex;
|
|
327
|
+
align-items: center;
|
|
328
|
+
gap: 12px;
|
|
329
|
+
}
|
|
330
|
+
.tag {
|
|
331
|
+
font-size: 10px;
|
|
332
|
+
padding: 2px 8px;
|
|
333
|
+
border-radius: 4px;
|
|
334
|
+
font-weight: 500;
|
|
335
|
+
text-transform: uppercase;
|
|
336
|
+
letter-spacing: 0.02em;
|
|
337
|
+
}
|
|
338
|
+
.tag-success { background: rgba(22,163,74,0.1); color: var(--success); }
|
|
339
|
+
.tag-warning { background: rgba(202,138,4,0.1); color: var(--warning); }
|
|
340
|
+
.tag-destructive { background: rgba(220,38,38,0.1); color: var(--destructive); }
|
|
341
|
+
.empty {
|
|
342
|
+
padding: 24px;
|
|
343
|
+
text-align: center;
|
|
344
|
+
color: var(--dim);
|
|
345
|
+
font-size: 13px;
|
|
346
|
+
}
|
|
347
|
+
.btn-sm {
|
|
348
|
+
padding: 4px 10px;
|
|
349
|
+
font-size: 11px;
|
|
350
|
+
border: 1px solid var(--border);
|
|
351
|
+
border-radius: 4px;
|
|
352
|
+
background: var(--card);
|
|
353
|
+
cursor: pointer;
|
|
354
|
+
}
|
|
355
|
+
.btn-sm:hover {
|
|
356
|
+
background: var(--bg);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/* Status Card */
|
|
360
|
+
.status-grid {
|
|
361
|
+
display: grid;
|
|
362
|
+
grid-template-columns: repeat(2, 1fr);
|
|
363
|
+
gap: 2px;
|
|
364
|
+
background: var(--border);
|
|
365
|
+
border-radius: 6px;
|
|
366
|
+
overflow: hidden;
|
|
367
|
+
margin-bottom: 24px;
|
|
368
|
+
}
|
|
369
|
+
.status-item {
|
|
370
|
+
background: var(--card);
|
|
371
|
+
padding: 14px 16px;
|
|
372
|
+
display: flex;
|
|
373
|
+
justify-content: space-between;
|
|
374
|
+
align-items: center;
|
|
375
|
+
}
|
|
376
|
+
.status-item-label {
|
|
377
|
+
font-size: 13px;
|
|
378
|
+
color: var(--muted);
|
|
379
|
+
}
|
|
380
|
+
.status-item-value {
|
|
381
|
+
font-size: 13px;
|
|
382
|
+
font-weight: 500;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/* Events */
|
|
386
|
+
.events-card {
|
|
387
|
+
background: var(--card);
|
|
388
|
+
border: 1px solid var(--border);
|
|
389
|
+
border-radius: 8px;
|
|
390
|
+
overflow: hidden;
|
|
391
|
+
}
|
|
392
|
+
.events-body {
|
|
393
|
+
max-height: 400px;
|
|
394
|
+
overflow-y: auto;
|
|
395
|
+
}
|
|
396
|
+
.event-item {
|
|
397
|
+
display: flex;
|
|
398
|
+
gap: 16px;
|
|
399
|
+
padding: 10px 16px;
|
|
400
|
+
font-family: 'SF Mono', 'Consolas', 'Monaco', monospace;
|
|
401
|
+
font-size: 12px;
|
|
402
|
+
border-bottom: 1px solid var(--border);
|
|
403
|
+
}
|
|
404
|
+
.event-item:last-child { border-bottom: none; }
|
|
405
|
+
.event-time {
|
|
406
|
+
color: var(--dim);
|
|
407
|
+
min-width: 72px;
|
|
408
|
+
flex-shrink: 0;
|
|
409
|
+
}
|
|
410
|
+
.event-msg {
|
|
411
|
+
color: var(--fg);
|
|
412
|
+
flex: 1;
|
|
413
|
+
word-break: break-word;
|
|
414
|
+
}
|
|
415
|
+
.event-msg.ok { color: var(--success); }
|
|
416
|
+
.event-msg.err { color: var(--destructive); }
|
|
417
|
+
.event-msg.warn { color: var(--warning); }
|
|
418
|
+
.event-msg.dim { color: var(--dim); }
|
|
419
|
+
</style>
|
|
420
|
+
</head>
|
|
421
|
+
<body>
|
|
422
|
+
<header class="header">
|
|
423
|
+
<div class="header-title">Firewall</div>
|
|
424
|
+
<div class="header-center">
|
|
425
|
+
<button class="mode-btn active" id="mode-observe">Observe</button>
|
|
426
|
+
<button class="mode-btn" id="mode-protect">Protect</button>
|
|
427
|
+
</div>
|
|
428
|
+
<div class="header-actions">
|
|
429
|
+
<button class="btn btn-destructive" id="btn-stop">Stop</button>
|
|
430
|
+
<button class="btn btn-primary" id="btn-resume" style="display:none">Resume</button>
|
|
431
|
+
</div>
|
|
432
|
+
</header>
|
|
433
|
+
|
|
434
|
+
<main class="main">
|
|
435
|
+
<div class="stats-row">
|
|
436
|
+
<div class="stat-card">
|
|
437
|
+
<div class="stat-label">Today Tokens</div>
|
|
438
|
+
<div class="stat-value" id="stat-tokens">-</div>
|
|
439
|
+
</div>
|
|
440
|
+
<div class="stat-card">
|
|
441
|
+
<div class="stat-label">Blocked</div>
|
|
442
|
+
<div class="stat-value" id="stat-blocked">-</div>
|
|
443
|
+
</div>
|
|
444
|
+
<div class="stat-card">
|
|
445
|
+
<div class="stat-label">Limit</div>
|
|
446
|
+
<div class="stat-value" id="stat-limit">-</div>
|
|
447
|
+
</div>
|
|
448
|
+
<div class="stat-card">
|
|
449
|
+
<div class="stat-label">Calls</div>
|
|
450
|
+
<div class="stat-value" id="stat-calls">-</div>
|
|
451
|
+
</div>
|
|
452
|
+
</div>
|
|
453
|
+
|
|
454
|
+
<div class="section">
|
|
455
|
+
<div class="section-title">Rules</div>
|
|
456
|
+
<div class="rules-grid">
|
|
457
|
+
<div class="rule-card">
|
|
458
|
+
<div class="rule-header">
|
|
459
|
+
<span class="rule-title">Daily Token Limit</span>
|
|
460
|
+
<label class="switch">
|
|
461
|
+
<input type="checkbox" id="switch-daily-limit">
|
|
462
|
+
<span class="slider"></span>
|
|
463
|
+
</label>
|
|
464
|
+
</div>
|
|
465
|
+
<div class="rule-content">
|
|
466
|
+
<div class="field-row">
|
|
467
|
+
<input type="number" class="field-input" id="input-daily-limit" placeholder="50000">
|
|
468
|
+
<span class="field-unit">tokens</span>
|
|
469
|
+
</div>
|
|
470
|
+
<div class="field-hint">Maximum tokens per day</div>
|
|
471
|
+
</div>
|
|
472
|
+
<div class="rule-footer">
|
|
473
|
+
<button class="btn-save" id="btn-save-daily-limit">Save</button>
|
|
474
|
+
</div>
|
|
475
|
+
</div>
|
|
476
|
+
|
|
477
|
+
<div class="rule-card">
|
|
478
|
+
<div class="rule-header">
|
|
479
|
+
<span class="rule-title">Consecutive Failures</span>
|
|
480
|
+
<label class="switch">
|
|
481
|
+
<input type="checkbox" id="switch-failures">
|
|
482
|
+
<span class="slider"></span>
|
|
483
|
+
</label>
|
|
484
|
+
</div>
|
|
485
|
+
<div class="rule-content">
|
|
486
|
+
<div class="field-row">
|
|
487
|
+
<input type="number" class="field-input" id="input-failures" placeholder="3">
|
|
488
|
+
<span class="field-unit">failures</span>
|
|
489
|
+
</div>
|
|
490
|
+
<div class="field-row">
|
|
491
|
+
<input type="number" class="field-input" id="input-cooldown" placeholder="30">
|
|
492
|
+
<span class="field-unit">sec</span>
|
|
493
|
+
</div>
|
|
494
|
+
<div class="field-hint">Cooldown after consecutive failures</div>
|
|
495
|
+
</div>
|
|
496
|
+
<div class="rule-footer">
|
|
497
|
+
<button class="btn-save" id="btn-save-failures">Save</button>
|
|
498
|
+
</div>
|
|
499
|
+
</div>
|
|
500
|
+
|
|
501
|
+
<div class="rule-card">
|
|
502
|
+
<div class="rule-header">
|
|
503
|
+
<span class="rule-title">Token Velocity</span>
|
|
504
|
+
<label class="switch">
|
|
505
|
+
<input type="checkbox" id="switch-velocity">
|
|
506
|
+
<span class="slider"></span>
|
|
507
|
+
</label>
|
|
508
|
+
</div>
|
|
509
|
+
<div class="rule-content">
|
|
510
|
+
<div class="field-row">
|
|
511
|
+
<input type="number" class="field-input" id="input-velocity" placeholder="1000">
|
|
512
|
+
<span class="field-unit">tokens</span>
|
|
513
|
+
</div>
|
|
514
|
+
<div class="field-row">
|
|
515
|
+
<input type="number" class="field-input" id="input-velocity-window" placeholder="60">
|
|
516
|
+
<span class="field-unit">sec</span>
|
|
517
|
+
</div>
|
|
518
|
+
<div class="field-hint">Max tokens within time window</div>
|
|
519
|
+
</div>
|
|
520
|
+
<div class="rule-footer">
|
|
521
|
+
<button class="btn-save" id="btn-save-velocity">Save</button>
|
|
522
|
+
</div>
|
|
523
|
+
</div>
|
|
524
|
+
|
|
525
|
+
<div class="rule-card">
|
|
526
|
+
<div class="rule-header">
|
|
527
|
+
<span class="rule-title">Call Frequency</span>
|
|
528
|
+
<label class="switch">
|
|
529
|
+
<input type="checkbox" id="switch-frequency">
|
|
530
|
+
<span class="slider"></span>
|
|
531
|
+
</label>
|
|
532
|
+
</div>
|
|
533
|
+
<div class="rule-content">
|
|
534
|
+
<div class="field-row">
|
|
535
|
+
<input type="number" class="field-input" id="input-frequency" placeholder="100">
|
|
536
|
+
<span class="field-unit">calls</span>
|
|
537
|
+
</div>
|
|
538
|
+
<div class="field-row">
|
|
539
|
+
<input type="number" class="field-input" id="input-frequency-window" placeholder="60">
|
|
540
|
+
<span class="field-unit">sec</span>
|
|
541
|
+
</div>
|
|
542
|
+
<div class="field-hint">Max calls within time window</div>
|
|
543
|
+
</div>
|
|
544
|
+
<div class="rule-footer">
|
|
545
|
+
<button class="btn-save" id="btn-save-frequency">Save</button>
|
|
546
|
+
</div>
|
|
547
|
+
</div>
|
|
548
|
+
</div>
|
|
549
|
+
</div>
|
|
550
|
+
|
|
551
|
+
<div class="section">
|
|
552
|
+
<div class="section-title">Monitoring</div>
|
|
553
|
+
<div class="status-grid">
|
|
554
|
+
<div class="status-item">
|
|
555
|
+
<span class="status-item-label">Mode</span>
|
|
556
|
+
<span class="status-item-value" id="status-mode">-</span>
|
|
557
|
+
</div>
|
|
558
|
+
<div class="status-item">
|
|
559
|
+
<span class="status-item-label">Status</span>
|
|
560
|
+
<span class="status-item-value" id="status-state">-</span>
|
|
561
|
+
</div>
|
|
562
|
+
<div class="status-item">
|
|
563
|
+
<span class="status-item-label">Token Limit</span>
|
|
564
|
+
<span class="status-item-value" id="status-limit">-</span>
|
|
565
|
+
</div>
|
|
566
|
+
<div class="status-item">
|
|
567
|
+
<span class="status-item-label">Today</span>
|
|
568
|
+
<span class="status-item-value" id="status-today">-</span>
|
|
569
|
+
</div>
|
|
570
|
+
</div>
|
|
571
|
+
<div class="monitoring-grid">
|
|
572
|
+
<div class="monitor-card">
|
|
573
|
+
<div class="monitor-header">Cooling Sources</div>
|
|
574
|
+
<div class="monitor-body" id="list-cooling">
|
|
575
|
+
<div class="empty">No cooling sources</div>
|
|
576
|
+
</div>
|
|
577
|
+
</div>
|
|
578
|
+
<div class="monitor-card">
|
|
579
|
+
<div class="monitor-header">Active Runs</div>
|
|
580
|
+
<div class="monitor-body" id="list-runs">
|
|
581
|
+
<div class="empty">No active runs</div>
|
|
582
|
+
</div>
|
|
583
|
+
</div>
|
|
584
|
+
</div>
|
|
585
|
+
</div>
|
|
586
|
+
|
|
587
|
+
<div class="section">
|
|
588
|
+
<div class="section-title">Events</div>
|
|
589
|
+
<div class="events-card">
|
|
590
|
+
<div class="events-body" id="events-log">
|
|
591
|
+
<div class="empty">No events</div>
|
|
592
|
+
</div>
|
|
593
|
+
</div>
|
|
594
|
+
</div>
|
|
595
|
+
</main>
|
|
596
|
+
|
|
597
|
+
<script>
|
|
598
|
+
let _saving = false;
|
|
599
|
+
let _refreshTimer = null;
|
|
600
|
+
|
|
601
|
+
function isFocused(id) {
|
|
602
|
+
var el = document.getElementById(id);
|
|
603
|
+
return el && document.activeElement === el;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function safeSetValue(id, value) {
|
|
607
|
+
if (isFocused(id)) return;
|
|
608
|
+
var el = document.getElementById(id);
|
|
609
|
+
if (el) el.value = value ?? '';
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function escapeHtml(str) {
|
|
613
|
+
if (str == null) return '';
|
|
614
|
+
return String(str)
|
|
615
|
+
.replace(/&/g, '&')
|
|
616
|
+
.replace(/</g, '<')
|
|
617
|
+
.replace(/>/g, '>')
|
|
618
|
+
.replace(/"/g, '"')
|
|
619
|
+
.replace(/\'/g, ''');
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
async function fetchStats() {
|
|
623
|
+
if (_saving) return;
|
|
624
|
+
try {
|
|
625
|
+
const res = await fetch('/mapick/api/stats');
|
|
626
|
+
const data = await res.json();
|
|
627
|
+
updateUI(data);
|
|
628
|
+
} catch (e) {
|
|
629
|
+
console.error('Failed to fetch stats:', e);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
async function saveConfig(body) {
|
|
634
|
+
_saving = true;
|
|
635
|
+
try {
|
|
636
|
+
await fetch('/mapick/api/config', {
|
|
637
|
+
method: 'POST',
|
|
638
|
+
headers: { 'Content-Type': 'application/json' },
|
|
639
|
+
body: JSON.stringify(body)
|
|
640
|
+
});
|
|
641
|
+
} finally {
|
|
642
|
+
_saving = false;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
async function setMode(m) {
|
|
647
|
+
await saveConfig({ mode: m });
|
|
648
|
+
fetchStats();
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
async function emergencyStop() {
|
|
652
|
+
await fetch('/mapick/api/stop');
|
|
653
|
+
fetchStats();
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
async function resume() {
|
|
657
|
+
await fetch('/mapick/api/resume');
|
|
658
|
+
fetchStats();
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
async function resetSource(src) {
|
|
662
|
+
await fetch('/mapick/api/reset-source?source=' + encodeURIComponent(src));
|
|
663
|
+
fetchStats();
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
async function fetchEvents() {
|
|
667
|
+
try {
|
|
668
|
+
const res = await fetch('/mapick/api/events');
|
|
669
|
+
const events = await res.json();
|
|
670
|
+
renderEvents(events);
|
|
671
|
+
} catch (e) {
|
|
672
|
+
console.error('Failed to fetch events:', e);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function updateUI(data) {
|
|
677
|
+
document.getElementById('stat-tokens').textContent = (data.today_tokens ?? 0).toLocaleString();
|
|
678
|
+
document.getElementById('stat-blocked').textContent = data.today_blocked ?? 0;
|
|
679
|
+
document.getElementById('stat-limit').textContent = data.daily_token_limit ? data.daily_token_limit.toLocaleString() : '∞';
|
|
680
|
+
document.getElementById('stat-calls').textContent = (data.active_runs ?? []).length;
|
|
681
|
+
|
|
682
|
+
const modeObserve = document.getElementById('mode-observe');
|
|
683
|
+
const modeProtect = document.getElementById('mode-protect');
|
|
684
|
+
if (data.mode === 'protect') {
|
|
685
|
+
modeProtect.classList.add('active');
|
|
686
|
+
modeObserve.classList.remove('active');
|
|
687
|
+
} else {
|
|
688
|
+
modeObserve.classList.add('active');
|
|
689
|
+
modeProtect.classList.remove('active');
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const btnStop = document.getElementById('btn-stop');
|
|
693
|
+
const btnResume = document.getElementById('btn-resume');
|
|
694
|
+
if (data.emergency_stop) {
|
|
695
|
+
btnStop.style.display = 'none';
|
|
696
|
+
btnResume.style.display = 'block';
|
|
697
|
+
} else {
|
|
698
|
+
btnStop.style.display = 'block';
|
|
699
|
+
btnResume.style.display = 'none';
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
const breaker = data.breaker ?? {};
|
|
703
|
+
|
|
704
|
+
const switchDailyLimit = document.getElementById('switch-daily-limit');
|
|
705
|
+
const hasDailyLimit = data.daily_token_limit != null && data.daily_token_limit > 0;
|
|
706
|
+
switchDailyLimit.checked = hasDailyLimit;
|
|
707
|
+
safeSetValue('input-daily-limit', data.daily_token_limit);
|
|
708
|
+
|
|
709
|
+
const switchFailures = document.getElementById('switch-failures');
|
|
710
|
+
const hasFailures = breaker.consecutive_failures > 0;
|
|
711
|
+
switchFailures.checked = hasFailures;
|
|
712
|
+
safeSetValue('input-failures', breaker.consecutive_failures);
|
|
713
|
+
safeSetValue('input-cooldown', breaker.cooldown_sec);
|
|
714
|
+
|
|
715
|
+
const switchVelocity = document.getElementById('switch-velocity');
|
|
716
|
+
const hasVelocity = breaker.token_velocity_threshold > 0;
|
|
717
|
+
switchVelocity.checked = hasVelocity;
|
|
718
|
+
safeSetValue('input-velocity', breaker.token_velocity_threshold);
|
|
719
|
+
safeSetValue('input-velocity-window', breaker.token_velocity_window_sec);
|
|
720
|
+
|
|
721
|
+
const switchFrequency = document.getElementById('switch-frequency');
|
|
722
|
+
const hasFrequency = breaker.call_frequency_threshold > 0;
|
|
723
|
+
switchFrequency.checked = hasFrequency;
|
|
724
|
+
safeSetValue('input-frequency', breaker.call_frequency_threshold);
|
|
725
|
+
safeSetValue('input-frequency-window', breaker.call_frequency_window_sec);
|
|
726
|
+
|
|
727
|
+
const modeLabel = data.mode ?? 'observe';
|
|
728
|
+
const estop = data.emergency_stop;
|
|
729
|
+
document.getElementById('status-mode').innerHTML = estop
|
|
730
|
+
? '<span class="tag tag-destructive">STOPPED</span>'
|
|
731
|
+
: modeLabel === 'protect'
|
|
732
|
+
? '<span class="tag tag-warning">Protect</span>'
|
|
733
|
+
: '<span class="tag tag-success">Observe</span>';
|
|
734
|
+
document.getElementById('status-state').textContent = estop ? 'Emergency Stop' : 'Normal';
|
|
735
|
+
document.getElementById('status-limit').textContent = data.daily_token_limit ? data.daily_token_limit.toLocaleString() : '∞';
|
|
736
|
+
document.getElementById('status-today').textContent = (data.today_tokens ?? 0).toLocaleString() + ' tokens | ' + (data.today_blocked ?? 0) + ' blocked';
|
|
737
|
+
|
|
738
|
+
const coolingSources = data.cooling_sources ?? [];
|
|
739
|
+
const coolingList = document.getElementById('list-cooling');
|
|
740
|
+
if (coolingSources.length > 0) {
|
|
741
|
+
coolingList.innerHTML = coolingSources.map(s => \`
|
|
742
|
+
<div class="monitor-item">
|
|
743
|
+
<div>
|
|
744
|
+
<div class="item-label">\${escapeHtml(s.source ?? '')}</div>
|
|
745
|
+
<div class="item-detail">\${escapeHtml(s.reason ?? '')}</div>
|
|
746
|
+
</div>
|
|
747
|
+
<div class="item-meta">
|
|
748
|
+
\${s.remainingSec > 0 ? '<span style="font-size:12px;color:var(--muted)">\${s.remainingSec}s</span>' : ''}
|
|
749
|
+
<button class="btn-sm" onclick="resetSource('\${escapeHtml(s.source ?? '')}')">Reset</button>
|
|
750
|
+
</div>
|
|
751
|
+
</div>
|
|
752
|
+
\`).join('');
|
|
753
|
+
} else {
|
|
754
|
+
coolingList.innerHTML = '<div class="empty">No cooling sources</div>';
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
const activeRuns = data.active_runs ?? [];
|
|
758
|
+
const runsList = document.getElementById('list-runs');
|
|
759
|
+
if (activeRuns.length > 0) {
|
|
760
|
+
runsList.innerHTML = activeRuns.map(r => \`
|
|
761
|
+
<div class="monitor-item">
|
|
762
|
+
<div>
|
|
763
|
+
<div class="item-label">\${escapeHtml((r.runId ?? '').slice(0, 8))}</div>
|
|
764
|
+
<div class="item-detail">\${escapeHtml(r.source ?? '')} · \${r.calls ?? 0} calls · \${(r.tokens ?? 0).toLocaleString()} tokens</div>
|
|
765
|
+
</div>
|
|
766
|
+
<span class="tag \${r.status === 'danger' ? 'tag-destructive' : r.status === 'warning' ? 'tag-warning' : 'tag-success'}">\${escapeHtml(r.status ?? 'healthy')}</span>
|
|
767
|
+
</div>
|
|
768
|
+
\`).join('');
|
|
769
|
+
} else {
|
|
770
|
+
runsList.innerHTML = '<div class="empty">No active runs</div>';
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function renderEvents(events) {
|
|
775
|
+
const log = document.getElementById('events-log');
|
|
776
|
+
if (!events ?? events.length === 0) {
|
|
777
|
+
log.innerHTML = '<div class="empty">No events</div>';
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
log.innerHTML = events.slice(0, 100).map(e => {
|
|
781
|
+
const ts = e.timestamp ?? e.time;
|
|
782
|
+
const date = ts ? new Date(ts) : null;
|
|
783
|
+
const timeStr = date ? date.toTimeString().slice(0, 8) : '--:--:--';
|
|
784
|
+
const type = e.type ?? 'unknown';
|
|
785
|
+
let cls = '';
|
|
786
|
+
let text = '';
|
|
787
|
+
|
|
788
|
+
if (type === 'model_call_ended') {
|
|
789
|
+
const provider = e.provider ?? 'unknown';
|
|
790
|
+
const model = e.model ?? 'unknown';
|
|
791
|
+
const outcome = e.outcome ?? 'completed';
|
|
792
|
+
const cost = e.estimatedCost ?? 0;
|
|
793
|
+
const costK = (cost / 1000).toFixed(1);
|
|
794
|
+
if (outcome === 'completed') {
|
|
795
|
+
cls = 'ok';
|
|
796
|
+
text = provider + '/' + model + ' completed — ' + costK + 'K tokens';
|
|
797
|
+
} else {
|
|
798
|
+
cls = 'err';
|
|
799
|
+
text = provider + '/' + model + ' error (' + outcome + ')';
|
|
800
|
+
}
|
|
801
|
+
} else if (type === 'blocked') {
|
|
802
|
+
cls = 'err';
|
|
803
|
+
const source = e.source ?? 'unknown';
|
|
804
|
+
const reason = e.reason ?? 'unknown';
|
|
805
|
+
text = 'BLOCKED ' + source + ' — ' + reason;
|
|
806
|
+
} else if (type === 'run_status_change') {
|
|
807
|
+
cls = 'warn';
|
|
808
|
+
const runId = e.runId ?? 'unknown';
|
|
809
|
+
const status = e.status ?? 'unknown';
|
|
810
|
+
const tokens = e.cumulativeTokens ?? 0;
|
|
811
|
+
const calls = e.runCalls ?? 0;
|
|
812
|
+
text = 'Run ' + runId + ' → ' + status + ' (' + tokens.toLocaleString() + ' tokens, ' + calls + ' calls)';
|
|
813
|
+
} else if (type === 'agent_end') {
|
|
814
|
+
cls = 'dim';
|
|
815
|
+
const runId = e.runId ?? 'unknown';
|
|
816
|
+
text = 'Run ' + runId + ' ended';
|
|
817
|
+
} else if (type === 'config_warning') {
|
|
818
|
+
cls = 'dim';
|
|
819
|
+
text = 'Config warning: ' + (e.message ?? e.msg ?? 'unknown');
|
|
820
|
+
} else if (type === 'zero_output_warning') {
|
|
821
|
+
cls = 'warn';
|
|
822
|
+
const provider = e.provider ?? 'unknown';
|
|
823
|
+
const model = e.model ?? 'unknown';
|
|
824
|
+
text = 'Zero output: ' + provider + '/' + model + ' — 0 bytes';
|
|
825
|
+
} else {
|
|
826
|
+
cls = 'dim';
|
|
827
|
+
text = e.message ?? e.msg ?? JSON.stringify(e);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
return '<div class="event-item">' +
|
|
831
|
+
'<span class="event-time">' + escapeHtml(timeStr) + '</span>' +
|
|
832
|
+
'<span class="event-msg ' + cls + '">' + escapeHtml(text) + '</span>' +
|
|
833
|
+
'</div>';
|
|
834
|
+
}).join('');
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
document.getElementById('mode-observe').addEventListener('click', () => setMode('observe'));
|
|
838
|
+
document.getElementById('mode-protect').addEventListener('click', () => setMode('protect'));
|
|
839
|
+
document.getElementById('btn-stop').addEventListener('click', emergencyStop);
|
|
840
|
+
document.getElementById('btn-resume').addEventListener('click', resume);
|
|
841
|
+
|
|
842
|
+
document.getElementById('switch-daily-limit').addEventListener('change', function() {
|
|
843
|
+
const val = this.checked ? parseInt(document.getElementById('input-daily-limit').value) || 50000 : null;
|
|
844
|
+
saveConfig({ dailyTokenLimit: val });
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
document.getElementById('switch-failures').addEventListener('change', function() {
|
|
848
|
+
const failures = this.checked ? parseInt(document.getElementById('input-failures').value) || 3 : 0;
|
|
849
|
+
const cooldown = parseInt(document.getElementById('input-cooldown').value) || 30;
|
|
850
|
+
saveConfig({ breaker: { consecutiveFailures: failures, cooldownSec: cooldown } });
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
document.getElementById('switch-velocity').addEventListener('change', function() {
|
|
854
|
+
const threshold = this.checked ? parseInt(document.getElementById('input-velocity').value) || 1000 : 0;
|
|
855
|
+
const window = parseInt(document.getElementById('input-velocity-window').value) || 60;
|
|
856
|
+
saveConfig({ breaker: { tokenVelocityThreshold: threshold, tokenVelocityWindowSec: window } });
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
document.getElementById('switch-frequency').addEventListener('change', function() {
|
|
860
|
+
const threshold = this.checked ? parseInt(document.getElementById('input-frequency').value) || 100 : 0;
|
|
861
|
+
const window = parseInt(document.getElementById('input-frequency-window').value) || 60;
|
|
862
|
+
saveConfig({ breaker: { callFrequencyThreshold: threshold, callFrequencyWindowSec: window } });
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
document.getElementById('btn-save-daily-limit').addEventListener('click', function() {
|
|
866
|
+
const val = parseInt(document.getElementById('input-daily-limit').value) || null;
|
|
867
|
+
saveConfig({ dailyTokenLimit: val });
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
document.getElementById('btn-save-failures').addEventListener('click', function() {
|
|
871
|
+
const failures = parseInt(document.getElementById('input-failures').value) || 0;
|
|
872
|
+
const cooldown = parseInt(document.getElementById('input-cooldown').value) || 30;
|
|
873
|
+
saveConfig({ breaker: { consecutiveFailures: failures, cooldownSec: cooldown } });
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
document.getElementById('btn-save-velocity').addEventListener('click', function() {
|
|
877
|
+
const threshold = parseInt(document.getElementById('input-velocity').value) || 0;
|
|
878
|
+
const window = parseInt(document.getElementById('input-velocity-window').value) || 60;
|
|
879
|
+
saveConfig({ breaker: { tokenVelocityThreshold: threshold, tokenVelocityWindowSec: window } });
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
document.getElementById('btn-save-frequency').addEventListener('click', function() {
|
|
883
|
+
const threshold = parseInt(document.getElementById('input-frequency').value) || 0;
|
|
884
|
+
const window = parseInt(document.getElementById('input-frequency-window').value) || 60;
|
|
885
|
+
saveConfig({ breaker: { callFrequencyThreshold: threshold, callFrequencyWindowSec: window } });
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
fetchStats();
|
|
889
|
+
fetchEvents();
|
|
890
|
+
_refreshTimer = setInterval(() => {
|
|
891
|
+
fetchStats();
|
|
892
|
+
fetchEvents();
|
|
893
|
+
}, 3000);
|
|
894
|
+
</script>
|
|
895
|
+
</body>
|
|
896
|
+
</html>`;
|
|
897
|
+
}
|
|
898
|
+
//# sourceMappingURL=html.js.map
|