@agent-e/server 1.7.2 → 1.8.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/dist/AgentEServer-O3DNPERG.mjs +7 -0
- package/dist/{chunk-P4DO66PX.mjs → chunk-XFI27OM4.mjs} +912 -399
- package/dist/chunk-XFI27OM4.mjs.map +1 -0
- package/dist/cli.js +911 -398
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/index.js +911 -398
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/package.json +2 -2
- package/dist/AgentEServer-4LVJONWI.mjs +0 -7
- package/dist/chunk-P4DO66PX.mjs.map +0 -1
- /package/dist/{AgentEServer-4LVJONWI.mjs.map → AgentEServer-O3DNPERG.mjs.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -41,96 +41,109 @@ function getDashboardHtml() {
|
|
|
41
41
|
<title>AgentE Dashboard</title>
|
|
42
42
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
43
43
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
44
|
-
<link href="https://fonts.googleapis.com/css2?family=
|
|
44
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,400;14..32,500;14..32,600;14..32,700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
45
45
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js"></script>
|
|
46
46
|
<style>
|
|
47
|
+
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
|
48
|
+
|
|
47
49
|
:root {
|
|
48
50
|
--bg-root: #09090b;
|
|
49
51
|
--bg-panel: #18181b;
|
|
50
|
-
--bg-
|
|
52
|
+
--bg-terminal: #09090b;
|
|
51
53
|
--border: #27272a;
|
|
52
|
-
--
|
|
53
|
-
--text-
|
|
54
|
-
--text-
|
|
55
|
-
--text-
|
|
56
|
-
--text-dim: #52525b;
|
|
54
|
+
--text-primary: #ffffff;
|
|
55
|
+
--text-secondary: #52525b;
|
|
56
|
+
--text-tertiary: #a1a1aa;
|
|
57
|
+
--text-value: #d4d4d8;
|
|
57
58
|
--accent: #22c55e;
|
|
58
|
-
--accent-dim: #166534;
|
|
59
59
|
--warning: #eab308;
|
|
60
|
-
--warning-dim: #854d0e;
|
|
61
60
|
--danger: #ef4444;
|
|
62
|
-
--
|
|
63
|
-
--blue: #3b82f6;
|
|
64
|
-
--font-sans: 'IBM Plex Sans', system-ui, sans-serif;
|
|
65
|
-
--font-mono: 'JetBrains Mono', monospace;
|
|
61
|
+
--info: #71717a;
|
|
66
62
|
}
|
|
67
63
|
|
|
68
|
-
|
|
64
|
+
html { scroll-behavior: smooth; }
|
|
69
65
|
|
|
70
66
|
body {
|
|
71
67
|
background: var(--bg-root);
|
|
72
68
|
color: var(--text-primary);
|
|
73
|
-
font-family:
|
|
74
|
-
font-
|
|
69
|
+
font-family: 'Inter', system-ui, sans-serif;
|
|
70
|
+
-webkit-font-smoothing: antialiased;
|
|
75
71
|
line-height: 1.5;
|
|
76
|
-
|
|
72
|
+
min-height: 100dvh;
|
|
73
|
+
overflow-y: auto;
|
|
77
74
|
}
|
|
78
75
|
|
|
79
|
-
/*
|
|
76
|
+
/* -- Header -- */
|
|
80
77
|
.header {
|
|
81
78
|
position: sticky;
|
|
82
79
|
top: 0;
|
|
83
|
-
z-index:
|
|
84
|
-
background: var(--bg-
|
|
80
|
+
z-index: 50;
|
|
81
|
+
background: var(--bg-panel);
|
|
85
82
|
border-bottom: 1px solid var(--border);
|
|
86
83
|
padding: 12px 24px;
|
|
87
84
|
display: flex;
|
|
88
85
|
align-items: center;
|
|
89
|
-
|
|
90
|
-
|
|
86
|
+
justify-content: space-between;
|
|
87
|
+
gap: 16px;
|
|
91
88
|
}
|
|
92
89
|
|
|
93
|
-
.header-
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
.header-left {
|
|
91
|
+
display: flex;
|
|
92
|
+
align-items: center;
|
|
93
|
+
gap: 10px;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.header-logo {
|
|
97
|
+
font-family: 'JetBrains Mono', monospace;
|
|
98
|
+
font-size: 15px;
|
|
99
|
+
font-weight: 500;
|
|
96
100
|
color: var(--text-primary);
|
|
97
|
-
white-space: nowrap;
|
|
98
101
|
}
|
|
99
102
|
|
|
100
|
-
.header-
|
|
103
|
+
.header-version {
|
|
104
|
+
font-family: 'JetBrains Mono', monospace;
|
|
105
|
+
font-size: 11px;
|
|
106
|
+
color: var(--text-secondary);
|
|
107
|
+
background: var(--bg-root);
|
|
108
|
+
padding: 2px 8px;
|
|
109
|
+
border-radius: 4px;
|
|
110
|
+
}
|
|
101
111
|
|
|
102
|
-
.
|
|
112
|
+
.header-right {
|
|
103
113
|
display: flex;
|
|
104
|
-
gap: 20px;
|
|
105
|
-
flex-wrap: wrap;
|
|
106
114
|
align-items: center;
|
|
107
|
-
|
|
115
|
+
gap: 20px;
|
|
108
116
|
}
|
|
109
117
|
|
|
110
|
-
.kpi {
|
|
118
|
+
.kpi-pill {
|
|
111
119
|
display: flex;
|
|
112
120
|
align-items: center;
|
|
113
121
|
gap: 6px;
|
|
114
|
-
font-size:
|
|
122
|
+
font-size: 12px;
|
|
123
|
+
font-family: 'JetBrains Mono', monospace;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.kpi-pill .label {
|
|
115
127
|
color: var(--text-secondary);
|
|
128
|
+
text-transform: uppercase;
|
|
129
|
+
letter-spacing: 0.05em;
|
|
116
130
|
}
|
|
117
131
|
|
|
118
|
-
.kpi-value {
|
|
119
|
-
|
|
120
|
-
font-
|
|
121
|
-
color: var(--text-primary);
|
|
122
|
-
font-size: 13px;
|
|
132
|
+
.kpi-pill .value {
|
|
133
|
+
color: var(--text-value);
|
|
134
|
+
font-variant-numeric: tabular-nums;
|
|
123
135
|
}
|
|
124
136
|
|
|
125
|
-
.kpi-value.health-good { color: var(--accent); }
|
|
126
|
-
.kpi-value.health-warn { color: var(--warning); }
|
|
127
|
-
.kpi-value.health-bad { color: var(--danger); }
|
|
137
|
+
.kpi-pill .value.health-good { color: var(--accent); }
|
|
138
|
+
.kpi-pill .value.health-warn { color: var(--warning); }
|
|
139
|
+
.kpi-pill .value.health-bad { color: var(--danger); }
|
|
128
140
|
|
|
129
141
|
.live-dot {
|
|
130
|
-
width:
|
|
131
|
-
height:
|
|
142
|
+
width: 6px;
|
|
143
|
+
height: 6px;
|
|
132
144
|
border-radius: 50%;
|
|
133
145
|
background: var(--accent);
|
|
146
|
+
flex-shrink: 0;
|
|
134
147
|
animation: pulse 2s ease-in-out infinite;
|
|
135
148
|
}
|
|
136
149
|
|
|
@@ -144,82 +157,159 @@ function getDashboardHtml() {
|
|
|
144
157
|
50% { opacity: 0.4; }
|
|
145
158
|
}
|
|
146
159
|
|
|
147
|
-
/*
|
|
148
|
-
.
|
|
160
|
+
/* -- Advisor Banner -- */
|
|
161
|
+
.advisor-banner {
|
|
162
|
+
display: none;
|
|
163
|
+
background: rgba(234,179,8,0.08);
|
|
164
|
+
border-bottom: 1px solid rgba(234,179,8,0.2);
|
|
165
|
+
padding: 8px 24px;
|
|
166
|
+
font-family: 'JetBrains Mono', monospace;
|
|
167
|
+
font-size: 11px;
|
|
168
|
+
color: var(--warning);
|
|
169
|
+
text-align: center;
|
|
170
|
+
letter-spacing: 0.03em;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.advisor-mode .advisor-banner { display: block; }
|
|
174
|
+
|
|
175
|
+
/* -- Advisor-specific: pending pill -- */
|
|
176
|
+
.pending-pill {
|
|
177
|
+
display: none;
|
|
178
|
+
background: rgba(234,179,8,0.15);
|
|
179
|
+
border-radius: 4px;
|
|
180
|
+
padding: 2px 8px;
|
|
181
|
+
cursor: pointer;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.pending-pill:hover { background: rgba(234,179,8,0.25); }
|
|
185
|
+
|
|
186
|
+
.advisor-mode .pending-pill { display: flex; }
|
|
187
|
+
|
|
188
|
+
/* -- Mode value color switching -- */
|
|
189
|
+
.mode-value-auto { color: var(--accent); }
|
|
190
|
+
.mode-value-advisor { color: var(--warning); }
|
|
191
|
+
|
|
192
|
+
/* -- Layout -- */
|
|
193
|
+
.dashboard {
|
|
149
194
|
max-width: 1440px;
|
|
150
195
|
margin: 0 auto;
|
|
151
|
-
padding:
|
|
196
|
+
padding: 16px;
|
|
152
197
|
display: flex;
|
|
153
198
|
flex-direction: column;
|
|
154
|
-
gap:
|
|
199
|
+
gap: 12px;
|
|
155
200
|
}
|
|
156
201
|
|
|
202
|
+
/* -- Panels -- */
|
|
157
203
|
.panel {
|
|
158
204
|
background: var(--bg-panel);
|
|
159
205
|
border: 1px solid var(--border);
|
|
160
206
|
border-radius: 8px;
|
|
161
|
-
padding: 16px;
|
|
207
|
+
padding: 16px 20px;
|
|
208
|
+
min-width: 0;
|
|
209
|
+
display: flex;
|
|
210
|
+
flex-direction: column;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.panel-header {
|
|
214
|
+
display: flex;
|
|
215
|
+
align-items: center;
|
|
216
|
+
justify-content: space-between;
|
|
217
|
+
margin-bottom: 12px;
|
|
218
|
+
flex-shrink: 0;
|
|
162
219
|
}
|
|
163
220
|
|
|
164
221
|
.panel-title {
|
|
165
|
-
font-size:
|
|
222
|
+
font-size: 11px;
|
|
166
223
|
font-weight: 600;
|
|
167
|
-
color: var(--text-secondary);
|
|
168
224
|
text-transform: uppercase;
|
|
169
|
-
letter-spacing: 0.
|
|
170
|
-
|
|
225
|
+
letter-spacing: 0.08em;
|
|
226
|
+
color: var(--info);
|
|
171
227
|
}
|
|
172
228
|
|
|
173
|
-
|
|
174
|
-
|
|
229
|
+
.panel-meta {
|
|
230
|
+
font-size: 11px;
|
|
231
|
+
font-family: 'JetBrains Mono', monospace;
|
|
232
|
+
color: var(--text-secondary);
|
|
233
|
+
display: flex;
|
|
234
|
+
align-items: center;
|
|
235
|
+
gap: 8px;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.panel-meta .live-label {
|
|
239
|
+
color: var(--accent);
|
|
240
|
+
display: flex;
|
|
241
|
+
align-items: center;
|
|
242
|
+
gap: 4px;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.panel-body {
|
|
246
|
+
flex: 1;
|
|
247
|
+
min-height: 0;
|
|
248
|
+
overflow: hidden;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/* -- Health Charts -- */
|
|
252
|
+
.charts-panel .chart-row {
|
|
175
253
|
display: grid;
|
|
176
|
-
grid-template-columns: repeat(
|
|
254
|
+
grid-template-columns: repeat(4, 1fr);
|
|
177
255
|
gap: 16px;
|
|
178
256
|
}
|
|
179
257
|
|
|
180
|
-
.chart
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
258
|
+
.mini-chart { position: relative; }
|
|
259
|
+
.mini-chart-label {
|
|
260
|
+
font-size: 10px;
|
|
261
|
+
font-weight: 600;
|
|
262
|
+
text-transform: uppercase;
|
|
263
|
+
letter-spacing: 0.06em;
|
|
264
|
+
color: var(--text-secondary);
|
|
265
|
+
margin-bottom: 4px;
|
|
185
266
|
}
|
|
267
|
+
.mini-chart canvas { width: 100% !important; height: 64px !important; }
|
|
186
268
|
|
|
187
|
-
|
|
269
|
+
@media (max-width: 900px) {
|
|
270
|
+
.charts-panel .chart-row { grid-template-columns: 1fr 1fr; }
|
|
271
|
+
}
|
|
188
272
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
color: var(--text-muted);
|
|
192
|
-
font-weight: 500;
|
|
193
|
-
margin-bottom: 8px;
|
|
273
|
+
@media (max-width: 500px) {
|
|
274
|
+
.charts-panel .chart-row { grid-template-columns: 1fr; }
|
|
194
275
|
}
|
|
195
276
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
277
|
+
/* -- Terminal Feed -- */
|
|
278
|
+
.terminal-panel {
|
|
279
|
+
border-left: 2px solid var(--accent);
|
|
280
|
+
height: 380px;
|
|
281
|
+
overflow: hidden;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.advisor-mode .terminal-panel {
|
|
285
|
+
border-left-color: var(--warning);
|
|
202
286
|
}
|
|
203
287
|
|
|
204
|
-
/* \u2500\u2500 Terminal (Decision Feed) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
205
288
|
.terminal {
|
|
206
|
-
background: var(--bg-
|
|
207
|
-
border:
|
|
208
|
-
|
|
209
|
-
height:
|
|
289
|
+
background: var(--bg-terminal);
|
|
290
|
+
border-radius: 6px;
|
|
291
|
+
padding: 12px 16px;
|
|
292
|
+
height: 100%;
|
|
210
293
|
overflow-y: auto;
|
|
211
|
-
font-family:
|
|
212
|
-
font-size:
|
|
294
|
+
font-family: 'JetBrains Mono', monospace;
|
|
295
|
+
font-size: 12.5px;
|
|
213
296
|
line-height: 1.7;
|
|
214
|
-
|
|
297
|
+
display: flex;
|
|
298
|
+
flex-direction: column;
|
|
215
299
|
}
|
|
216
300
|
|
|
217
|
-
.terminal
|
|
301
|
+
.terminal-inner {
|
|
302
|
+
margin-top: auto;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.terminal::-webkit-scrollbar { width: 4px; }
|
|
218
306
|
.terminal::-webkit-scrollbar-track { background: transparent; }
|
|
219
|
-
.terminal::-webkit-scrollbar-thumb { background: var(--border
|
|
307
|
+
.terminal::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
|
|
220
308
|
|
|
221
309
|
.term-line {
|
|
222
310
|
white-space: nowrap;
|
|
311
|
+
overflow: hidden;
|
|
312
|
+
text-overflow: ellipsis;
|
|
223
313
|
opacity: 0;
|
|
224
314
|
transform: translateY(4px);
|
|
225
315
|
animation: termIn 0.3s ease-out forwards;
|
|
@@ -229,120 +319,197 @@ function getDashboardHtml() {
|
|
|
229
319
|
to { opacity: 1; transform: translateY(0); }
|
|
230
320
|
}
|
|
231
321
|
|
|
232
|
-
.t-tick { color: var(--text-
|
|
322
|
+
.t-tick { color: var(--text-secondary); }
|
|
233
323
|
.t-ok { color: var(--accent); }
|
|
324
|
+
.t-check { color: var(--accent); }
|
|
234
325
|
.t-skip { color: var(--warning); }
|
|
235
326
|
.t-fail { color: var(--danger); }
|
|
236
327
|
.t-principle { color: var(--text-primary); font-weight: 500; }
|
|
237
|
-
.t-param { color: var(--text-
|
|
238
|
-
.t-old { color:
|
|
239
|
-
.t-arrow { color: var(--
|
|
328
|
+
.t-param { color: var(--text-tertiary); }
|
|
329
|
+
.t-old { color: var(--text-value); font-variant-numeric: tabular-nums; }
|
|
330
|
+
.t-arrow { color: var(--info); }
|
|
240
331
|
.t-new { color: var(--accent); font-variant-numeric: tabular-nums; }
|
|
241
|
-
.t-meta { color: var(--text-
|
|
332
|
+
.t-meta { color: var(--text-secondary); }
|
|
333
|
+
.t-violation-id { color: var(--warning); }
|
|
334
|
+
.t-violation-desc { color: var(--text-tertiary); }
|
|
335
|
+
.t-status-label { color: var(--text-tertiary); }
|
|
336
|
+
.t-status-value { color: var(--accent); font-variant-numeric: tabular-nums; }
|
|
337
|
+
.t-dim { color: var(--text-secondary); }
|
|
338
|
+
.t-white { color: var(--text-primary); }
|
|
339
|
+
.t-separator { color: var(--info); }
|
|
340
|
+
.t-pending-icon { color: var(--warning); }
|
|
341
|
+
.t-pending-val { color: var(--warning); font-variant-numeric: tabular-nums; }
|
|
342
|
+
|
|
343
|
+
/* -- Advisor Inline Buttons -- */
|
|
344
|
+
.advisor-btn {
|
|
345
|
+
display: none;
|
|
346
|
+
font-family: 'JetBrains Mono', monospace;
|
|
347
|
+
font-size: 10px;
|
|
348
|
+
border-radius: 3px;
|
|
349
|
+
padding: 2px 8px;
|
|
350
|
+
cursor: pointer;
|
|
351
|
+
border: 1px solid;
|
|
352
|
+
margin-left: 6px;
|
|
353
|
+
vertical-align: middle;
|
|
354
|
+
line-height: 1.4;
|
|
355
|
+
transition: background 0.15s;
|
|
356
|
+
}
|
|
242
357
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
overflow-y: auto;
|
|
358
|
+
.advisor-mode .advisor-btn { display: inline-flex; align-items: center; }
|
|
359
|
+
|
|
360
|
+
.advisor-btn.approve-btn {
|
|
361
|
+
background: rgba(34,197,94,0.15);
|
|
362
|
+
color: var(--accent);
|
|
363
|
+
border-color: rgba(34,197,94,0.3);
|
|
250
364
|
}
|
|
365
|
+
.advisor-btn.approve-btn:hover { background: rgba(34,197,94,0.25); }
|
|
251
366
|
|
|
252
|
-
.
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
padding: 12px;
|
|
257
|
-
border-radius: 6px;
|
|
258
|
-
border: 1px solid var(--border);
|
|
259
|
-
background: var(--bg-panel);
|
|
260
|
-
transition: opacity 0.3s, transform 0.3s;
|
|
367
|
+
.advisor-btn.reject-btn {
|
|
368
|
+
background: rgba(239,68,68,0.1);
|
|
369
|
+
color: var(--danger);
|
|
370
|
+
border-color: rgba(239,68,68,0.2);
|
|
261
371
|
}
|
|
372
|
+
.advisor-btn.reject-btn:hover { background: rgba(239,68,68,0.2); }
|
|
262
373
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
374
|
+
/* Approved/Rejected flash labels */
|
|
375
|
+
.action-flash {
|
|
376
|
+
font-family: 'JetBrains Mono', monospace;
|
|
377
|
+
font-size: 10px;
|
|
378
|
+
margin-left: 8px;
|
|
379
|
+
animation: flashIn 0.3s ease-out;
|
|
266
380
|
}
|
|
267
381
|
|
|
268
|
-
.
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
white-space: nowrap;
|
|
382
|
+
.action-flash.approved { color: var(--accent); }
|
|
383
|
+
.action-flash.rejected { color: var(--info); }
|
|
384
|
+
|
|
385
|
+
@keyframes flashIn {
|
|
386
|
+
from { opacity: 0; transform: translateX(-4px); }
|
|
387
|
+
to { opacity: 1; transform: translateX(0); }
|
|
275
388
|
}
|
|
276
389
|
|
|
277
|
-
|
|
278
|
-
.
|
|
279
|
-
|
|
390
|
+
/* Dimmed line after rejection */
|
|
391
|
+
.term-line.rejected-line { opacity: 0.5; }
|
|
392
|
+
|
|
393
|
+
/* -- Alerts -- */
|
|
394
|
+
.alert-card {
|
|
395
|
+
background: var(--bg-root);
|
|
396
|
+
border-radius: 6px;
|
|
397
|
+
padding: 10px 14px;
|
|
398
|
+
margin-bottom: 8px;
|
|
399
|
+
border-left: 3px solid transparent;
|
|
400
|
+
overflow: hidden;
|
|
401
|
+
}
|
|
280
402
|
|
|
281
|
-
.alert-
|
|
282
|
-
.alert-
|
|
283
|
-
.alert-
|
|
403
|
+
.alert-card.sev-high { border-left-color: var(--danger); }
|
|
404
|
+
.alert-card.sev-med { border-left-color: var(--warning); }
|
|
405
|
+
.alert-card.sev-low { border-left-color: var(--accent); }
|
|
284
406
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
407
|
+
.alert-top {
|
|
408
|
+
display: flex;
|
|
409
|
+
align-items: center;
|
|
410
|
+
gap: 8px;
|
|
411
|
+
margin-bottom: 4px;
|
|
290
412
|
}
|
|
291
413
|
|
|
292
|
-
.
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
414
|
+
.sev-badge {
|
|
415
|
+
width: 20px;
|
|
416
|
+
height: 20px;
|
|
417
|
+
border-radius: 50%;
|
|
418
|
+
display: flex;
|
|
419
|
+
align-items: center;
|
|
420
|
+
justify-content: center;
|
|
421
|
+
font-size: 10px;
|
|
422
|
+
font-weight: 700;
|
|
423
|
+
font-family: 'JetBrains Mono', monospace;
|
|
424
|
+
color: var(--bg-root);
|
|
425
|
+
flex-shrink: 0;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.sev-badge.high { background: var(--danger); }
|
|
429
|
+
.sev-badge.med { background: var(--warning); }
|
|
430
|
+
.sev-badge.low { background: var(--accent); }
|
|
431
|
+
|
|
432
|
+
.alert-principle-id { color: var(--warning); font-family: 'JetBrains Mono', monospace; font-size: 11px; }
|
|
433
|
+
.alert-principle-name { color: var(--text-primary); font-size: 12px; font-weight: 500; }
|
|
434
|
+
.alert-evidence { color: var(--text-tertiary); font-size: 10px; margin-top: 2px; }
|
|
435
|
+
.alert-suggestion { color: var(--accent); font-size: 10px; margin-top: 2px; }
|
|
436
|
+
|
|
437
|
+
.alert-approve-btn {
|
|
438
|
+
display: none;
|
|
439
|
+
font-family: 'JetBrains Mono', monospace;
|
|
440
|
+
font-size: 10px;
|
|
441
|
+
background: rgba(34,197,94,0.15);
|
|
442
|
+
color: var(--accent);
|
|
443
|
+
border: 1px solid rgba(34,197,94,0.3);
|
|
444
|
+
border-radius: 3px;
|
|
445
|
+
padding: 3px 10px;
|
|
298
446
|
cursor: pointer;
|
|
299
|
-
|
|
447
|
+
margin-top: 8px;
|
|
448
|
+
transition: background 0.15s;
|
|
300
449
|
}
|
|
301
450
|
|
|
302
|
-
.
|
|
451
|
+
.alert-approve-btn:hover { background: rgba(34,197,94,0.25); }
|
|
303
452
|
|
|
304
|
-
.
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
453
|
+
.alert-reject-btn {
|
|
454
|
+
display: none;
|
|
455
|
+
font-family: 'JetBrains Mono', monospace;
|
|
456
|
+
font-size: 10px;
|
|
457
|
+
background: rgba(239,68,68,0.1);
|
|
458
|
+
color: var(--danger);
|
|
459
|
+
border: 1px solid rgba(239,68,68,0.2);
|
|
460
|
+
border-radius: 3px;
|
|
461
|
+
padding: 3px 10px;
|
|
462
|
+
cursor: pointer;
|
|
463
|
+
margin-top: 8px;
|
|
464
|
+
margin-left: 6px;
|
|
465
|
+
transition: background 0.15s;
|
|
310
466
|
}
|
|
311
467
|
|
|
312
|
-
.
|
|
468
|
+
.alert-reject-btn:hover { background: rgba(239,68,68,0.2); }
|
|
313
469
|
|
|
314
|
-
|
|
315
|
-
.
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
470
|
+
.advisor-mode .alert-approve-btn { display: inline-block; }
|
|
471
|
+
.advisor-mode .alert-reject-btn { display: inline-block; }
|
|
472
|
+
|
|
473
|
+
/* Alert resolved state */
|
|
474
|
+
.alert-card.resolved {
|
|
475
|
+
opacity: 0;
|
|
476
|
+
max-height: 0;
|
|
477
|
+
padding: 0 14px;
|
|
478
|
+
margin-bottom: 0;
|
|
479
|
+
overflow: hidden;
|
|
480
|
+
transition: opacity 0.4s ease-out, max-height 0.5s ease-out 0.1s, padding 0.5s ease-out 0.1s, margin 0.5s ease-out 0.1s;
|
|
319
481
|
}
|
|
320
482
|
|
|
321
|
-
|
|
322
|
-
|
|
483
|
+
.alerts-scroll {
|
|
484
|
+
overflow-y: auto;
|
|
485
|
+
max-height: 300px;
|
|
323
486
|
}
|
|
324
487
|
|
|
325
|
-
|
|
326
|
-
.
|
|
488
|
+
.alerts-scroll::-webkit-scrollbar { width: 4px; }
|
|
489
|
+
.alerts-scroll::-webkit-scrollbar-track { background: transparent; }
|
|
490
|
+
.alerts-scroll::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
|
|
327
491
|
|
|
492
|
+
/* -- Persona Bars -- */
|
|
328
493
|
.persona-row {
|
|
329
494
|
display: flex;
|
|
330
495
|
align-items: center;
|
|
331
496
|
gap: 8px;
|
|
332
|
-
|
|
497
|
+
margin-bottom: 5px;
|
|
498
|
+
font-size: 11px;
|
|
499
|
+
font-family: 'JetBrains Mono', monospace;
|
|
333
500
|
}
|
|
334
501
|
|
|
335
502
|
.persona-label {
|
|
336
503
|
width: 100px;
|
|
337
504
|
text-align: right;
|
|
338
|
-
color: var(--text-
|
|
339
|
-
font-size: 11px;
|
|
505
|
+
color: var(--text-tertiary);
|
|
340
506
|
flex-shrink: 0;
|
|
507
|
+
font-variant-numeric: tabular-nums;
|
|
341
508
|
}
|
|
342
509
|
|
|
343
510
|
.persona-bar-track {
|
|
344
511
|
flex: 1;
|
|
345
|
-
height:
|
|
512
|
+
height: 12px;
|
|
346
513
|
background: var(--bg-root);
|
|
347
514
|
border-radius: 3px;
|
|
348
515
|
overflow: hidden;
|
|
@@ -350,166 +517,311 @@ function getDashboardHtml() {
|
|
|
350
517
|
|
|
351
518
|
.persona-bar-fill {
|
|
352
519
|
height: 100%;
|
|
353
|
-
background: var(--accent);
|
|
354
520
|
border-radius: 3px;
|
|
355
|
-
transition: width 0.
|
|
521
|
+
transition: width 0.6s ease-out;
|
|
356
522
|
}
|
|
357
523
|
|
|
358
524
|
.persona-pct {
|
|
359
|
-
width:
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
525
|
+
width: 32px;
|
|
526
|
+
text-align: right;
|
|
527
|
+
color: var(--text-secondary);
|
|
528
|
+
font-variant-numeric: tabular-nums;
|
|
363
529
|
}
|
|
364
530
|
|
|
365
|
-
/*
|
|
366
|
-
.
|
|
367
|
-
|
|
368
|
-
.registry-item {
|
|
531
|
+
/* -- Parameter Registry -- */
|
|
532
|
+
.param-row {
|
|
369
533
|
display: flex;
|
|
370
|
-
justify-content: space-between;
|
|
371
534
|
align-items: center;
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
535
|
+
justify-content: space-between;
|
|
536
|
+
padding: 6px 0;
|
|
537
|
+
border-bottom: 1px solid rgba(39,39,42,0.4);
|
|
538
|
+
font-family: 'JetBrains Mono', monospace;
|
|
539
|
+
font-size: 11px;
|
|
375
540
|
}
|
|
376
541
|
|
|
377
|
-
.
|
|
378
|
-
.registry-key { color: var(--text-secondary); font-family: var(--font-mono); }
|
|
379
|
-
.registry-val { color: var(--accent); font-family: var(--font-mono); font-weight: 500; }
|
|
542
|
+
.param-row:last-child { border-bottom: none; }
|
|
380
543
|
|
|
381
|
-
|
|
382
|
-
|
|
544
|
+
.param-name {
|
|
545
|
+
color: var(--text-tertiary);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
.param-val {
|
|
549
|
+
color: var(--accent);
|
|
550
|
+
font-variant-numeric: tabular-nums;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
.param-changed {
|
|
554
|
+
color: var(--text-secondary);
|
|
555
|
+
font-size: 9px;
|
|
556
|
+
margin-left: 6px;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/* Ghost preview for pending recommendations */
|
|
560
|
+
.param-ghost {
|
|
383
561
|
display: none;
|
|
384
|
-
background: var(--warning-dim);
|
|
385
|
-
border: 1px solid var(--warning);
|
|
386
562
|
color: var(--warning);
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
font-
|
|
390
|
-
font-weight: 500;
|
|
391
|
-
align-items: center;
|
|
392
|
-
gap: 8px;
|
|
563
|
+
font-size: 9px;
|
|
564
|
+
margin-left: 4px;
|
|
565
|
+
font-variant-numeric: tabular-nums;
|
|
393
566
|
}
|
|
394
567
|
|
|
395
|
-
.advisor-mode .
|
|
568
|
+
.advisor-mode .param-ghost { display: inline; }
|
|
396
569
|
|
|
397
|
-
.pending-
|
|
398
|
-
|
|
399
|
-
color: var(--
|
|
570
|
+
.param-pending-label {
|
|
571
|
+
display: none;
|
|
572
|
+
color: var(--warning);
|
|
573
|
+
font-size: 9px;
|
|
574
|
+
margin-left: 6px;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
.advisor-mode .param-pending-label { display: inline; }
|
|
578
|
+
|
|
579
|
+
.params-scroll {
|
|
580
|
+
overflow-y: auto;
|
|
581
|
+
max-height: 300px;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
.params-scroll::-webkit-scrollbar { width: 4px; }
|
|
585
|
+
.params-scroll::-webkit-scrollbar-track { background: transparent; }
|
|
586
|
+
.params-scroll::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
|
|
587
|
+
|
|
588
|
+
/* -- Violations Table -- */
|
|
589
|
+
.violations-table {
|
|
590
|
+
width: 100%;
|
|
591
|
+
border-collapse: collapse;
|
|
400
592
|
font-size: 11px;
|
|
593
|
+
font-family: 'JetBrains Mono', monospace;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
.violations-table thead th {
|
|
597
|
+
text-align: left;
|
|
598
|
+
padding: 7px 8px;
|
|
599
|
+
border-bottom: 1px solid var(--border);
|
|
600
|
+
color: var(--text-secondary);
|
|
401
601
|
font-weight: 600;
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
602
|
+
font-size: 10px;
|
|
603
|
+
text-transform: uppercase;
|
|
604
|
+
letter-spacing: 0.06em;
|
|
605
|
+
cursor: pointer;
|
|
606
|
+
user-select: none;
|
|
607
|
+
white-space: nowrap;
|
|
608
|
+
position: sticky;
|
|
609
|
+
top: 0;
|
|
610
|
+
background: var(--bg-panel);
|
|
405
611
|
}
|
|
406
612
|
|
|
407
|
-
.
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
padding:
|
|
613
|
+
.violations-table thead th:hover { color: var(--text-tertiary); }
|
|
614
|
+
|
|
615
|
+
.violations-table tbody td {
|
|
616
|
+
padding: 6px 8px;
|
|
617
|
+
border-bottom: 1px solid rgba(39,39,42,0.4);
|
|
618
|
+
color: var(--text-value);
|
|
619
|
+
font-variant-numeric: tabular-nums;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
.violations-table tbody tr:hover td { background: rgba(39,39,42,0.3); }
|
|
623
|
+
|
|
624
|
+
.table-scroll {
|
|
625
|
+
overflow-y: auto;
|
|
626
|
+
max-height: 240px;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
.table-scroll::-webkit-scrollbar { width: 4px; }
|
|
630
|
+
.table-scroll::-webkit-scrollbar-track { background: transparent; }
|
|
631
|
+
.table-scroll::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
|
|
632
|
+
|
|
633
|
+
.badge-applied { color: var(--accent); font-size: 10px; }
|
|
634
|
+
.badge-skipped { color: var(--text-secondary); font-size: 10px; }
|
|
635
|
+
.badge-rejected { color: var(--danger); font-size: 10px; opacity: 0.6; }
|
|
636
|
+
|
|
637
|
+
/* Pending badge in violations table (advisor mode) */
|
|
638
|
+
.badge-pending {
|
|
639
|
+
display: none;
|
|
640
|
+
color: var(--warning);
|
|
641
|
+
font-size: 10px;
|
|
642
|
+
cursor: pointer;
|
|
643
|
+
position: relative;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
.advisor-mode .badge-pending { display: inline-flex; align-items: center; gap: 4px; }
|
|
647
|
+
|
|
648
|
+
.badge-pending:hover { text-decoration: underline; }
|
|
649
|
+
|
|
650
|
+
/* Pending dropdown in violations table */
|
|
651
|
+
.pending-dropdown {
|
|
652
|
+
display: none;
|
|
653
|
+
position: absolute;
|
|
654
|
+
bottom: 100%;
|
|
655
|
+
left: 0;
|
|
656
|
+
background: var(--bg-panel);
|
|
657
|
+
border: 1px solid var(--border);
|
|
411
658
|
border-radius: 4px;
|
|
412
|
-
|
|
659
|
+
padding: 4px 0;
|
|
660
|
+
z-index: 20;
|
|
661
|
+
min-width: 120px;
|
|
662
|
+
box-shadow: 0 -4px 12px rgba(0,0,0,0.4);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
.pending-dropdown.open { display: block; }
|
|
666
|
+
|
|
667
|
+
.pending-dropdown-item {
|
|
668
|
+
padding: 5px 12px;
|
|
669
|
+
font-family: 'JetBrains Mono', monospace;
|
|
670
|
+
font-size: 10px;
|
|
413
671
|
cursor: pointer;
|
|
414
|
-
|
|
415
|
-
|
|
672
|
+
display: flex;
|
|
673
|
+
align-items: center;
|
|
674
|
+
gap: 6px;
|
|
675
|
+
white-space: nowrap;
|
|
416
676
|
}
|
|
417
677
|
|
|
418
|
-
.
|
|
419
|
-
.
|
|
420
|
-
.
|
|
678
|
+
.pending-dropdown-item:hover { background: rgba(39,39,42,0.5); }
|
|
679
|
+
.pending-dropdown-item.approve-item { color: var(--accent); }
|
|
680
|
+
.pending-dropdown-item.reject-item { color: var(--danger); }
|
|
421
681
|
|
|
422
|
-
|
|
423
|
-
.
|
|
682
|
+
/* -- Bottom split row -- */
|
|
683
|
+
.split-row {
|
|
684
|
+
display: grid;
|
|
685
|
+
grid-template-columns: 1fr 1fr;
|
|
686
|
+
gap: 12px;
|
|
687
|
+
}
|
|
424
688
|
|
|
425
|
-
/*
|
|
689
|
+
/* -- Empty state -- */
|
|
426
690
|
.empty-state {
|
|
427
|
-
color: var(--text-
|
|
428
|
-
font-
|
|
691
|
+
color: var(--text-secondary);
|
|
692
|
+
font-family: 'JetBrains Mono', monospace;
|
|
693
|
+
font-size: 11px;
|
|
429
694
|
text-align: center;
|
|
430
|
-
padding:
|
|
695
|
+
padding: 20px;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/* -- Responsive -- */
|
|
699
|
+
@media (max-width: 768px) {
|
|
700
|
+
.split-row { grid-template-columns: 1fr; }
|
|
701
|
+
.header { flex-direction: column; align-items: flex-start; gap: 8px; }
|
|
702
|
+
.header-right { flex-wrap: wrap; gap: 12px; }
|
|
703
|
+
.dashboard { padding: 8px; gap: 8px; }
|
|
431
704
|
}
|
|
432
705
|
|
|
433
|
-
/*
|
|
706
|
+
/* -- Reduced motion -- */
|
|
434
707
|
@media (prefers-reduced-motion: reduce) {
|
|
435
708
|
.term-line { animation: none; opacity: 1; transform: none; }
|
|
436
709
|
.live-dot { animation: none; }
|
|
437
710
|
.persona-bar-fill { transition: none; }
|
|
711
|
+
.alert-card.resolved { transition: none; }
|
|
438
712
|
}
|
|
439
713
|
</style>
|
|
440
714
|
</head>
|
|
441
715
|
<body>
|
|
442
716
|
|
|
443
717
|
<!-- Header -->
|
|
444
|
-
<
|
|
445
|
-
<div class="header-
|
|
446
|
-
|
|
447
|
-
<
|
|
448
|
-
<div class="kpi">Mode <span class="kpi-value" id="kpi-mode">--</span></div>
|
|
449
|
-
<div class="kpi">Tick <span class="kpi-value" id="kpi-tick">0</span></div>
|
|
450
|
-
<div class="kpi">Uptime <span class="kpi-value" id="kpi-uptime">0s</span></div>
|
|
451
|
-
<div class="kpi">Plans <span class="kpi-value" id="kpi-plans">0</span></div>
|
|
452
|
-
<div class="live-dot" id="live-dot" title="WebSocket connected"></div>
|
|
718
|
+
<header class="header" id="header">
|
|
719
|
+
<div class="header-left">
|
|
720
|
+
<span class="header-logo">AgentE</span>
|
|
721
|
+
<span class="header-version">v1.8.0</span>
|
|
453
722
|
</div>
|
|
723
|
+
<div class="header-right">
|
|
724
|
+
<div class="kpi-pill">
|
|
725
|
+
<span class="label">Health</span>
|
|
726
|
+
<span class="value health-good" id="h-health">--</span>
|
|
727
|
+
</div>
|
|
728
|
+
<div class="kpi-pill">
|
|
729
|
+
<span class="label">Mode</span>
|
|
730
|
+
<span class="value mode-value-auto" id="h-mode">--</span>
|
|
731
|
+
</div>
|
|
732
|
+
<div class="kpi-pill">
|
|
733
|
+
<span class="label">Tick</span>
|
|
734
|
+
<span class="value" id="h-tick">0</span>
|
|
735
|
+
</div>
|
|
736
|
+
<div class="kpi-pill pending-pill" id="pending-pill">
|
|
737
|
+
<span class="label" style="color: var(--warning);">Pending</span>
|
|
738
|
+
<span class="value" style="color: var(--warning);" id="h-pending">0</span>
|
|
739
|
+
</div>
|
|
740
|
+
<div class="kpi-pill">
|
|
741
|
+
<span class="label">Uptime</span>
|
|
742
|
+
<span class="value" id="h-uptime">0s</span>
|
|
743
|
+
</div>
|
|
744
|
+
<div class="kpi-pill">
|
|
745
|
+
<div class="live-dot" id="live-dot" title="WebSocket connected"></div>
|
|
746
|
+
<span style="color: var(--accent); font-size: 11px;">LIVE</span>
|
|
747
|
+
</div>
|
|
748
|
+
</div>
|
|
749
|
+
</header>
|
|
750
|
+
|
|
751
|
+
<!-- Advisor Banner -->
|
|
752
|
+
<div class="advisor-banner" id="advisor-banner">
|
|
753
|
+
ADVISOR MODE \\u2014 AgentE is waiting for your approval before applying changes
|
|
454
754
|
</div>
|
|
455
755
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
<div class="advisor-banner" id="advisor-banner">
|
|
459
|
-
ADVISOR MODE \u2014 Recommendations require manual approval
|
|
460
|
-
<span class="pending-pill" id="pending-count">0</span> pending
|
|
461
|
-
</div>
|
|
756
|
+
<!-- Dashboard -->
|
|
757
|
+
<main class="dashboard" id="dashboard-root">
|
|
462
758
|
|
|
463
|
-
<!--
|
|
464
|
-
<div class="charts-
|
|
465
|
-
<div class="
|
|
466
|
-
<
|
|
467
|
-
<div class="chart-value" id="cv-health">--</div>
|
|
468
|
-
<canvas id="chart-health"></canvas>
|
|
469
|
-
</div>
|
|
470
|
-
<div class="chart-box">
|
|
471
|
-
<div class="chart-label">Gini Coefficient</div>
|
|
472
|
-
<div class="chart-value" id="cv-gini">--</div>
|
|
473
|
-
<canvas id="chart-gini"></canvas>
|
|
474
|
-
</div>
|
|
475
|
-
<div class="chart-box">
|
|
476
|
-
<div class="chart-label">Net Flow</div>
|
|
477
|
-
<div class="chart-value" id="cv-netflow">--</div>
|
|
478
|
-
<canvas id="chart-netflow"></canvas>
|
|
759
|
+
<!-- Health & Metrics -->
|
|
760
|
+
<div class="panel charts-panel">
|
|
761
|
+
<div class="panel-header">
|
|
762
|
+
<span class="panel-title">Health & Metrics</span>
|
|
479
763
|
</div>
|
|
480
|
-
<div class="chart-
|
|
481
|
-
<div class="chart
|
|
482
|
-
|
|
483
|
-
|
|
764
|
+
<div class="chart-row">
|
|
765
|
+
<div class="mini-chart">
|
|
766
|
+
<div class="mini-chart-label">Health Score</div>
|
|
767
|
+
<canvas id="chart-health"></canvas>
|
|
768
|
+
</div>
|
|
769
|
+
<div class="mini-chart">
|
|
770
|
+
<div class="mini-chart-label">Gini Coefficient</div>
|
|
771
|
+
<canvas id="chart-gini"></canvas>
|
|
772
|
+
</div>
|
|
773
|
+
<div class="mini-chart">
|
|
774
|
+
<div class="mini-chart-label">Net Flow</div>
|
|
775
|
+
<canvas id="chart-flow"></canvas>
|
|
776
|
+
</div>
|
|
777
|
+
<div class="mini-chart">
|
|
778
|
+
<div class="mini-chart-label">Avg Satisfaction</div>
|
|
779
|
+
<canvas id="chart-satisfaction"></canvas>
|
|
780
|
+
</div>
|
|
484
781
|
</div>
|
|
485
782
|
</div>
|
|
486
783
|
|
|
487
784
|
<!-- Decision Feed -->
|
|
488
|
-
<div class="panel">
|
|
489
|
-
<div class="panel-
|
|
490
|
-
|
|
785
|
+
<div class="panel terminal-panel">
|
|
786
|
+
<div class="panel-header">
|
|
787
|
+
<span class="panel-title">Decision Feed</span>
|
|
788
|
+
<div class="panel-meta">
|
|
789
|
+
<span id="decision-count">0 decisions</span>
|
|
790
|
+
<span class="live-label"><div class="live-dot" style="width:5px;height:5px;"></div> LIVE</span>
|
|
791
|
+
</div>
|
|
792
|
+
</div>
|
|
793
|
+
<div class="panel-body">
|
|
794
|
+
<div class="terminal" id="terminal">
|
|
795
|
+
<div id="terminal-inner"></div>
|
|
796
|
+
</div>
|
|
797
|
+
</div>
|
|
491
798
|
</div>
|
|
492
799
|
|
|
493
800
|
<!-- Active Alerts -->
|
|
494
|
-
<div class="panel">
|
|
495
|
-
<div class="panel-
|
|
496
|
-
|
|
497
|
-
<
|
|
801
|
+
<div class="panel alerts-panel">
|
|
802
|
+
<div class="panel-header">
|
|
803
|
+
<span class="panel-title">Active Alerts</span>
|
|
804
|
+
<span class="panel-meta" id="alerts-count">All clear</span>
|
|
498
805
|
</div>
|
|
806
|
+
<div class="alerts-scroll" id="alerts"></div>
|
|
499
807
|
</div>
|
|
500
808
|
|
|
501
809
|
<!-- Violation History -->
|
|
502
810
|
<div class="panel">
|
|
503
|
-
<div class="panel-
|
|
504
|
-
|
|
811
|
+
<div class="panel-header">
|
|
812
|
+
<span class="panel-title">Violation History</span>
|
|
813
|
+
<span class="panel-meta">Last 100 decisions</span>
|
|
814
|
+
</div>
|
|
815
|
+
<div class="table-scroll">
|
|
505
816
|
<table class="violations-table" id="violations-table">
|
|
506
817
|
<thead>
|
|
507
818
|
<tr>
|
|
508
819
|
<th data-sort="tick">Tick</th>
|
|
509
820
|
<th data-sort="principle">Principle</th>
|
|
510
|
-
<th data-sort="severity">
|
|
821
|
+
<th data-sort="severity">Sev</th>
|
|
511
822
|
<th data-sort="parameter">Parameter</th>
|
|
512
|
-
<th data-sort="result">
|
|
823
|
+
<th data-sort="result">Action</th>
|
|
824
|
+
<th>Change</th>
|
|
513
825
|
</tr>
|
|
514
826
|
</thead>
|
|
515
827
|
<tbody id="violations-body"></tbody>
|
|
@@ -517,69 +829,88 @@ function getDashboardHtml() {
|
|
|
517
829
|
</div>
|
|
518
830
|
</div>
|
|
519
831
|
|
|
520
|
-
<!--
|
|
832
|
+
<!-- Persona Distribution + Parameters -->
|
|
521
833
|
<div class="split-row">
|
|
522
|
-
<div class="panel">
|
|
523
|
-
<div class="panel-
|
|
524
|
-
|
|
834
|
+
<div class="panel persona-panel">
|
|
835
|
+
<div class="panel-header">
|
|
836
|
+
<span class="panel-title">Persona Distribution</span>
|
|
837
|
+
<span class="panel-meta" id="persona-count"></span>
|
|
838
|
+
</div>
|
|
839
|
+
<div id="persona-bars">
|
|
525
840
|
<div class="empty-state">No persona data yet</div>
|
|
526
841
|
</div>
|
|
527
842
|
</div>
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
<div class="
|
|
843
|
+
|
|
844
|
+
<div class="panel params-panel">
|
|
845
|
+
<div class="panel-header">
|
|
846
|
+
<span class="panel-title">Parameters</span>
|
|
847
|
+
<span class="panel-meta" id="params-count"></span>
|
|
848
|
+
</div>
|
|
849
|
+
<div class="params-scroll" id="params-list">
|
|
531
850
|
<div class="empty-state">No parameters registered</div>
|
|
532
851
|
</div>
|
|
533
852
|
</div>
|
|
534
853
|
</div>
|
|
535
|
-
|
|
854
|
+
|
|
855
|
+
</main>
|
|
536
856
|
|
|
537
857
|
<script>
|
|
538
858
|
(function() {
|
|
539
859
|
'use strict';
|
|
540
860
|
|
|
541
|
-
//
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
861
|
+
// -- State --
|
|
862
|
+
var ws = null;
|
|
863
|
+
var reconnectDelay = 1000;
|
|
864
|
+
var MAX_RECONNECT = 30000;
|
|
865
|
+
var isAdvisor = false;
|
|
866
|
+
var pendingDecisions = [];
|
|
867
|
+
var MAX_TERMINAL_LINES = 80;
|
|
868
|
+
var MAX_VIOLATIONS = 100;
|
|
869
|
+
var violationSortKey = 'tick';
|
|
870
|
+
var violationSortAsc = false;
|
|
871
|
+
var violations = [];
|
|
872
|
+
var decisionCount = 0;
|
|
552
873
|
|
|
553
874
|
// Chart instances
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
//
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
875
|
+
var chartHealth, chartGini, chartFlow, chartSatisfaction;
|
|
876
|
+
|
|
877
|
+
// Param state for registry display
|
|
878
|
+
var paramRegistry = [];
|
|
879
|
+
var paramValues = {};
|
|
880
|
+
var paramLastTick = {};
|
|
881
|
+
var paramPending = {};
|
|
882
|
+
var currentTick = 0;
|
|
883
|
+
|
|
884
|
+
// -- DOM refs --
|
|
885
|
+
var $hHealth = document.getElementById('h-health');
|
|
886
|
+
var $hMode = document.getElementById('h-mode');
|
|
887
|
+
var $hTick = document.getElementById('h-tick');
|
|
888
|
+
var $hUptime = document.getElementById('h-uptime');
|
|
889
|
+
var $hPending = document.getElementById('h-pending');
|
|
890
|
+
var $liveDot = document.getElementById('live-dot');
|
|
891
|
+
var $terminal = document.getElementById('terminal');
|
|
892
|
+
var $terminalInner = document.getElementById('terminal-inner');
|
|
893
|
+
var $alerts = document.getElementById('alerts');
|
|
894
|
+
var $alertsCount = document.getElementById('alerts-count');
|
|
895
|
+
var $violationsBody = document.getElementById('violations-body');
|
|
896
|
+
var $personaBars = document.getElementById('persona-bars');
|
|
897
|
+
var $paramsList = document.getElementById('params-list');
|
|
898
|
+
var $paramsCount = document.getElementById('params-count');
|
|
899
|
+
var $personaCount = document.getElementById('persona-count');
|
|
900
|
+
var $decisionCount = document.getElementById('decision-count');
|
|
901
|
+
var $dashboardRoot = document.getElementById('dashboard-root');
|
|
902
|
+
|
|
903
|
+
// -- Helpers --
|
|
573
904
|
function esc(s) { return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,''').replace(/\\\\/g,'\'); }
|
|
574
905
|
function pad(n, w) { return String(n).padStart(w || 4, ' '); }
|
|
575
|
-
function fmt(n) { return typeof n === 'number' ? n.toFixed(3) : '
|
|
576
|
-
function pct(n) { return typeof n === 'number' ? (n * 100).toFixed(0) + '%' : '
|
|
906
|
+
function fmt(n) { return typeof n === 'number' ? n.toFixed(3) : '\\u2014'; }
|
|
907
|
+
function pct(n) { return typeof n === 'number' ? (n * 100).toFixed(0) + '%' : '\\u2014'; }
|
|
577
908
|
|
|
578
909
|
function formatUptime(ms) {
|
|
579
|
-
|
|
910
|
+
var s = Math.floor(ms / 1000);
|
|
580
911
|
if (s < 60) return s + 's';
|
|
581
912
|
if (s < 3600) return Math.floor(s / 60) + 'm ' + (s % 60) + 's';
|
|
582
|
-
|
|
913
|
+
var h = Math.floor(s / 3600);
|
|
583
914
|
return h + 'h ' + Math.floor((s % 3600) / 60) + 'm';
|
|
584
915
|
}
|
|
585
916
|
|
|
@@ -590,34 +921,63 @@ function getDashboardHtml() {
|
|
|
590
921
|
}
|
|
591
922
|
|
|
592
923
|
function sevClass(s) {
|
|
924
|
+
if (s >= 7) return 'high';
|
|
925
|
+
if (s >= 4) return 'med';
|
|
926
|
+
return 'low';
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
function sevCardClass(s) {
|
|
593
930
|
if (s >= 7) return 'sev-high';
|
|
594
931
|
if (s >= 4) return 'sev-med';
|
|
595
932
|
return 'sev-low';
|
|
596
933
|
}
|
|
597
934
|
|
|
598
|
-
|
|
599
|
-
|
|
935
|
+
function personaColor(name) {
|
|
936
|
+
var n = (name || '').toLowerCase();
|
|
937
|
+
if (n === 'atrisk' || n === 'at_risk' || n === 'dormant') return 'var(--danger)';
|
|
938
|
+
if (n === 'spender' || n === 'newentrant' || n === 'new_entrant' || n === 'passive') return 'var(--warning)';
|
|
939
|
+
return 'var(--accent)';
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// -- Chart setup --
|
|
943
|
+
var chartOpts = {
|
|
600
944
|
responsive: true,
|
|
601
945
|
maintainAspectRatio: false,
|
|
602
|
-
animation:
|
|
603
|
-
plugins: {
|
|
946
|
+
animation: false,
|
|
947
|
+
plugins: {
|
|
948
|
+
legend: { display: false },
|
|
949
|
+
tooltip: {
|
|
950
|
+
backgroundColor: '#18181b',
|
|
951
|
+
titleColor: '#a1a1aa',
|
|
952
|
+
bodyColor: '#d4d4d8',
|
|
953
|
+
titleFont: { family: 'JetBrains Mono', size: 10 },
|
|
954
|
+
bodyFont: { family: 'JetBrains Mono', size: 11 },
|
|
955
|
+
borderColor: '#27272a',
|
|
956
|
+
borderWidth: 1,
|
|
957
|
+
padding: 8,
|
|
958
|
+
}
|
|
959
|
+
},
|
|
604
960
|
scales: {
|
|
605
961
|
x: { display: false },
|
|
606
962
|
y: {
|
|
607
|
-
|
|
608
|
-
|
|
963
|
+
grid: { color: 'rgba(39,39,42,0.5)', drawBorder: false },
|
|
964
|
+
ticks: {
|
|
965
|
+
color: '#52525b',
|
|
966
|
+
font: { family: 'JetBrains Mono', size: 9 },
|
|
967
|
+
maxTicksLimit: 3,
|
|
968
|
+
},
|
|
609
969
|
border: { display: false },
|
|
610
970
|
}
|
|
611
971
|
},
|
|
612
972
|
elements: {
|
|
613
|
-
point: { radius: 0 },
|
|
973
|
+
point: { radius: 0, hoverRadius: 3, backgroundColor: '#22c55e' },
|
|
614
974
|
line: { borderWidth: 1.5, tension: 0.3 },
|
|
615
975
|
}
|
|
616
976
|
};
|
|
617
977
|
|
|
618
978
|
function makeChart(id, color, minY, maxY) {
|
|
619
|
-
|
|
620
|
-
|
|
979
|
+
var ctx = document.getElementById(id).getContext('2d');
|
|
980
|
+
var opts = JSON.parse(JSON.stringify(chartOpts));
|
|
621
981
|
if (minY !== undefined) opts.scales.y.min = minY;
|
|
622
982
|
if (maxY !== undefined) opts.scales.y.max = maxY;
|
|
623
983
|
return new Chart(ctx, {
|
|
@@ -638,7 +998,7 @@ function getDashboardHtml() {
|
|
|
638
998
|
function initCharts() {
|
|
639
999
|
chartHealth = makeChart('chart-health', '#22c55e', 0, 100);
|
|
640
1000
|
chartGini = makeChart('chart-gini', '#eab308', 0, 1);
|
|
641
|
-
|
|
1001
|
+
chartFlow = makeChart('chart-flow', '#3b82f6');
|
|
642
1002
|
chartSatisfaction = makeChart('chart-satisfaction', '#22c55e', 0, 100);
|
|
643
1003
|
}
|
|
644
1004
|
|
|
@@ -648,178 +1008,278 @@ function getDashboardHtml() {
|
|
|
648
1008
|
chart.update('none');
|
|
649
1009
|
}
|
|
650
1010
|
|
|
651
|
-
//
|
|
1011
|
+
// -- Terminal --
|
|
652
1012
|
function addTerminalLine(html) {
|
|
653
|
-
|
|
1013
|
+
var el = document.createElement('div');
|
|
654
1014
|
el.className = 'term-line';
|
|
655
1015
|
el.innerHTML = html;
|
|
656
|
-
$
|
|
657
|
-
while ($
|
|
658
|
-
$
|
|
1016
|
+
$terminalInner.appendChild(el);
|
|
1017
|
+
while ($terminalInner.children.length > MAX_TERMINAL_LINES) {
|
|
1018
|
+
$terminalInner.removeChild($terminalInner.firstChild);
|
|
659
1019
|
}
|
|
660
1020
|
$terminal.scrollTop = $terminal.scrollHeight;
|
|
661
1021
|
}
|
|
662
1022
|
|
|
663
1023
|
function decisionToTerminal(d) {
|
|
664
|
-
|
|
665
|
-
? '<span class="t-
|
|
1024
|
+
var resultIcon = d.result === 'applied'
|
|
1025
|
+
? '<span class="t-check">\\u2705 </span>'
|
|
666
1026
|
: d.result === 'rejected'
|
|
667
1027
|
? '<span class="t-fail">\\u274c </span>'
|
|
668
|
-
:
|
|
1028
|
+
: d.result === 'skipped_override'
|
|
1029
|
+
? '<span class="t-pending-icon">\\u23f3 </span>'
|
|
1030
|
+
: '<span class="t-skip">\\u23f8 </span>';
|
|
669
1031
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
1032
|
+
var principle = d.diagnosis?.principle || {};
|
|
1033
|
+
var plan = d.plan || {};
|
|
1034
|
+
var severity = d.diagnosis?.violation?.severity ?? '?';
|
|
1035
|
+
var confidence = d.diagnosis?.violation?.confidence;
|
|
1036
|
+
var confStr = confidence != null ? (confidence * 100).toFixed(0) + '%' : '?';
|
|
675
1037
|
|
|
676
|
-
|
|
1038
|
+
var advisorBtns = '';
|
|
677
1039
|
if (isAdvisor && d.result === 'skipped_override') {
|
|
678
|
-
advisorBtns = '<span class="advisor-
|
|
679
|
-
+ '<button class="advisor-btn approve" data-action="approve" data-id="' + esc(d.id) + '"
|
|
680
|
-
+ '<button class="advisor-btn reject" data-action="reject" data-id="' + esc(d.id) + '"
|
|
1040
|
+
advisorBtns = '<span class="advisor-btn-group" data-id="' + esc(d.id) + '">'
|
|
1041
|
+
+ '<button class="advisor-btn approve-btn" data-action="approve" data-id="' + esc(d.id) + '">✓ Approve</button>'
|
|
1042
|
+
+ '<button class="advisor-btn reject-btn" data-action="reject" data-id="' + esc(d.id) + '">✕ Reject</button>'
|
|
681
1043
|
+ '</span>';
|
|
682
1044
|
}
|
|
683
1045
|
|
|
684
1046
|
return '<span class="t-tick">[Tick ' + pad(d.tick) + ']</span> '
|
|
685
1047
|
+ resultIcon
|
|
686
|
-
+ '<span class="t-principle">
|
|
687
|
-
+ '<span class="t-param">' + esc(plan.parameter || '
|
|
1048
|
+
+ '<span class="t-principle">' + esc(principle.name || '') + ':</span> '
|
|
1049
|
+
+ '<span class="t-param">' + esc(plan.parameter || '\\u2014') + ' </span>'
|
|
688
1050
|
+ '<span class="t-old">' + fmt(plan.currentValue) + '</span>'
|
|
689
1051
|
+ '<span class="t-arrow"> \\u2192 </span>'
|
|
690
|
-
+
|
|
691
|
-
|
|
1052
|
+
+ (d.result === 'skipped_override'
|
|
1053
|
+
? '<span class="t-pending-val">' + fmt(plan.targetValue) + '</span>'
|
|
1054
|
+
: '<span class="t-new">' + fmt(plan.targetValue) + '</span>')
|
|
1055
|
+
+ '<span class="t-meta"> sev ' + severity + ', conf ' + confStr + '</span>'
|
|
692
1056
|
+ advisorBtns;
|
|
693
1057
|
}
|
|
694
1058
|
|
|
695
|
-
//
|
|
1059
|
+
// -- Alerts --
|
|
696
1060
|
function renderAlerts(alerts) {
|
|
697
1061
|
if (!alerts || alerts.length === 0) {
|
|
698
|
-
$
|
|
1062
|
+
$alerts.innerHTML = '<div class="empty-state">No active violations. Economy is healthy.</div>';
|
|
1063
|
+
$alertsCount.textContent = 'All clear';
|
|
699
1064
|
return;
|
|
700
1065
|
}
|
|
701
|
-
|
|
702
|
-
$
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
1066
|
+
var sorted = alerts.slice().sort(function(a, b) { return (b.severity || 0) - (a.severity || 0); });
|
|
1067
|
+
$alertsCount.textContent = sorted.length + ' violation' + (sorted.length !== 1 ? 's' : '');
|
|
1068
|
+
|
|
1069
|
+
$alerts.innerHTML = sorted.map(function(a) {
|
|
1070
|
+
var sev = a.severity || a.violation?.severity || 0;
|
|
1071
|
+
var sc = sevClass(sev);
|
|
1072
|
+
var cardCls = sevCardClass(sev);
|
|
1073
|
+
var name = a.principleName || a.principle?.name || '?';
|
|
1074
|
+
var pid = a.principleId || a.principle?.id || '?';
|
|
1075
|
+
var reason = a.reasoning || a.violation?.suggestedAction?.reasoning || '';
|
|
1076
|
+
var suggestion = a.suggestion || '';
|
|
1077
|
+
|
|
1078
|
+
var hasPending = isAdvisor && pendingDecisions.some(function(pd) {
|
|
1079
|
+
return pd.principleId === pid || (pd.diagnosis?.principle?.id === pid);
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
var btns = '';
|
|
1083
|
+
if (hasPending) {
|
|
1084
|
+
btns = '<button class="alert-approve-btn" data-action="approve-alert" data-principle="' + esc(pid) + '">✓ Approve Fix</button>'
|
|
1085
|
+
+ '<button class="alert-reject-btn" data-action="reject-alert" data-principle="' + esc(pid) + '">✗ Reject</button>';
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
return '<div class="alert-card ' + cardCls + '" data-principle-id="' + esc(pid) + '">'
|
|
1089
|
+
+ '<div class="alert-top">'
|
|
1090
|
+
+ '<span class="sev-badge ' + sc + '">' + sev + '</span>'
|
|
1091
|
+
+ '<span class="alert-principle-name">[' + esc(pid) + '] ' + esc(name) + '</span>'
|
|
1092
|
+
+ '</div>'
|
|
1093
|
+
+ (reason ? '<div class="alert-evidence">' + esc(reason) + '</div>' : '')
|
|
1094
|
+
+ (suggestion ? '<div class="alert-suggestion">Suggested: ' + esc(suggestion) + '</div>' : '')
|
|
1095
|
+
+ btns
|
|
1096
|
+
+ '</div>';
|
|
714
1097
|
}).join('');
|
|
715
1098
|
}
|
|
716
1099
|
|
|
717
|
-
//
|
|
1100
|
+
// -- Violations table --
|
|
718
1101
|
function addViolation(d) {
|
|
1102
|
+
var plan = d.plan || {};
|
|
719
1103
|
violations.push({
|
|
720
1104
|
tick: d.tick,
|
|
721
1105
|
principle: (d.diagnosis?.principle?.id || '?') + ' ' + (d.diagnosis?.principle?.name || ''),
|
|
722
1106
|
severity: d.diagnosis?.violation?.severity || 0,
|
|
723
|
-
parameter:
|
|
1107
|
+
parameter: plan.parameter || '\\u2014',
|
|
724
1108
|
result: d.result,
|
|
1109
|
+
currentValue: plan.currentValue,
|
|
1110
|
+
targetValue: plan.targetValue,
|
|
1111
|
+
decisionId: d.id,
|
|
725
1112
|
});
|
|
726
1113
|
if (violations.length > MAX_VIOLATIONS) violations.shift();
|
|
1114
|
+
decisionCount = violations.length;
|
|
1115
|
+
$decisionCount.textContent = decisionCount + ' decisions';
|
|
727
1116
|
renderViolations();
|
|
728
1117
|
}
|
|
729
1118
|
|
|
730
1119
|
function renderViolations() {
|
|
731
|
-
|
|
732
|
-
|
|
1120
|
+
var sorted = violations.slice().sort(function(a, b) {
|
|
1121
|
+
var va = a[violationSortKey], vb = b[violationSortKey];
|
|
733
1122
|
if (va < vb) return violationSortAsc ? -1 : 1;
|
|
734
1123
|
if (va > vb) return violationSortAsc ? 1 : -1;
|
|
735
1124
|
return 0;
|
|
736
1125
|
});
|
|
737
1126
|
$violationsBody.innerHTML = sorted.map(function(v) {
|
|
1127
|
+
var isPending = v.result === 'skipped_override';
|
|
1128
|
+
|
|
1129
|
+
var actionHtml;
|
|
1130
|
+
if (isPending && isAdvisor) {
|
|
1131
|
+
actionHtml = '<span class="badge-pending" data-action="toggle-pending" data-id="' + esc(v.decisionId || '') + '">'
|
|
1132
|
+
+ 'Pending \\u25BE'
|
|
1133
|
+
+ '<div class="pending-dropdown">'
|
|
1134
|
+
+ '<div class="pending-dropdown-item approve-item" data-action="approve" data-id="' + esc(v.decisionId || '') + '">✓ Approve</div>'
|
|
1135
|
+
+ '<div class="pending-dropdown-item reject-item" data-action="reject" data-id="' + esc(v.decisionId || '') + '">✗ Reject</div>'
|
|
1136
|
+
+ '</div>'
|
|
1137
|
+
+ '</span>';
|
|
1138
|
+
} else if (v.result === 'applied') {
|
|
1139
|
+
actionHtml = '<span class="badge-applied">Applied</span>';
|
|
1140
|
+
} else if (v.result === 'rejected') {
|
|
1141
|
+
actionHtml = '<span class="badge-rejected">Rejected</span>';
|
|
1142
|
+
} else {
|
|
1143
|
+
actionHtml = '<span class="badge-skipped">' + esc(v.result || 'Skipped') + '</span>';
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
var changeHtml = '';
|
|
1147
|
+
if (v.currentValue != null && v.targetValue != null) {
|
|
1148
|
+
var valColor = isPending && isAdvisor ? 'var(--warning)' : 'var(--accent)';
|
|
1149
|
+
changeHtml = '<span style="color:var(--text-value)">' + fmt(v.currentValue) + '</span>'
|
|
1150
|
+
+ '<span style="color:var(--info)"> \\u2192 </span>'
|
|
1151
|
+
+ '<span style="color:' + valColor + '">' + fmt(v.targetValue) + '</span>';
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
var sevColor = v.severity >= 7 ? 'var(--danger)' : v.severity >= 4 ? 'var(--warning)' : 'var(--accent)';
|
|
1155
|
+
|
|
738
1156
|
return '<tr>'
|
|
739
|
-
+ '<td>' + v.tick + '</td>'
|
|
740
|
-
+ '<td style="color:var(--text-
|
|
741
|
-
+ '<td
|
|
742
|
-
+ '<td>' + esc(v.parameter) + '</td>'
|
|
743
|
-
+ '<td>' +
|
|
1157
|
+
+ '<td style="color:var(--text-secondary)">' + v.tick + '</td>'
|
|
1158
|
+
+ '<td style="color:var(--text-value)">' + esc(v.principle) + '</td>'
|
|
1159
|
+
+ '<td style="color:' + sevColor + '">' + v.severity + '</td>'
|
|
1160
|
+
+ '<td style="color:var(--text-tertiary)">' + esc(v.parameter) + '</td>'
|
|
1161
|
+
+ '<td class="action-cell">' + actionHtml + '</td>'
|
|
1162
|
+
+ '<td>' + changeHtml + '</td>'
|
|
744
1163
|
+ '</tr>';
|
|
745
1164
|
}).join('');
|
|
746
1165
|
}
|
|
747
1166
|
|
|
748
1167
|
// Table sorting
|
|
749
|
-
document.querySelectorAll('.violations-table th').forEach(function(th) {
|
|
1168
|
+
document.querySelectorAll('.violations-table th[data-sort]').forEach(function(th) {
|
|
750
1169
|
th.addEventListener('click', function() {
|
|
751
|
-
|
|
1170
|
+
var key = th.dataset.sort;
|
|
752
1171
|
if (violationSortKey === key) violationSortAsc = !violationSortAsc;
|
|
753
1172
|
else { violationSortKey = key; violationSortAsc = true; }
|
|
754
1173
|
renderViolations();
|
|
755
1174
|
});
|
|
756
1175
|
});
|
|
757
1176
|
|
|
758
|
-
//
|
|
1177
|
+
// -- Personas --
|
|
759
1178
|
function renderPersonas(dist) {
|
|
760
1179
|
if (!dist || Object.keys(dist).length === 0) {
|
|
761
1180
|
$personaBars.innerHTML = '<div class="empty-state">No persona data yet</div>';
|
|
1181
|
+
$personaCount.textContent = '';
|
|
762
1182
|
return;
|
|
763
1183
|
}
|
|
764
|
-
|
|
765
|
-
|
|
1184
|
+
var total = Object.values(dist).reduce(function(s, v) { return s + v; }, 0);
|
|
1185
|
+
$personaCount.textContent = total + ' agents';
|
|
1186
|
+
var entries = Object.entries(dist).sort(function(a, b) { return b[1] - a[1]; });
|
|
766
1187
|
$personaBars.innerHTML = entries.map(function(e) {
|
|
767
|
-
|
|
1188
|
+
var pctVal = total > 0 ? (e[1] / total * 100) : 0;
|
|
1189
|
+
var color = personaColor(e[0]);
|
|
768
1190
|
return '<div class="persona-row">'
|
|
769
|
-
+ '<
|
|
770
|
-
+ '<div class="persona-bar-track"><div class="persona-bar-fill" style="width:' + pctVal + '
|
|
771
|
-
+ '<
|
|
1191
|
+
+ '<span class="persona-label">' + esc(e[0]) + '</span>'
|
|
1192
|
+
+ '<div class="persona-bar-track"><div class="persona-bar-fill" style="width:' + pctVal.toFixed(0) + '%;background:' + color + ';"></div></div>'
|
|
1193
|
+
+ '<span class="persona-pct">' + pctVal.toFixed(0) + '%</span>'
|
|
772
1194
|
+ '</div>';
|
|
773
1195
|
}).join('');
|
|
774
1196
|
}
|
|
775
1197
|
|
|
776
|
-
//
|
|
777
|
-
function
|
|
778
|
-
if (!principles || principles.length === 0) {
|
|
779
|
-
$
|
|
1198
|
+
// -- Parameters --
|
|
1199
|
+
function renderParams(principles, registryValues) {
|
|
1200
|
+
if ((!principles || principles.length === 0) && Object.keys(paramValues).length === 0) {
|
|
1201
|
+
$paramsList.innerHTML = '<div class="empty-state">No parameters registered</div>';
|
|
1202
|
+
$paramsCount.textContent = '';
|
|
780
1203
|
return;
|
|
781
1204
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
1205
|
+
|
|
1206
|
+
// If we have actual parameter values from API, show those
|
|
1207
|
+
if (registryValues && Object.keys(registryValues).length > 0) {
|
|
1208
|
+
var entries = Object.entries(registryValues);
|
|
1209
|
+
$paramsCount.textContent = entries.length + ' tracked';
|
|
1210
|
+
$paramsList.innerHTML = entries.map(function(e) {
|
|
1211
|
+
var key = e[0];
|
|
1212
|
+
var val = e[1];
|
|
1213
|
+
var ticksAgo = currentTick - (paramLastTick[key] || 0);
|
|
1214
|
+
var agoText = ticksAgo <= 0 ? '' : ticksAgo <= 5 ? 'just now' : ticksAgo + ' ticks ago';
|
|
1215
|
+
var pending = paramPending[key];
|
|
1216
|
+
|
|
1217
|
+
if (pending && isAdvisor) {
|
|
1218
|
+
return '<div class="param-row">'
|
|
1219
|
+
+ '<span class="param-name">' + esc(key) + '</span>'
|
|
1220
|
+
+ '<span>'
|
|
1221
|
+
+ '<span class="param-val">' + fmt(val) + '</span>'
|
|
1222
|
+
+ '<span class="param-ghost" style="display:inline;"> \\u2192 ' + fmt(pending.proposedVal) + '?</span>'
|
|
1223
|
+
+ '<span class="param-pending-label" style="display:inline;">pending</span>'
|
|
1224
|
+
+ '</span>'
|
|
1225
|
+
+ '</div>';
|
|
1226
|
+
}
|
|
1227
|
+
return '<div class="param-row">'
|
|
1228
|
+
+ '<span class="param-name">' + esc(key) + '</span>'
|
|
1229
|
+
+ '<span><span class="param-val">' + fmt(val) + '</span>'
|
|
1230
|
+
+ (agoText ? '<span class="param-changed">' + agoText + '</span>' : '')
|
|
1231
|
+
+ '</span>'
|
|
1232
|
+
+ '</div>';
|
|
1233
|
+
}).join('');
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// Fallback: show principle names (legacy behavior)
|
|
1238
|
+
if (principles && principles.length > 0) {
|
|
1239
|
+
$paramsCount.textContent = principles.length + ' registered';
|
|
1240
|
+
$paramsList.innerHTML = principles.slice(0, 30).map(function(p) {
|
|
1241
|
+
return '<div class="param-row">'
|
|
1242
|
+
+ '<span class="param-name">[' + esc(p.id) + ']</span>'
|
|
1243
|
+
+ '<span class="param-val">' + esc(p.name) + '</span>'
|
|
1244
|
+
+ '</div>';
|
|
1245
|
+
}).join('');
|
|
1246
|
+
}
|
|
788
1247
|
}
|
|
789
1248
|
|
|
790
|
-
//
|
|
1249
|
+
// -- KPI update --
|
|
791
1250
|
function updateKPIs(data) {
|
|
792
1251
|
if (data.health != null) {
|
|
793
|
-
$
|
|
794
|
-
$
|
|
795
|
-
document.getElementById('cv-health').textContent = data.health + '/100';
|
|
1252
|
+
$hHealth.textContent = data.health + '/100';
|
|
1253
|
+
$hHealth.className = 'value ' + healthClass(data.health);
|
|
796
1254
|
}
|
|
797
1255
|
if (data.mode != null) {
|
|
798
|
-
$
|
|
1256
|
+
$hMode.textContent = data.mode;
|
|
799
1257
|
isAdvisor = data.mode === 'advisor';
|
|
800
|
-
$
|
|
1258
|
+
$hMode.className = 'value ' + (isAdvisor ? 'mode-value-advisor' : 'mode-value-auto');
|
|
1259
|
+
document.body.classList.toggle('advisor-mode', isAdvisor);
|
|
1260
|
+
}
|
|
1261
|
+
if (data.tick != null) {
|
|
1262
|
+
$hTick.textContent = data.tick;
|
|
1263
|
+
currentTick = data.tick;
|
|
1264
|
+
}
|
|
1265
|
+
if (data.uptime != null) $hUptime.textContent = formatUptime(data.uptime);
|
|
1266
|
+
if (data.pendingCount != null || data.activePlans != null) {
|
|
1267
|
+
var count = data.pendingCount || data.activePlans || 0;
|
|
1268
|
+
$hPending.textContent = count;
|
|
801
1269
|
}
|
|
802
|
-
if (data.tick != null) $kpiTick.textContent = data.tick;
|
|
803
|
-
if (data.uptime != null) $kpiUptime.textContent = formatUptime(data.uptime);
|
|
804
|
-
if (data.activePlans != null) $kpiPlans.textContent = data.activePlans;
|
|
805
1270
|
}
|
|
806
1271
|
|
|
807
|
-
//
|
|
1272
|
+
// -- Metrics history --
|
|
808
1273
|
function updateChartsFromHistory(history) {
|
|
809
1274
|
if (!history || history.length === 0) return;
|
|
810
|
-
|
|
1275
|
+
var ticks = history.map(function(h) { return h.tick; });
|
|
811
1276
|
updateChart(chartHealth, ticks, history.map(function(h) { return h.health; }));
|
|
812
1277
|
updateChart(chartGini, ticks, history.map(function(h) { return h.giniCoefficient; }));
|
|
813
|
-
updateChart(
|
|
1278
|
+
updateChart(chartFlow, ticks, history.map(function(h) { return h.netFlow; }));
|
|
814
1279
|
updateChart(chartSatisfaction, ticks, history.map(function(h) { return h.avgSatisfaction; }));
|
|
815
|
-
|
|
816
|
-
const last = history[history.length - 1];
|
|
817
|
-
document.getElementById('cv-gini').textContent = last.giniCoefficient.toFixed(3);
|
|
818
|
-
document.getElementById('cv-netflow').textContent = last.netFlow.toFixed(1);
|
|
819
|
-
document.getElementById('cv-satisfaction').textContent = last.avgSatisfaction.toFixed(0) + '/100';
|
|
820
1280
|
}
|
|
821
1281
|
|
|
822
|
-
//
|
|
1282
|
+
// -- API calls --
|
|
823
1283
|
function fetchJSON(path) {
|
|
824
1284
|
return fetch(path).then(function(r) { return r.json(); });
|
|
825
1285
|
}
|
|
@@ -854,19 +1314,22 @@ function getDashboardHtml() {
|
|
|
854
1314
|
}).catch(function() {});
|
|
855
1315
|
|
|
856
1316
|
fetchJSON('/principles').then(function(data) {
|
|
857
|
-
if (data.principles)
|
|
1317
|
+
if (data.principles) {
|
|
1318
|
+
paramRegistry = data.principles;
|
|
1319
|
+
renderParams(data.principles, paramValues);
|
|
1320
|
+
}
|
|
858
1321
|
}).catch(function() {});
|
|
859
1322
|
|
|
860
1323
|
fetchJSON('/pending').then(function(data) {
|
|
861
1324
|
if (data.pending) {
|
|
862
1325
|
pendingDecisions = data.pending;
|
|
863
|
-
$
|
|
1326
|
+
$hPending.textContent = data.count || 0;
|
|
864
1327
|
}
|
|
865
1328
|
}).catch(function() {});
|
|
866
1329
|
}
|
|
867
1330
|
|
|
868
|
-
//
|
|
869
|
-
|
|
1331
|
+
// -- Polling fallback --
|
|
1332
|
+
var pollInterval = null;
|
|
870
1333
|
|
|
871
1334
|
function startPolling() {
|
|
872
1335
|
if (pollInterval) return;
|
|
@@ -883,9 +1346,9 @@ function getDashboardHtml() {
|
|
|
883
1346
|
if (pollInterval) { clearInterval(pollInterval); pollInterval = null; }
|
|
884
1347
|
}
|
|
885
1348
|
|
|
886
|
-
//
|
|
1349
|
+
// -- WebSocket --
|
|
887
1350
|
function connectWS() {
|
|
888
|
-
|
|
1351
|
+
var proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
889
1352
|
ws = new WebSocket(proto + '//' + location.host);
|
|
890
1353
|
|
|
891
1354
|
ws.onopen = function() {
|
|
@@ -893,13 +1356,12 @@ function getDashboardHtml() {
|
|
|
893
1356
|
$liveDot.classList.remove('disconnected');
|
|
894
1357
|
$liveDot.title = 'WebSocket connected';
|
|
895
1358
|
stopPolling();
|
|
896
|
-
// Request fresh health
|
|
897
1359
|
ws.send(JSON.stringify({ type: 'health' }));
|
|
898
1360
|
};
|
|
899
1361
|
|
|
900
1362
|
ws.onclose = function() {
|
|
901
1363
|
$liveDot.classList.add('disconnected');
|
|
902
|
-
$liveDot.title = 'WebSocket disconnected
|
|
1364
|
+
$liveDot.title = 'WebSocket disconnected \\u2014 reconnecting...';
|
|
903
1365
|
startPolling();
|
|
904
1366
|
setTimeout(connectWS, reconnectDelay);
|
|
905
1367
|
reconnectDelay = Math.min(reconnectDelay * 1.5, MAX_RECONNECT);
|
|
@@ -915,7 +1377,6 @@ function getDashboardHtml() {
|
|
|
915
1377
|
case 'tick_result':
|
|
916
1378
|
updateKPIs({ health: msg.health, tick: msg.tick });
|
|
917
1379
|
if (msg.alerts) renderAlerts(msg.alerts);
|
|
918
|
-
// Refresh charts
|
|
919
1380
|
fetchJSON('/metrics').then(function(data) {
|
|
920
1381
|
if (data.history) updateChartsFromHistory(data.history);
|
|
921
1382
|
if (data.latest) renderPersonas(data.latest.personaDistribution);
|
|
@@ -931,30 +1392,75 @@ function getDashboardHtml() {
|
|
|
931
1392
|
pendingDecisions = pendingDecisions.filter(function(d) {
|
|
932
1393
|
return d.id !== msg.decisionId;
|
|
933
1394
|
});
|
|
934
|
-
$
|
|
1395
|
+
$hPending.textContent = pendingDecisions.length;
|
|
935
1396
|
}
|
|
936
1397
|
break;
|
|
937
1398
|
}
|
|
938
1399
|
};
|
|
939
1400
|
}
|
|
940
1401
|
|
|
941
|
-
//
|
|
1402
|
+
// -- Advisor actions (event delegation) --
|
|
942
1403
|
document.addEventListener('click', function(e) {
|
|
943
1404
|
var btn = e.target.closest('[data-action]');
|
|
944
1405
|
if (!btn) return;
|
|
945
1406
|
var action = btn.getAttribute('data-action');
|
|
946
1407
|
var id = btn.getAttribute('data-id');
|
|
1408
|
+
var principleId = btn.getAttribute('data-principle');
|
|
1409
|
+
|
|
1410
|
+
// Toggle pending dropdown
|
|
1411
|
+
if (action === 'toggle-pending') {
|
|
1412
|
+
var dropdown = btn.querySelector('.pending-dropdown');
|
|
1413
|
+
if (!dropdown) return;
|
|
1414
|
+
document.querySelectorAll('.pending-dropdown.open').forEach(function(d) {
|
|
1415
|
+
if (d !== dropdown) d.classList.remove('open');
|
|
1416
|
+
});
|
|
1417
|
+
dropdown.classList.toggle('open');
|
|
1418
|
+
e.stopPropagation();
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
// Approve from alert card
|
|
1423
|
+
if (action === 'approve-alert' && principleId) {
|
|
1424
|
+
var pd = pendingDecisions.find(function(d) {
|
|
1425
|
+
return d.principleId === principleId || (d.diagnosis?.principle?.id === principleId);
|
|
1426
|
+
});
|
|
1427
|
+
if (pd) {
|
|
1428
|
+
postJSON('/approve', { decisionId: pd.id }).then(function(data) {
|
|
1429
|
+
if (data.ok) {
|
|
1430
|
+
addTerminalLine('<span class="t-tick">[Advisor]</span> <span class="t-check">\\u2705 Approved ' + esc(pd.id) + '</span>');
|
|
1431
|
+
}
|
|
1432
|
+
}).catch(function() {});
|
|
1433
|
+
}
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
// Reject from alert card
|
|
1438
|
+
if (action === 'reject-alert' && principleId) {
|
|
1439
|
+
var pd2 = pendingDecisions.find(function(d) {
|
|
1440
|
+
return d.principleId === principleId || (d.diagnosis?.principle?.id === principleId);
|
|
1441
|
+
});
|
|
1442
|
+
if (pd2) {
|
|
1443
|
+
var reason = prompt('Rejection reason (optional):');
|
|
1444
|
+
postJSON('/reject', { decisionId: pd2.id, reason: reason || undefined }).then(function(data) {
|
|
1445
|
+
if (data.ok) {
|
|
1446
|
+
addTerminalLine('<span class="t-tick">[Advisor]</span> <span class="t-fail">\\u274c Rejected ' + esc(pd2.id) + '</span>');
|
|
1447
|
+
}
|
|
1448
|
+
}).catch(function() {});
|
|
1449
|
+
}
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
947
1453
|
if (!id) return;
|
|
948
1454
|
|
|
949
1455
|
if (action === 'approve') {
|
|
950
1456
|
postJSON('/approve', { decisionId: id }).then(function(data) {
|
|
951
1457
|
if (data.ok) {
|
|
952
|
-
addTerminalLine('<span class="t-tick">[Advisor]</span> <span class="t-
|
|
1458
|
+
addTerminalLine('<span class="t-tick">[Advisor]</span> <span class="t-check">\\u2705 Approved ' + esc(id) + '</span>');
|
|
953
1459
|
}
|
|
954
1460
|
}).catch(function() {});
|
|
955
1461
|
} else if (action === 'reject') {
|
|
956
|
-
var
|
|
957
|
-
postJSON('/reject', { decisionId: id, reason:
|
|
1462
|
+
var reason2 = prompt('Rejection reason (optional):');
|
|
1463
|
+
postJSON('/reject', { decisionId: id, reason: reason2 || undefined }).then(function(data) {
|
|
958
1464
|
if (data.ok) {
|
|
959
1465
|
addTerminalLine('<span class="t-tick">[Advisor]</span> <span class="t-fail">\\u274c Rejected ' + esc(id) + '</span>');
|
|
960
1466
|
}
|
|
@@ -962,7 +1468,14 @@ function getDashboardHtml() {
|
|
|
962
1468
|
}
|
|
963
1469
|
});
|
|
964
1470
|
|
|
965
|
-
//
|
|
1471
|
+
// Close dropdowns on outside click
|
|
1472
|
+
document.addEventListener('click', function(e) {
|
|
1473
|
+
if (!e.target.closest('.badge-pending')) {
|
|
1474
|
+
document.querySelectorAll('.pending-dropdown.open').forEach(function(d) { d.classList.remove('open'); });
|
|
1475
|
+
}
|
|
1476
|
+
});
|
|
1477
|
+
|
|
1478
|
+
// -- Init --
|
|
966
1479
|
initCharts();
|
|
967
1480
|
loadInitialData();
|
|
968
1481
|
connectWS();
|