@steadwing/openalerts 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +198 -137
- package/dist/channels/console.d.ts +6 -0
- package/dist/channels/console.d.ts.map +1 -0
- package/dist/channels/console.js +10 -0
- package/dist/channels/console.js.map +1 -0
- package/dist/channels/telegram.d.ts +12 -0
- package/dist/channels/telegram.d.ts.map +1 -0
- package/dist/channels/telegram.js +28 -0
- package/dist/channels/telegram.js.map +1 -0
- package/dist/channels/webhook.d.ts +8 -0
- package/dist/channels/webhook.d.ts.map +1 -0
- package/dist/channels/webhook.js +15 -0
- package/dist/channels/webhook.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +234 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +51 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +86 -0
- package/dist/config.js.map +1 -0
- package/dist/core/alert-channel.d.ts +3 -10
- package/dist/core/alert-channel.d.ts.map +1 -0
- package/dist/core/alert-channel.js +9 -30
- package/dist/core/alert-channel.js.map +1 -0
- package/dist/core/bounded-map.d.ts +1 -0
- package/dist/core/bounded-map.d.ts.map +1 -0
- package/dist/core/bounded-map.js +1 -0
- package/dist/core/bounded-map.js.map +1 -0
- package/dist/core/engine.d.ts +6 -18
- package/dist/core/engine.d.ts.map +1 -0
- package/dist/core/engine.js +49 -98
- package/dist/core/engine.js.map +1 -0
- package/dist/core/evaluator.d.ts +1 -0
- package/dist/core/evaluator.d.ts.map +1 -0
- package/dist/core/evaluator.js +1 -0
- package/dist/core/evaluator.js.map +1 -0
- package/dist/core/event-bus.d.ts +1 -0
- package/dist/core/event-bus.d.ts.map +1 -0
- package/dist/core/event-bus.js +1 -0
- package/dist/core/event-bus.js.map +1 -0
- package/dist/core/formatter.d.ts +1 -0
- package/dist/core/formatter.d.ts.map +1 -0
- package/dist/core/formatter.js +1 -0
- package/dist/core/formatter.js.map +1 -0
- package/dist/core/rules.d.ts +1 -0
- package/dist/core/rules.d.ts.map +1 -0
- package/dist/core/rules.js +98 -0
- package/dist/core/rules.js.map +1 -0
- package/dist/core/store.d.ts +6 -9
- package/dist/core/store.d.ts.map +1 -0
- package/dist/core/store.js +43 -96
- package/dist/core/store.js.map +1 -0
- package/dist/core/types.d.ts +2 -1
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +1 -0
- package/dist/core/types.js.map +1 -0
- package/dist/db/index.d.ts +6 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +31 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/queries.d.ts +157 -0
- package/dist/db/queries.d.ts.map +1 -0
- package/dist/db/queries.js +221 -0
- package/dist/db/queries.js.map +1 -0
- package/dist/db/schema.d.ts +5 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +177 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/readers/openclaw.d.ts +11 -0
- package/dist/readers/openclaw.d.ts.map +1 -0
- package/dist/readers/openclaw.js +267 -0
- package/dist/readers/openclaw.js.map +1 -0
- package/dist/server/dashboard.d.ts +2 -0
- package/dist/server/dashboard.d.ts.map +1 -0
- package/dist/server/dashboard.js +765 -0
- package/dist/server/dashboard.js.map +1 -0
- package/dist/server/index.d.ts +10 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +28 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/routes.d.ts +6 -0
- package/dist/server/routes.d.ts.map +1 -0
- package/dist/server/routes.js +146 -0
- package/dist/server/routes.js.map +1 -0
- package/dist/server/sse.d.ts +21 -0
- package/dist/server/sse.d.ts.map +1 -0
- package/dist/server/sse.js +53 -0
- package/dist/server/sse.js.map +1 -0
- package/dist/watchers/files.d.ts +19 -0
- package/dist/watchers/files.d.ts.map +1 -0
- package/dist/watchers/files.js +105 -0
- package/dist/watchers/files.js.map +1 -0
- package/dist/watchers/gateway-adapter.d.ts +18 -0
- package/dist/watchers/gateway-adapter.d.ts.map +1 -0
- package/dist/watchers/gateway-adapter.js +273 -0
- package/dist/watchers/gateway-adapter.js.map +1 -0
- package/dist/watchers/gateway.d.ts +27 -0
- package/dist/watchers/gateway.d.ts.map +1 -0
- package/dist/watchers/gateway.js +131 -0
- package/dist/watchers/gateway.js.map +1 -0
- package/package.json +29 -43
- package/LICENSE +0 -201
- package/dist/core/index.d.ts +0 -12
- package/dist/core/index.js +0 -23
- package/dist/core/llm-enrichment.d.ts +0 -21
- package/dist/core/llm-enrichment.js +0 -180
- package/dist/core/platform.d.ts +0 -17
- package/dist/core/platform.js +0 -93
- package/dist/index.d.ts +0 -8
- package/dist/index.js +0 -213
- package/dist/plugin/adapter.d.ts +0 -150
- package/dist/plugin/adapter.js +0 -530
- package/dist/plugin/commands.d.ts +0 -18
- package/dist/plugin/commands.js +0 -103
- package/dist/plugin/dashboard-html.d.ts +0 -7
- package/dist/plugin/dashboard-html.js +0 -938
- package/dist/plugin/dashboard-routes.d.ts +0 -7
- package/dist/plugin/dashboard-routes.js +0 -336
- package/dist/plugin/gateway-client.d.ts +0 -39
- package/dist/plugin/gateway-client.js +0 -193
- package/dist/plugin/log-bridge.d.ts +0 -22
- package/dist/plugin/log-bridge.js +0 -363
- package/openclaw.plugin.json +0 -57
|
@@ -1,938 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OpenAlerts real-time monitoring dashboard.
|
|
3
|
-
* Tabs: Activity (unified event + log timeline), System Logs, Health.
|
|
4
|
-
* Activity shows both OpenAlerts engine events AND OpenClaw internal logs
|
|
5
|
-
* grouped by sessionId for a complete picture of what's happening.
|
|
6
|
-
*/
|
|
7
|
-
export function getDashboardHtml() {
|
|
8
|
-
return `<!DOCTYPE html>
|
|
9
|
-
<html lang="en">
|
|
10
|
-
<head>
|
|
11
|
-
<meta charset="utf-8">
|
|
12
|
-
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
13
|
-
<title>OpenAlerts Monitor</title>
|
|
14
|
-
<style>
|
|
15
|
-
*{margin:0;padding:0;box-sizing:border-box}
|
|
16
|
-
body{font-family:'SF Mono','Cascadia Code','Consolas',monospace;background:#0d1117;color:#c9d1d9;font-size:14px;overflow:hidden;height:100vh}
|
|
17
|
-
.grid{display:grid;grid-template-rows:auto auto 1fr;height:100vh}
|
|
18
|
-
|
|
19
|
-
/* ── Top bar ──────────────────── */
|
|
20
|
-
.topbar{background:#161b22;border-bottom:1px solid #30363d;padding:8px 16px;display:flex;align-items:center;gap:16px;flex-wrap:wrap}
|
|
21
|
-
.topbar h1{font-size:16px;font-weight:600;color:#f0f6fc;letter-spacing:0.5px}
|
|
22
|
-
.dot{width:7px;height:7px;border-radius:50%;display:inline-block;margin-right:4px}
|
|
23
|
-
.dot.live{background:#3fb950;animation:pulse 2s infinite}
|
|
24
|
-
.dot.dead{background:#f85149}
|
|
25
|
-
@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.4}}
|
|
26
|
-
.stat{color:#8b949e;font-size:12px}
|
|
27
|
-
.stat b{color:#c9d1d9;font-weight:500}
|
|
28
|
-
|
|
29
|
-
/* ── Tabs ──────────────────── */
|
|
30
|
-
.tabbar{background:#161b22;border-bottom:1px solid #30363d;display:flex}
|
|
31
|
-
.tab{padding:7px 18px;font-size:13px;font-weight:600;color:#8b949e;cursor:pointer;border-bottom:2px solid transparent;transition:all 0.15s}
|
|
32
|
-
.tab:hover{color:#c9d1d9;background:#1c2129}
|
|
33
|
-
.tab.active{color:#58a6ff;border-bottom-color:#58a6ff}
|
|
34
|
-
.tab-content{display:none;overflow:hidden;flex:1}
|
|
35
|
-
.tab-content.active{display:flex;overflow:hidden}
|
|
36
|
-
.content{display:flex;flex-direction:column;overflow:hidden}
|
|
37
|
-
|
|
38
|
-
/* ── Activity layout ──────────────────── */
|
|
39
|
-
.activity-panels{display:grid;grid-template-columns:1fr 280px;gap:0;overflow:hidden;flex:1}
|
|
40
|
-
@media(max-width:900px){.activity-panels{grid-template-columns:1fr}}
|
|
41
|
-
.panel{display:flex;flex-direction:column;overflow:hidden}
|
|
42
|
-
.panel-header{background:#161b22;padding:6px 12px;font-size:12px;font-weight:600;color:#8b949e;text-transform:uppercase;letter-spacing:0.8px;border-bottom:1px solid #30363d;flex-shrink:0;display:flex;align-items:center;justify-content:space-between}
|
|
43
|
-
.panel:first-child{border-right:1px solid #30363d}
|
|
44
|
-
.scroll{flex:1;overflow-y:auto}
|
|
45
|
-
.scroll::-webkit-scrollbar{width:5px}
|
|
46
|
-
.scroll::-webkit-scrollbar-thumb{background:#30363d;border-radius:3px}
|
|
47
|
-
.empty-msg{color:#484f58;padding:30px 14px;text-align:center;font-style:italic;font-size:12px}
|
|
48
|
-
|
|
49
|
-
/* ── Session flow (collapsible group) ──────────────────── */
|
|
50
|
-
.flow{border-bottom:1px solid #21262d}
|
|
51
|
-
.flow-hdr{padding:6px 10px;display:flex;align-items:center;gap:6px;cursor:pointer;font-size:11px;background:#161b22;border-left:3px solid #30363d;transition:all 0.12s}
|
|
52
|
-
.flow-hdr:hover{background:#1c2129}
|
|
53
|
-
.flow.active .flow-hdr{border-left-color:#58a6ff}
|
|
54
|
-
.flow.done .flow-hdr{border-left-color:#3fb950}
|
|
55
|
-
.flow.error .flow-hdr{border-left-color:#f85149}
|
|
56
|
-
.flow-arr{color:#484f58;font-size:9px;width:12px;text-align:center;transition:transform 0.12s;flex-shrink:0}
|
|
57
|
-
.flow-arr.shut{transform:rotate(-90deg)}
|
|
58
|
-
.flow-lbl{font-weight:600;color:#c9d1d9;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:260px}
|
|
59
|
-
.flow-badge{font-size:9px;padding:1px 5px;border-radius:3px;font-weight:700;flex-shrink:0}
|
|
60
|
-
.flow-badge.active{background:#1f3a5f;color:#58a6ff}
|
|
61
|
-
.flow-badge.done{background:#1a3a2a;color:#3fb950}
|
|
62
|
-
.flow-badge.error{background:#3d1a1a;color:#f85149}
|
|
63
|
-
.flow-info{color:#484f58;font-size:10px;margin-left:auto;white-space:nowrap;flex-shrink:0}
|
|
64
|
-
.flow-body{overflow:hidden;transition:max-height 0.2s ease-out}
|
|
65
|
-
.flow-body.shut{max-height:0!important;overflow:hidden}
|
|
66
|
-
|
|
67
|
-
/* ── Event/Log row ──────────────────── */
|
|
68
|
-
.row{padding:3px 10px 3px 24px;border-top:1px solid #0d1117;font-size:12px;line-height:1.5;animation:fi 0.15s ease}
|
|
69
|
-
.row:hover{background:#0d1117}
|
|
70
|
-
.row.standalone{padding-left:10px;border-bottom:1px solid #21262d;border-top:none}
|
|
71
|
-
.row.deep{padding-left:38px}
|
|
72
|
-
|
|
73
|
-
/* OpenAlerts event row */
|
|
74
|
-
.row .r-main{display:flex;align-items:center;gap:5px}
|
|
75
|
-
.r-time{color:#484f58;font-size:11px;min-width:55px;flex-shrink:0}
|
|
76
|
-
.r-icon{width:14px;text-align:center;flex-shrink:0;font-size:12px}
|
|
77
|
-
.r-type{font-weight:600;min-width:110px;font-size:11px;flex-shrink:0}
|
|
78
|
-
.r-type.llm{color:#58a6ff} .r-type.tool{color:#bc8cff} .r-type.agent{color:#3fb950}
|
|
79
|
-
.r-type.session{color:#d29922} .r-type.infra{color:#f85149} .r-type.custom{color:#8b949e}
|
|
80
|
-
.r-type.watchdog{color:#6e7681}
|
|
81
|
-
.r-oc{font-size:9px;padding:0 4px;border-radius:3px;font-weight:700;flex-shrink:0}
|
|
82
|
-
.r-oc.success{background:#1a3a2a;color:#3fb950}
|
|
83
|
-
.r-oc.error{background:#3d1a1a;color:#f85149}
|
|
84
|
-
.r-oc.timeout{background:#3d2e1a;color:#d29922}
|
|
85
|
-
.r-pills{display:flex;gap:4px;flex-wrap:wrap;margin-left:auto;align-items:center}
|
|
86
|
-
.p{font-size:10px;background:#21262d;padding:0 5px;border-radius:3px;white-space:nowrap}
|
|
87
|
-
.p.t{color:#bc8cff;background:#2a1f3d} .p.d{color:#d29922} .p.tk{color:#58a6ff}
|
|
88
|
-
.p.q{color:#f0883e} .p.m{color:#8b949e} .p.ch{color:#d2a8ff} .p.s{color:#6e7681;font-size:8px}
|
|
89
|
-
.r-det{padding:1px 0 1px 70px;color:#6e7681;font-size:11px}
|
|
90
|
-
.r-det .err{color:#f85149} .r-det .dim{color:#484f58} .r-det .sc{color:#d29922}
|
|
91
|
-
|
|
92
|
-
/* OpenClaw log row */
|
|
93
|
-
.row.log .r-main{display:flex;align-items:baseline;gap:5px}
|
|
94
|
-
.r-lvl{font-size:9px;font-weight:700;min-width:38px;flex-shrink:0}
|
|
95
|
-
.r-lvl.DEBUG{color:#6e7681} .r-lvl.INFO{color:#58a6ff} .r-lvl.WARN{color:#d29922} .r-lvl.ERROR{color:#f85149}
|
|
96
|
-
.r-sub{color:#bc8cff;font-size:10px;min-width:100px;max-width:140px;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
97
|
-
.r-msg{color:#c9d1d9;font-size:11px;word-break:break-word}
|
|
98
|
-
.r-kvs{color:#484f58;font-size:10px;padding-left:70px}
|
|
99
|
-
.r-kvs span{margin-right:8px}
|
|
100
|
-
|
|
101
|
-
@keyframes fi{from{opacity:0;transform:translateY(-2px)}to{opacity:1;transform:none}}
|
|
102
|
-
|
|
103
|
-
/* ── Alerts panel ──────────────────── */
|
|
104
|
-
.al{padding:6px 10px;border-bottom:1px solid #21262d;font-size:12px;animation:fi 0.2s}
|
|
105
|
-
.al-sev{font-weight:700;text-transform:uppercase;font-size:10px;letter-spacing:0.4px}
|
|
106
|
-
.al-sev.error{color:#f85149} .al-sev.warn{color:#d29922} .al-sev.critical{color:#ff7b72} .al-sev.info{color:#58a6ff}
|
|
107
|
-
.al-title{color:#c9d1d9;margin-top:1px;font-size:12px}
|
|
108
|
-
.al-detail{color:#8b949e;margin-top:1px;font-size:11px}
|
|
109
|
-
.al-time{color:#484f58;font-size:11px;margin-top:1px}
|
|
110
|
-
|
|
111
|
-
/* ── Rules ──────────────────── */
|
|
112
|
-
.rules{border-top:1px solid #30363d;padding:6px 10px;flex-shrink:0}
|
|
113
|
-
.rules h3{font-size:12px;color:#8b949e;text-transform:uppercase;letter-spacing:0.7px;margin-bottom:4px}
|
|
114
|
-
.rl{display:flex;align-items:center;gap:5px;font-size:12px;padding:1px 0}
|
|
115
|
-
.rl-d{width:5px;height:5px;border-radius:50%;flex-shrink:0}
|
|
116
|
-
.rl-d.ok{background:#3fb950} .rl-d.fired{background:#f85149;animation:pulse 1s infinite}
|
|
117
|
-
.rl-s{color:#8b949e;margin-left:auto;font-size:11px}
|
|
118
|
-
|
|
119
|
-
/* ── Logs tab ──────────────────── */
|
|
120
|
-
.logs-t{flex:1;display:flex;flex-direction:column;overflow:hidden}
|
|
121
|
-
.log-bar{background:#161b22;padding:8px 12px;border-bottom:1px solid #30363d;display:flex;align-items:center;gap:10px;flex-wrap:wrap;flex-shrink:0;font-size:12px}
|
|
122
|
-
.log-bar select,.log-bar input{background:#0d1117;border:1px solid #30363d;color:#c9d1d9;font-family:inherit;font-size:11px;padding:3px 6px;border-radius:3px}
|
|
123
|
-
.log-bar input[type="text"]{min-width:180px}
|
|
124
|
-
.log-bar button{background:#21262d;border:1px solid #30363d;color:#c9d1d9;font-family:inherit;font-size:10px;padding:3px 10px;border-radius:3px;cursor:pointer;transition:all 0.12s}
|
|
125
|
-
.log-bar button:hover{background:#30363d;border-color:#484f58}
|
|
126
|
-
.log-bar button:active{background:#0d1117}
|
|
127
|
-
.log-bar label{color:#8b949e;display:flex;align-items:center;gap:4px;cursor:pointer}
|
|
128
|
-
.log-bar label:hover{color:#c9d1d9}
|
|
129
|
-
.log-bar input[type="checkbox"]{cursor:pointer}
|
|
130
|
-
.log-filters{display:flex;gap:8px;flex-wrap:wrap;align-items:center}
|
|
131
|
-
.log-filters label{font-size:10px}
|
|
132
|
-
.log-truncate{background:#3d2e1a;border:1px solid #d29922;color:#d29922;padding:4px 10px;font-size:10px;border-radius:3px;margin:8px 12px;display:none}
|
|
133
|
-
.log-truncate.show{display:block}
|
|
134
|
-
.log-list{flex:1;overflow-y:auto;font-size:12px}
|
|
135
|
-
.log-e{padding:2px 12px;border-bottom:1px solid #161b22;display:flex;gap:6px;align-items:baseline;position:relative}
|
|
136
|
-
.log-e:hover{background:#161b22}
|
|
137
|
-
.log-e:hover .log-copy{opacity:1}
|
|
138
|
-
.log-ts{color:#484f58;font-size:11px;min-width:70px;flex-shrink:0}
|
|
139
|
-
.log-lv{font-size:10px;font-weight:700;min-width:42px;flex-shrink:0}
|
|
140
|
-
.log-lv.TRACE{color:#6e7681} .log-lv.DEBUG{color:#8b949e} .log-lv.INFO{color:#58a6ff} .log-lv.WARN{color:#d29922} .log-lv.ERROR{color:#f85149} .log-lv.FATAL{color:#ff7b72;background:#3d1a1a;padding:1px 3px;border-radius:2px}
|
|
141
|
-
.log-su{color:#bc8cff;font-size:11px;min-width:110px;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
142
|
-
.log-mg{color:#c9d1d9;font-size:12px;word-break:break-all;flex:1}
|
|
143
|
-
.log-copy{position:absolute;right:8px;top:4px;font-size:9px;color:#484f58;cursor:pointer;border:1px solid #30363d;background:#161b22;padding:1px 4px;border-radius:2px;font-family:inherit;opacity:0;transition:opacity 0.12s}
|
|
144
|
-
.log-copy:hover{color:#c9d1d9;border-color:#484f58}
|
|
145
|
-
|
|
146
|
-
/* ── Health tab ──────────────────── */
|
|
147
|
-
.health-t{flex:1;overflow-y:auto;padding:14px}
|
|
148
|
-
.h-sec{margin-bottom:16px}
|
|
149
|
-
.h-sec h3{font-size:13px;color:#8b949e;text-transform:uppercase;letter-spacing:0.7px;margin-bottom:6px;padding-bottom:3px;border-bottom:1px solid #21262d}
|
|
150
|
-
.h-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:6px}
|
|
151
|
-
.h-card{background:#161b22;border:1px solid #21262d;border-radius:5px;padding:8px 12px}
|
|
152
|
-
.h-card .lb{color:#8b949e;font-size:11px;margin-bottom:2px}
|
|
153
|
-
.h-card .vl{color:#c9d1d9;font-size:15px;font-weight:600}
|
|
154
|
-
.h-card .vl.ok{color:#3fb950} .h-card .vl.bad{color:#f85149}
|
|
155
|
-
.h-tbl{width:100%;border-collapse:collapse}
|
|
156
|
-
.h-tbl td{padding:4px 8px;font-size:13px;border-bottom:1px solid #161b22}
|
|
157
|
-
.h-tbl td:first-child{color:#8b949e;width:140px}
|
|
158
|
-
.h-tbl th{padding:4px 8px;font-size:12px;color:#8b949e;text-align:left;border-bottom:1px solid #30363d;font-weight:600}
|
|
159
|
-
|
|
160
|
-
/* ── Expandable event detail ──────────────────── */
|
|
161
|
-
.row.expandable{cursor:pointer}
|
|
162
|
-
.row.expandable:hover{background:#161b22}
|
|
163
|
-
.ev-detail{background:#0d1117;border:1px solid #21262d;border-radius:4px;margin:4px 10px 6px 24px;padding:8px 10px;display:none;animation:fi 0.15s ease}
|
|
164
|
-
.ev-detail.open{display:block}
|
|
165
|
-
.ev-detail h4{font-size:10px;color:#58a6ff;text-transform:uppercase;letter-spacing:0.6px;margin:6px 0 3px;font-weight:600}
|
|
166
|
-
.ev-detail h4:first-child{margin-top:0}
|
|
167
|
-
.ev-detail .dv{display:flex;gap:4px;align-items:baseline;font-size:11px;padding:1px 0}
|
|
168
|
-
.ev-detail .dk{color:#8b949e;min-width:90px;flex-shrink:0}
|
|
169
|
-
.ev-detail .dd{color:#c9d1d9;word-break:break-all}
|
|
170
|
-
.ev-detail .dd .cp-btn{font-size:9px;color:#484f58;cursor:pointer;margin-left:4px;border:1px solid #30363d;background:#161b22;padding:0 3px;border-radius:2px;font-family:inherit}
|
|
171
|
-
.ev-detail .dd .cp-btn:hover{color:#c9d1d9;border-color:#484f58}
|
|
172
|
-
.ev-detail .err-block{background:#1a0a0a;border:1px solid #3d1a1a;border-radius:3px;padding:6px 8px;color:#f85149;font-size:11px;max-height:120px;overflow-y:auto;white-space:pre-wrap;word-break:break-all}
|
|
173
|
-
.ev-detail .meta-grid{display:grid;grid-template-columns:auto 1fr;gap:2px 8px;font-size:11px}
|
|
174
|
-
.ev-detail .meta-grid .mk{color:#8b949e;text-align:right}
|
|
175
|
-
.ev-detail .meta-grid .mv{color:#c9d1d9;word-break:break-all}
|
|
176
|
-
|
|
177
|
-
/* Inline row indicators */
|
|
178
|
-
.sev-dot{width:6px;height:6px;border-radius:50%;display:inline-block;flex-shrink:0}
|
|
179
|
-
.sev-dot.info{background:#58a6ff} .sev-dot.warn{background:#d29922} .sev-dot.error{background:#f85149} .sev-dot.critical{background:#ff7b72}
|
|
180
|
-
.p.cost{color:#3fb950;background:#1a3a2a} .p.agent{color:#d2a8ff;background:#2a1f3d}
|
|
181
|
-
|
|
182
|
-
/* ── Flow summary bar ──────────────────── */
|
|
183
|
-
.flow-summary{background:#0d1117;padding:4px 10px;font-size:10px;color:#8b949e;display:flex;flex-wrap:wrap;gap:4px 12px;border-bottom:1px solid #21262d;align-items:center}
|
|
184
|
-
.flow-summary .fs-v{color:#c9d1d9;font-weight:600}
|
|
185
|
-
.flow-summary .fs-err{color:#f85149;font-weight:600}
|
|
186
|
-
.flow-summary .fs-tools{color:#bc8cff}
|
|
187
|
-
.flow-summary .fs-agent{color:#d2a8ff}
|
|
188
|
-
|
|
189
|
-
/* ── Expandable log row ──────────────────── */
|
|
190
|
-
.log-e{cursor:pointer;transition:background 0.1s}
|
|
191
|
-
.log-detail{display:none;background:#0d1117;border:1px solid #21262d;border-radius:3px;margin:2px 12px 4px 12px;padding:6px 8px;animation:fi 0.12s ease}
|
|
192
|
-
.log-detail.open{display:block}
|
|
193
|
-
.log-detail .ld-grid{display:grid;grid-template-columns:auto 1fr;gap:1px 8px;font-size:10px}
|
|
194
|
-
.log-detail .ld-grid .lk{color:#8b949e;text-align:right}
|
|
195
|
-
.log-detail .ld-grid .lv{color:#c9d1d9;word-break:break-all}
|
|
196
|
-
.log-detail .ld-file{color:#484f58;font-size:10px;margin-top:3px}
|
|
197
|
-
</style>
|
|
198
|
-
</head>
|
|
199
|
-
<body>
|
|
200
|
-
<div class="grid">
|
|
201
|
-
<div class="topbar">
|
|
202
|
-
<h1><span class="dot dead" id="sDot"></span> OPENALERTS</h1>
|
|
203
|
-
<span class="stat" id="sConn">connecting...</span>
|
|
204
|
-
<span class="stat">up: <b id="sUp">--</b></span>
|
|
205
|
-
<span class="stat">msgs: <b id="sMsgs">0</b></span>
|
|
206
|
-
<span class="stat">err: <b id="sErr">0</b></span>
|
|
207
|
-
<span class="stat">tools: <b id="sTools">0</b></span>
|
|
208
|
-
<span class="stat">agents: <b id="sAgents">0</b></span>
|
|
209
|
-
</div>
|
|
210
|
-
<div class="tabbar">
|
|
211
|
-
<div class="tab active" data-tab="activity">Activity</div>
|
|
212
|
-
<div class="tab" data-tab="logs">System Logs</div>
|
|
213
|
-
<div class="tab" data-tab="health">Health</div>
|
|
214
|
-
<div class="tab" data-tab="debug">Debug</div>
|
|
215
|
-
</div>
|
|
216
|
-
<div class="content">
|
|
217
|
-
<!-- Activity -->
|
|
218
|
-
<div class="tab-content active" id="tab-activity">
|
|
219
|
-
<div class="activity-panels">
|
|
220
|
-
<div class="panel">
|
|
221
|
-
<div class="panel-header"><span>Live Timeline</span><span style="color:#484f58;font-weight:400" id="evCnt">0</span></div>
|
|
222
|
-
<div class="scroll" id="evList"><div class="empty-msg" id="emptyMsg">Waiting for events... send a message to your bot.</div></div>
|
|
223
|
-
</div>
|
|
224
|
-
<div class="panel">
|
|
225
|
-
<div class="panel-header">Alerts</div>
|
|
226
|
-
<div class="scroll" id="alList"><div class="empty-msg" id="alEmpty">No alerts.</div></div>
|
|
227
|
-
<div class="rules" id="rulesEl"><h3>Rules</h3></div>
|
|
228
|
-
</div>
|
|
229
|
-
</div>
|
|
230
|
-
</div>
|
|
231
|
-
<!-- System Logs -->
|
|
232
|
-
<div class="tab-content" id="tab-logs">
|
|
233
|
-
<div class="logs-t">
|
|
234
|
-
<div class="log-bar">
|
|
235
|
-
<label>Subsystem:</label>
|
|
236
|
-
<select id="lF"><option value="">All</option></select>
|
|
237
|
-
<input type="text" id="lS" placeholder="Search logs..." title="Ctrl+F to focus">
|
|
238
|
-
<div class="log-filters">
|
|
239
|
-
<label title="Show TRACE logs"><input type="checkbox" id="lv-TRACE" checked> TRACE</label>
|
|
240
|
-
<label title="Show DEBUG logs"><input type="checkbox" id="lv-DEBUG" checked> DEBUG</label>
|
|
241
|
-
<label title="Show INFO logs"><input type="checkbox" id="lv-INFO" checked> INFO</label>
|
|
242
|
-
<label title="Show WARN logs"><input type="checkbox" id="lv-WARN" checked> WARN</label>
|
|
243
|
-
<label title="Show ERROR logs"><input type="checkbox" id="lv-ERROR" checked> ERROR</label>
|
|
244
|
-
<label title="Show FATAL logs"><input type="checkbox" id="lv-FATAL" checked> FATAL</label>
|
|
245
|
-
</div>
|
|
246
|
-
<button id="lR" title="Refresh logs (Ctrl+R)">↻ Refresh</button>
|
|
247
|
-
<button id="lE" title="Export filtered logs">⬇ Export</button>
|
|
248
|
-
<label title="Fetch all logs (not just recent) when searching or filtering"><input type="checkbox" id="lAll"> All logs</label>
|
|
249
|
-
<label style="margin-left:auto" title="Auto-scroll to new logs"><input type="checkbox" id="lA" checked> Auto-follow</label>
|
|
250
|
-
</div>
|
|
251
|
-
<div class="log-truncate" id="lT">⚠ Logs truncated — showing most recent entries only. Check "All logs" to search across the full log file.</div>
|
|
252
|
-
<div class="log-list" id="logList"><div class="empty-msg">Loading...</div></div>
|
|
253
|
-
</div>
|
|
254
|
-
</div>
|
|
255
|
-
<!-- Health -->
|
|
256
|
-
<div class="tab-content" id="tab-health">
|
|
257
|
-
<div class="health-t" id="hC"><div class="empty-msg">Loading...</div></div>
|
|
258
|
-
</div>
|
|
259
|
-
<!-- Debug -->
|
|
260
|
-
<div class="tab-content" id="tab-debug">
|
|
261
|
-
<div class="health-t">
|
|
262
|
-
<div class="h-sec">
|
|
263
|
-
<h3>OpenAlerts State Snapshot</h3>
|
|
264
|
-
<button id="dbRefresh" style="margin-left:10px;padding:4px 12px;font-size:11px">↻ Refresh</button>
|
|
265
|
-
<div class="h-grid" id="dbState" style="margin-top:12px"></div>
|
|
266
|
-
</div>
|
|
267
|
-
<div class="h-sec">
|
|
268
|
-
<h3>Recent Events (Last 20)</h3>
|
|
269
|
-
<div id="dbEvents" style="font-size:11px;max-height:400px;overflow-y:auto"></div>
|
|
270
|
-
</div>
|
|
271
|
-
<div class="h-sec">
|
|
272
|
-
<h3>Alert Rules Status</h3>
|
|
273
|
-
<div id="dbRules"></div>
|
|
274
|
-
</div>
|
|
275
|
-
</div>
|
|
276
|
-
</div>
|
|
277
|
-
</div>
|
|
278
|
-
</div>
|
|
279
|
-
<script>
|
|
280
|
-
(function(){
|
|
281
|
-
var MAX_FLOWS=150, MAX_STANDALONE=300, MAX_ALERTS=50, STALE_MS=120000;
|
|
282
|
-
var total=0, paused=false, evSrc=null;
|
|
283
|
-
var $=function(i){return document.getElementById(i)};
|
|
284
|
-
var evList=$('evList'), emptyMsg=$('emptyMsg'), alList=$('alList'), alEmpty=$('alEmpty'), evCnt=$('evCnt');
|
|
285
|
-
|
|
286
|
-
function esc(s){var d=document.createElement('div');d.textContent=s;return d.innerHTML}
|
|
287
|
-
function cat(t){if(!t)return'custom';var p=t.split('.')[0];return['llm','tool','agent','session','infra','watchdog'].indexOf(p)>=0?p:'custom'}
|
|
288
|
-
function fT(ts){if(!ts)return'';var d=typeof ts==='number'?new Date(ts):new Date(ts);return d.toLocaleTimeString('en-US',{hour12:false,hour:'2-digit',minute:'2-digit',second:'2-digit'})}
|
|
289
|
-
|
|
290
|
-
// ─── Human-readable event labels ──────────────────────
|
|
291
|
-
var EVT_LABELS={
|
|
292
|
-
'llm.call':['\\u{1F916}','LLM Called'],
|
|
293
|
-
'llm.error':['\\u26A0','LLM Failed'],
|
|
294
|
-
'llm.token_usage':['\\u{1F4CA}','Token Usage'],
|
|
295
|
-
'tool.call':['\\u{1F527}','Tool Executed'],
|
|
296
|
-
'tool.error':['\\u{1F527}','Tool Failed'],
|
|
297
|
-
'agent.start':['\\u25B6','Agent Started'],
|
|
298
|
-
'agent.end':['\\u23F9','Agent Finished'],
|
|
299
|
-
'agent.error':['\\u{1F6A8}','Agent Error'],
|
|
300
|
-
'agent.stuck':['\\u23F3','Agent Stuck'],
|
|
301
|
-
'session.start':['\\u{1F4AC}','Session Started'],
|
|
302
|
-
'session.end':['\\u{1F3C1}','Session Ended'],
|
|
303
|
-
'session.stuck':['\\u23F3','Session Stuck'],
|
|
304
|
-
'infra.error':['\\u{1F6A8}','Infra Error'],
|
|
305
|
-
'infra.heartbeat':['\\u2764','Heartbeat'],
|
|
306
|
-
'infra.queue_depth':['\\u{1F4E6}','Queue Depth'],
|
|
307
|
-
'watchdog.tick':['\\u{1F440}','Health Check'],
|
|
308
|
-
'msg.delivered':['\\u2709','Message Sent'],
|
|
309
|
-
'msg.in':['\\u{1F4E5}','Message Received'],
|
|
310
|
-
'msg.out':['\\u{1F4E4}','Message Sending']
|
|
311
|
-
};
|
|
312
|
-
function friendlyLabel(ft){return EVT_LABELS[ft]||['\\u2022',ft]}
|
|
313
|
-
function fISO(ts){if(!ts)return'';return new Date(typeof ts==='number'?ts:Date.parse(ts)).toISOString()}
|
|
314
|
-
function fD(ms){if(ms==null)return'';if(ms<1000)return ms+'ms';if(ms<60000)return(ms/1000).toFixed(1)+'s';return Math.floor(ms/60000)+'m '+Math.round((ms%60000)/1000)+'s'}
|
|
315
|
-
function fU(ms){var s=Math.floor(ms/1000),m=Math.floor(s/60),h=Math.floor(m/60);return h>0?h+'h '+m%60+'m':m+'m '+s%60+'s'}
|
|
316
|
-
function fAgo(ts){if(!ts)return'never';var d=Date.now()-ts;if(d<0)d=0;if(d<1000)return'just now';if(d<60000)return Math.floor(d/1000)+'s ago';if(d<3600000)return Math.floor(d/60000)+'m ago';return Math.floor(d/3600000)+'h ago'}
|
|
317
|
-
|
|
318
|
-
/** Copy text to clipboard with visual feedback */
|
|
319
|
-
function cpToClip(text,btn){
|
|
320
|
-
navigator.clipboard.writeText(text).then(function(){
|
|
321
|
-
var orig=btn.textContent;btn.textContent='\\u2713';setTimeout(function(){btn.textContent=orig},800);
|
|
322
|
-
}).catch(function(){});
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// ─── Subsystem label helpers ──────────────────────
|
|
326
|
-
var subIcons={'diagnostic':'\\u2139','plugins':'\\u2699','agent/embedded':'\\u25B6','gateway':'\\u2302','gateway/ws':'\\u21C4','heartbeat':'\\u2764','canvas':'\\u25A1'};
|
|
327
|
-
function subIcon(s){for(var k in subIcons)if(s.indexOf(k)>=0)return subIcons[k];return'\\u2022'}
|
|
328
|
-
|
|
329
|
-
// ─── Flow tracker ──────────────────────
|
|
330
|
-
var flows={}, flowOrd=[];
|
|
331
|
-
|
|
332
|
-
function getFlow(sid,ev){
|
|
333
|
-
if(flows[sid])return flows[sid];
|
|
334
|
-
var c=document.createElement('div');c.className='flow active';
|
|
335
|
-
var hdr=document.createElement('div');hdr.className='flow-hdr';
|
|
336
|
-
var short=sid.length>20?sid.slice(0,8)+'..'+sid.slice(-4):sid;
|
|
337
|
-
hdr.innerHTML='<span class="flow-arr">\\u25BC</span><span class="flow-lbl" title="'+esc(sid)+'">Session '+esc(short)+'</span><span class="flow-badge active" data-r="st">Running</span><span class="flow-info" data-r="info">'+fT(ev.ts||ev.tsMs)+'</span>';
|
|
338
|
-
var summary=document.createElement('div');summary.className='flow-summary';summary.setAttribute('data-r','summary');
|
|
339
|
-
var body=document.createElement('div');body.className='flow-body';
|
|
340
|
-
hdr.addEventListener('click',function(){
|
|
341
|
-
var shut=!body.classList.contains('shut');
|
|
342
|
-
body.classList.toggle('shut',shut);
|
|
343
|
-
summary.style.display=shut?'none':'flex';
|
|
344
|
-
hdr.querySelector('.flow-arr').classList.toggle('shut',shut);
|
|
345
|
-
});
|
|
346
|
-
c.appendChild(hdr);c.appendChild(summary);c.appendChild(body);
|
|
347
|
-
var f={el:c,body:body,hdr:hdr,summary:summary,st:'active',n:0,startTs:Date.now(),sid:sid,err:false,errCount:0,dur:0,tok:0,cost:0,tools:0,llms:0,toolNames:{},agentId:''};
|
|
348
|
-
flows[sid]=f;flowOrd.push(sid);
|
|
349
|
-
emptyMsg.style.display='none';
|
|
350
|
-
evList.insertBefore(c,evList.firstChild);
|
|
351
|
-
return f;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
function updFlow(f,st){
|
|
355
|
-
f.st=st;f.el.className='flow '+st;
|
|
356
|
-
var sEl=f.hdr.querySelector('[data-r="st"]');
|
|
357
|
-
var stLabels={active:'Running',done:'Completed',error:'Failed'};
|
|
358
|
-
if(sEl){sEl.className='flow-badge '+st;sEl.textContent=stLabels[st]||st}
|
|
359
|
-
var iEl=f.hdr.querySelector('[data-r="info"]');
|
|
360
|
-
if(iEl){
|
|
361
|
-
var ps=[f.n+' events'];
|
|
362
|
-
if(f.dur>0)ps.push(fD(f.dur));
|
|
363
|
-
if(f.tok>0)ps.push(f.tok+' tokens');
|
|
364
|
-
if(f.cost>0)ps.push('$'+f.cost.toFixed(4));
|
|
365
|
-
if(f.tools>0)ps.push(f.tools+(f.tools===1?' tool':' tools'));
|
|
366
|
-
if(f.llms>0)ps.push(f.llms+(f.llms===1?' LLM call':' LLM calls'));
|
|
367
|
-
iEl.textContent=ps.join(' \\u00B7 ');
|
|
368
|
-
}
|
|
369
|
-
// Update summary bar
|
|
370
|
-
var sh='';
|
|
371
|
-
sh+='<span>Events: <span class="fs-v">'+f.n+'</span></span>';
|
|
372
|
-
if(f.dur>0)sh+='<span>Duration: <span class="fs-v">'+fD(f.dur)+'</span></span>';
|
|
373
|
-
if(f.tok>0)sh+='<span>Tokens used: <span class="fs-v">'+f.tok+'</span></span>';
|
|
374
|
-
if(f.cost>0)sh+='<span>Cost: <span class="fs-v">$'+f.cost.toFixed(4)+'</span></span>';
|
|
375
|
-
var tn=Object.keys(f.toolNames);
|
|
376
|
-
if(tn.length)sh+='<span>Tools used: <span class="fs-tools">'+tn.map(esc).join(', ')+'</span></span>';
|
|
377
|
-
if(f.errCount>0)sh+='<span>\\u26A0 Errors: <span class="fs-err">'+f.errCount+'</span></span>';
|
|
378
|
-
if(f.agentId)sh+='<span>Agent: <span class="fs-agent">'+esc(f.agentId)+'</span></span>';
|
|
379
|
-
f.summary.innerHTML=sh;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// ─── Build expandable event detail panel ──────────────────────
|
|
383
|
-
function buildEvDetail(ev){
|
|
384
|
-
var d=document.createElement('div');d.className='ev-detail';
|
|
385
|
-
var m=ev.meta||{};
|
|
386
|
-
var h='<h4>Identity</h4>';
|
|
387
|
-
h+=dvRow('Type',ev.type||'?');
|
|
388
|
-
h+=dvRow('Timestamp',fISO(ev.ts));
|
|
389
|
-
if(ev.sessionKey)h+=dvRowCopy('Session Key',ev.sessionKey);
|
|
390
|
-
if(ev.agentId)h+=dvRowCopy('Agent ID',ev.agentId);
|
|
391
|
-
if(ev.channel)h+=dvRow('Channel',ev.channel);
|
|
392
|
-
if(ev.outcome)h+=dvRow('Outcome',ev.outcome);
|
|
393
|
-
if(ev.severity)h+=dvRow('Severity',ev.severity);
|
|
394
|
-
|
|
395
|
-
var hasMetrics=ev.durationMs!=null||ev.tokenCount!=null||ev.costUsd!=null||ev.queueDepth!=null||ev.ageMs!=null;
|
|
396
|
-
if(hasMetrics){
|
|
397
|
-
h+='<h4>Metrics</h4>';
|
|
398
|
-
if(ev.durationMs!=null)h+=dvRow('Duration',fD(ev.durationMs)+' ('+ev.durationMs+'ms)');
|
|
399
|
-
if(ev.tokenCount!=null)h+=dvRow('Tokens',String(ev.tokenCount));
|
|
400
|
-
if(ev.costUsd!=null)h+=dvRow('Cost','$'+ev.costUsd.toFixed(6));
|
|
401
|
-
if(ev.queueDepth!=null)h+=dvRow('Queue Depth',String(ev.queueDepth));
|
|
402
|
-
if(ev.ageMs!=null)h+=dvRow('Age',fD(ev.ageMs)+' ('+ev.ageMs+'ms)');
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
if(ev.error){
|
|
406
|
-
h+='<h4>Error</h4>';
|
|
407
|
-
h+='<div class="err-block">'+esc(ev.error)+'</div>';
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
var mKeys=Object.keys(m);
|
|
411
|
-
if(mKeys.length){
|
|
412
|
-
h+='<h4>Meta ('+mKeys.length+' fields)</h4>';
|
|
413
|
-
h+='<div class="meta-grid">';
|
|
414
|
-
for(var i=0;i<mKeys.length;i++){
|
|
415
|
-
var k=mKeys[i],v=m[k];
|
|
416
|
-
var vs=typeof v==='object'?JSON.stringify(v):String(v!=null?v:'');
|
|
417
|
-
h+='<span class="mk">'+esc(k)+'</span><span class="mv">'+esc(vs)+'</span>';
|
|
418
|
-
}
|
|
419
|
-
h+='</div>';
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
d.innerHTML=h;
|
|
423
|
-
// Wire up copy buttons after inserting
|
|
424
|
-
setTimeout(function(){
|
|
425
|
-
d.querySelectorAll('.cp-btn').forEach(function(btn){
|
|
426
|
-
btn.addEventListener('click',function(e){e.stopPropagation();cpToClip(btn.getAttribute('data-cp'),btn)});
|
|
427
|
-
});
|
|
428
|
-
},0);
|
|
429
|
-
return d;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
function dvRow(label,val){return'<div class="dv"><span class="dk">'+esc(label)+'</span><span class="dd">'+esc(String(val))+'</span></div>'}
|
|
433
|
-
function dvRowCopy(label,val){return'<div class="dv"><span class="dk">'+esc(label)+'</span><span class="dd">'+esc(String(val))+'<button class="cp-btn" data-cp="'+esc(String(val))+'">copy</button></span></div>'}
|
|
434
|
-
|
|
435
|
-
// ─── Build an OpenAlerts event row ──────────────────────
|
|
436
|
-
function buildEvRow(ev,depth){
|
|
437
|
-
var wrap=document.createElement('div');
|
|
438
|
-
var div=document.createElement('div');
|
|
439
|
-
div.className='row expandable'+(depth===0?' standalone':depth>1?' deep':'');
|
|
440
|
-
var c=cat(ev.type),oc=ev.outcome||'',m=ev.meta||{};
|
|
441
|
-
var ft=ev.type||'?';
|
|
442
|
-
if(ft==='custom'&&m.openclawEventType==='session.state')ft='session.'+(m.sessionState||'state');
|
|
443
|
-
if(ft==='custom'&&m.openclawEventType==='message_sent')ft='msg.delivered';
|
|
444
|
-
if(ft==='custom'&&m.openclawHook==='message_received')ft='msg.in';
|
|
445
|
-
if(ft==='custom'&&m.openclawHook==='message_sending')ft='msg.out';
|
|
446
|
-
|
|
447
|
-
var fl=friendlyLabel(ft);
|
|
448
|
-
|
|
449
|
-
var h='<div class="r-main">';
|
|
450
|
-
h+='<span class="r-time">'+fT(ev.ts)+'</span>';
|
|
451
|
-
h+='<span class="r-icon">'+fl[0]+'</span>';
|
|
452
|
-
if(ev.severity)h+='<span class="sev-dot '+(ev.severity||'')+'" title="'+esc(ev.severity)+'"></span>';
|
|
453
|
-
h+='<span class="r-type '+c+'">'+esc(fl[1])+'</span>';
|
|
454
|
-
if(oc)h+='<span class="r-oc '+oc+'">'+(oc==='success'?'\\u2713 OK':oc==='error'?'\\u2717 Failed':oc==='timeout'?'\\u23F1 Timeout':'\\u25CB '+oc)+'</span>';
|
|
455
|
-
h+='<span class="r-pills">';
|
|
456
|
-
if(m.toolName)h+='<span class="p t">'+esc(String(m.toolName))+'</span>';
|
|
457
|
-
if(ev.durationMs!=null)h+='<span class="p d">'+fD(ev.durationMs)+'</span>';
|
|
458
|
-
if(ev.tokenCount!=null)h+='<span class="p tk">'+ev.tokenCount+' tokens</span>';
|
|
459
|
-
if(ev.costUsd!=null)h+='<span class="p cost">$'+ev.costUsd.toFixed(4)+'</span>';
|
|
460
|
-
if(ev.agentId)h+='<span class="p agent">'+esc(ev.agentId.length>12?ev.agentId.slice(0,8)+'..':ev.agentId)+'</span>';
|
|
461
|
-
if(ev.queueDepth!=null)h+='<span class="p q">Queue: '+ev.queueDepth+'</span>';
|
|
462
|
-
if(m.model)h+='<span class="p m">'+esc(String(m.model))+'</span>';
|
|
463
|
-
if(ev.channel)h+='<span class="p ch">via '+esc(ev.channel)+'</span>';
|
|
464
|
-
if(m.messageCount!=null)h+='<span class="p">'+m.messageCount+' msgs</span>';
|
|
465
|
-
if(m.content){var preview=String(m.content);if(preview.length>60)preview=preview.slice(0,57)+'...';h+='<span class="p">'+esc(preview)+'</span>'}
|
|
466
|
-
if(m.source&&String(m.source)!=='simulate')h+='<span class="p s">'+esc(String(m.source))+'</span>';
|
|
467
|
-
h+='</span></div>';
|
|
468
|
-
|
|
469
|
-
var ds=[];
|
|
470
|
-
if(ev.error)ds.push('<span class="err">'+esc(ev.error.length>120?ev.error.slice(0,120)+'...':ev.error)+'</span>');
|
|
471
|
-
if(ev.ageMs!=null)ds.push('idle for '+fD(ev.ageMs));
|
|
472
|
-
if(m.sessionState)ds.push('<span class="sc">'+esc(String(m.previousState||'?'))+' \\u2192 '+esc(String(m.sessionState))+'</span>');
|
|
473
|
-
if(m.provider)ds.push('<span class="dim">provider: '+esc(String(m.provider))+'</span>');
|
|
474
|
-
if(m.to)ds.push('<span class="dim">to: '+esc(String(m.to))+'</span>');
|
|
475
|
-
if(ev.sessionKey&&depth===0)ds.push('<span class="dim">session: '+esc(ev.sessionKey.slice(0,12))+'</span>');
|
|
476
|
-
if(ds.length)h+='<div class="r-det">'+ds.join(' \\u00B7 ')+'</div>';
|
|
477
|
-
|
|
478
|
-
div.innerHTML=h;
|
|
479
|
-
wrap.appendChild(div);
|
|
480
|
-
|
|
481
|
-
// Click to expand/collapse detail panel
|
|
482
|
-
var detail=buildEvDetail(ev);
|
|
483
|
-
wrap.appendChild(detail);
|
|
484
|
-
div.addEventListener('click',function(e){
|
|
485
|
-
if(e.target.classList.contains('cp-btn'))return;
|
|
486
|
-
detail.classList.toggle('open');
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
return wrap;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
// ─── Build an OpenClaw log row (Activity tab) ──────────────────────
|
|
493
|
-
function buildLogRow(entry,depth){
|
|
494
|
-
var div=document.createElement('div');
|
|
495
|
-
div.className='row log'+(depth===0?' standalone':depth>1?' deep':'');
|
|
496
|
-
var h='<div class="r-main">';
|
|
497
|
-
h+='<span class="r-time">'+fT(entry.tsMs||entry.ts)+'</span>';
|
|
498
|
-
h+='<span class="r-lvl '+esc(entry.level)+'">'+esc(entry.level)+'</span>';
|
|
499
|
-
h+='<span class="r-sub" title="'+esc(entry.subsystem)+'">'+subIcon(entry.subsystem)+' '+esc(entry.subsystem)+'</span>';
|
|
500
|
-
h+='<span class="r-msg">'+esc(entry.message)+'</span>';
|
|
501
|
-
h+='</div>';
|
|
502
|
-
|
|
503
|
-
// Show ALL parsed key=value pairs
|
|
504
|
-
var kvs=entry.extra||{};
|
|
505
|
-
var kvKeys=Object.keys(kvs);
|
|
506
|
-
if(kvKeys.length){
|
|
507
|
-
var kvParts=[];
|
|
508
|
-
for(var i=0;i<kvKeys.length;i++){
|
|
509
|
-
var k=kvKeys[i],v=kvs[k];
|
|
510
|
-
if(k==='sessionId'||k==='runId')kvParts.push('<span>'+esc(k)+'='+esc(v.length>16?v.slice(0,12)+'..':v)+'</span>');
|
|
511
|
-
else if(k==='durationMs')kvParts.push('<span>duration='+fD(parseInt(v))+'</span>');
|
|
512
|
-
else kvParts.push('<span>'+esc(k)+'='+esc(v)+'</span>');
|
|
513
|
-
}
|
|
514
|
-
if(kvParts.length)h+='<div class="r-kvs">'+kvParts.join('')+'</div>';
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
div.innerHTML=h;return div;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
// ─── Ingest OpenAlerts event ──────────────────────
|
|
521
|
-
function addEvent(ev){
|
|
522
|
-
total++;evCnt.textContent=total;emptyMsg.style.display='none';
|
|
523
|
-
if(paused)return;
|
|
524
|
-
var sid=ev.sessionKey||ev.agentId;
|
|
525
|
-
if(sid&&(flows[sid]||ev.type==='agent.start'||ev.type==='session.start'||ev.type==='custom')){
|
|
526
|
-
var f=getFlow(sid,ev);f.n++;
|
|
527
|
-
if(ev.tokenCount)f.tok+=ev.tokenCount;
|
|
528
|
-
if(ev.costUsd)f.cost+=ev.costUsd;
|
|
529
|
-
if(ev.agentId&&!f.agentId)f.agentId=ev.agentId;
|
|
530
|
-
if(ev.type==='tool.call'||ev.type==='tool.error'){f.tools++;if(ev.meta&&ev.meta.toolName)f.toolNames[String(ev.meta.toolName)]=true}
|
|
531
|
-
if(ev.type==='llm.call')f.llms++;
|
|
532
|
-
if(ev.outcome==='error'){f.err=true;f.errCount++}
|
|
533
|
-
var depth=1;
|
|
534
|
-
if(ev.type==='tool.call'||ev.type==='tool.error'||ev.type==='llm.call'||ev.type==='llm.token_usage')depth=2;
|
|
535
|
-
f.body.appendChild(buildEvRow(ev,depth));
|
|
536
|
-
if(evList.firstChild!==f.el)evList.insertBefore(f.el,evList.firstChild);
|
|
537
|
-
if(ev.type==='agent.end'||ev.type==='session.end'){if(ev.durationMs)f.dur=ev.durationMs;updFlow(f,f.err?'error':'done')}
|
|
538
|
-
else if(ev.type==='agent.error'){f.err=true;f.errCount++;if(ev.durationMs)f.dur=ev.durationMs;updFlow(f,'error')}
|
|
539
|
-
else updFlow(f,f.st);
|
|
540
|
-
f.el.scrollIntoView({block:'nearest',behavior:'smooth'});
|
|
541
|
-
return;
|
|
542
|
-
}
|
|
543
|
-
var row=buildEvRow(ev,0);
|
|
544
|
-
if(evList.firstChild&&evList.firstChild!==emptyMsg)evList.insertBefore(row,evList.firstChild);
|
|
545
|
-
else evList.appendChild(row);
|
|
546
|
-
trimStandalone();
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
// ─── Ingest OpenClaw log entry (from log tailer) ──────────────────────
|
|
550
|
-
function addLogEntry(entry){
|
|
551
|
-
total++;evCnt.textContent=total;emptyMsg.style.display='none';
|
|
552
|
-
if(paused)return;
|
|
553
|
-
// Also stream to logs tab if active and auto-refresh is on
|
|
554
|
-
if($('tab-logs').classList.contains('active')&&$('lA').checked){
|
|
555
|
-
appendLogToTab(entry);
|
|
556
|
-
}
|
|
557
|
-
var sid=entry.sessionId;
|
|
558
|
-
if(sid&&flows[sid]){
|
|
559
|
-
var f=flows[sid];f.n++;
|
|
560
|
-
f.body.appendChild(buildLogRow(entry,1));
|
|
561
|
-
if(evList.firstChild!==f.el)evList.insertBefore(f.el,evList.firstChild);
|
|
562
|
-
updFlow(f,f.st);
|
|
563
|
-
f.el.scrollIntoView({block:'nearest',behavior:'smooth'});
|
|
564
|
-
return;
|
|
565
|
-
}
|
|
566
|
-
if(sid){
|
|
567
|
-
var f=getFlow(sid,entry);f.n++;
|
|
568
|
-
f.body.appendChild(buildLogRow(entry,1));
|
|
569
|
-
updFlow(f,f.st);
|
|
570
|
-
f.el.scrollIntoView({block:'nearest',behavior:'smooth'});
|
|
571
|
-
return;
|
|
572
|
-
}
|
|
573
|
-
var row=buildLogRow(entry,0);
|
|
574
|
-
if(evList.firstChild&&evList.firstChild!==emptyMsg)evList.insertBefore(row,evList.firstChild);
|
|
575
|
-
else evList.appendChild(row);
|
|
576
|
-
trimStandalone();
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
function trimStandalone(){
|
|
580
|
-
var all=evList.querySelectorAll('.row.standalone');
|
|
581
|
-
while(all.length>MAX_STANDALONE){all[all.length-1].remove();all=evList.querySelectorAll('.row.standalone')}
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
// ─── Stale flow cleanup ──────────────────────
|
|
585
|
-
setInterval(function(){
|
|
586
|
-
var now=Date.now();
|
|
587
|
-
for(var k in flows){
|
|
588
|
-
var f=flows[k];
|
|
589
|
-
if(f.st==='active'&&now-f.startTs>STALE_MS)updFlow(f,'done');
|
|
590
|
-
if(f.st!=='active'&&now-f.startTs>600000){f.el.remove();delete flows[k];var i=flowOrd.indexOf(k);if(i>=0)flowOrd.splice(i,1)}
|
|
591
|
-
}
|
|
592
|
-
while(flowOrd.length>MAX_FLOWS){var old=flowOrd.shift();if(flows[old]){flows[old].el.remove();delete flows[old]}}
|
|
593
|
-
},30000);
|
|
594
|
-
|
|
595
|
-
// ─── Alerts ──────────────────────
|
|
596
|
-
function addAlert(a){
|
|
597
|
-
alEmpty.style.display='none';
|
|
598
|
-
var d=document.createElement('div');d.className='al';
|
|
599
|
-
var sv=a.severity||'error';d.innerHTML='<div class="al-sev '+esc(sv)+'">['+esc(sv.toUpperCase())+'] '+esc(a.ruleId||'')+'</div><div class="al-title">'+esc(a.title||'')+'</div><div class="al-detail">'+esc(a.detail||'')+'</div><div class="al-time">'+fT(a.ts)+'</div>';
|
|
600
|
-
alList.insertBefore(d,alList.firstChild);
|
|
601
|
-
while(alList.children.length>MAX_ALERTS+1)alList.removeChild(alList.lastChild);
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
evList.addEventListener('mouseenter',function(){paused=true});
|
|
605
|
-
evList.addEventListener('mouseleave',function(){paused=false});
|
|
606
|
-
|
|
607
|
-
// ─── Tabs ──────────────────────
|
|
608
|
-
document.querySelectorAll('.tab').forEach(function(t){
|
|
609
|
-
t.addEventListener('click',function(){
|
|
610
|
-
document.querySelectorAll('.tab').forEach(function(x){x.classList.remove('active')});
|
|
611
|
-
document.querySelectorAll('.tab-content').forEach(function(x){x.classList.remove('active')});
|
|
612
|
-
t.classList.add('active');
|
|
613
|
-
var tgt=$('tab-'+t.dataset.tab);if(tgt)tgt.classList.add('active');
|
|
614
|
-
if(t.dataset.tab==='logs')refreshLogs();
|
|
615
|
-
if(t.dataset.tab==='health')refreshHealth();
|
|
616
|
-
if(t.dataset.tab==='debug')refreshDebug();
|
|
617
|
-
});
|
|
618
|
-
});
|
|
619
|
-
|
|
620
|
-
// ─── SSE (OpenAlerts events + OpenClaw log tailing) ──────────────────────
|
|
621
|
-
function connectSSE(){
|
|
622
|
-
if(evSrc)evSrc.close();
|
|
623
|
-
try{
|
|
624
|
-
evSrc=new EventSource('/openalerts/events');
|
|
625
|
-
evSrc.addEventListener('openalerts',function(e){try{addEvent(JSON.parse(e.data))}catch(_){}});
|
|
626
|
-
evSrc.addEventListener('history',function(e){try{var evs=JSON.parse(e.data);for(var i=0;i<evs.length;i++)addEvent(evs[i])}catch(_){}});
|
|
627
|
-
evSrc.addEventListener('oclog',function(e){try{addLogEntry(JSON.parse(e.data))}catch(_){}});
|
|
628
|
-
evSrc.onopen=function(){$('sDot').className='dot live';$('sConn').textContent='live'};
|
|
629
|
-
evSrc.onerror=function(e){$('sDot').className='dot dead';$('sConn').textContent='err:'+evSrc.readyState};
|
|
630
|
-
}catch(e){$('sConn').textContent='SSE fail:'+e.message}
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
// ─── State polling ──────────────────────
|
|
634
|
-
var prevAl={};
|
|
635
|
-
function pollState(){
|
|
636
|
-
fetch('/openalerts/state').then(function(r){if(!r.ok)throw new Error('HTTP '+r.status);return r.json()}).catch(function(e){$('sUp').textContent='fetch err: '+e.message;return null}).then(function(s){
|
|
637
|
-
if(!s)return;
|
|
638
|
-
if(s.stats){
|
|
639
|
-
$('sMsgs').textContent=s.stats.messagesProcessed||0;
|
|
640
|
-
$('sErr').textContent=(s.stats.messageErrors||0)+(s.stats.webhookErrors||0)+(s.stats.toolErrors||0);
|
|
641
|
-
$('sTools').textContent=s.stats.toolCalls||0;
|
|
642
|
-
$('sAgents').textContent=s.stats.agentStarts||0;
|
|
643
|
-
}
|
|
644
|
-
if(s.uptimeMs!=null)$('sUp').textContent=fU(s.uptimeMs);
|
|
645
|
-
if(s.recentAlerts){
|
|
646
|
-
var nids={};
|
|
647
|
-
for(var i=0;i<s.recentAlerts.length;i++){var a=s.recentAlerts[i];nids[a.id]=true;if(!prevAl[a.id])addAlert(a)}
|
|
648
|
-
prevAl=nids;
|
|
649
|
-
}
|
|
650
|
-
if(s.rules){
|
|
651
|
-
var sec=$('rulesEl'),rh='<h3>Rules</h3>';
|
|
652
|
-
for(var j=0;j<s.rules.length;j++){var r=s.rules[j];var rf=r.status==='fired';rh+='<div class="rl"><span class="rl-d '+(rf?'fired':'ok')+'"></span><span>'+esc(r.id)+'</span><span class="rl-s">'+(rf?'FIRING':'OK')+'</span></div>'}
|
|
653
|
-
sec.innerHTML=rh;
|
|
654
|
-
}
|
|
655
|
-
window._ss=s;
|
|
656
|
-
}).catch(function(){});
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
// ─── Logs tab ──────────────────────
|
|
660
|
-
var logSubsPopulated=false;
|
|
661
|
-
|
|
662
|
-
/** Build an expandable log row for the Logs tab */
|
|
663
|
-
function buildLogTabRow(e){
|
|
664
|
-
var container=document.createElement('div');
|
|
665
|
-
var row=document.createElement('div');row.className='log-e';
|
|
666
|
-
var rawLine=fISO(e.tsMs||e.ts)+' ['+e.level+'] '+e.subsystem+': '+e.message;
|
|
667
|
-
row.innerHTML='<span class="log-ts">'+fT(e.tsMs||e.ts)+'</span><span class="log-lv '+esc(e.level)+'">'+esc(e.level)+'</span><span class="log-su">'+esc(e.subsystem)+'</span><span class="log-mg">'+esc(e.message)+'</span><button class="log-copy" data-raw="'+esc(rawLine)+'" title="Copy log line">copy</button>';
|
|
668
|
-
container.appendChild(row);
|
|
669
|
-
|
|
670
|
-
// Build expandable detail
|
|
671
|
-
var detail=document.createElement('div');detail.className='log-detail';
|
|
672
|
-
var dh='<div class="ld-grid">';
|
|
673
|
-
dh+='<span class="lk">Time</span><span class="lv">'+fISO(e.tsMs||e.ts)+'</span>';
|
|
674
|
-
dh+='<span class="lk">Level</span><span class="lv">'+esc(e.level)+'</span>';
|
|
675
|
-
dh+='<span class="lk">Subsystem</span><span class="lv">'+esc(e.subsystem)+'</span>';
|
|
676
|
-
// ALL key-value pairs from extra
|
|
677
|
-
var kvs=e.extra||{};
|
|
678
|
-
var kvKeys=Object.keys(kvs);
|
|
679
|
-
for(var i=0;i<kvKeys.length;i++){
|
|
680
|
-
dh+='<span class="lk">'+esc(kvKeys[i])+'</span><span class="lv">'+esc(kvs[kvKeys[i]])+'</span>';
|
|
681
|
-
}
|
|
682
|
-
dh+='</div>';
|
|
683
|
-
// File path + method
|
|
684
|
-
if(e.filePath||e.method){
|
|
685
|
-
dh+='<div class="ld-file">';
|
|
686
|
-
if(e.filePath)dh+='\\u{1F4C4} '+esc(e.filePath);
|
|
687
|
-
if(e.method)dh+=' ('+esc(e.method)+')';
|
|
688
|
-
if(e.hostname)dh+=' @ '+esc(e.hostname);
|
|
689
|
-
dh+='</div>';
|
|
690
|
-
}
|
|
691
|
-
detail.innerHTML=dh;
|
|
692
|
-
container.appendChild(detail);
|
|
693
|
-
|
|
694
|
-
row.addEventListener('click',function(){detail.classList.toggle('open')});
|
|
695
|
-
return container;
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
/** Check if log level is enabled */
|
|
699
|
-
function isLevelEnabled(level){
|
|
700
|
-
var cb=$('lv-'+level);
|
|
701
|
-
return cb?cb.checked:true;
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
/** Append a single SSE-streamed log entry to the Logs tab */
|
|
705
|
-
function appendLogToTab(entry){
|
|
706
|
-
var list=$('logList');
|
|
707
|
-
var fSub=$('lF').value, fSrch=$('lS').value.toLowerCase();
|
|
708
|
-
if(fSub&&entry.subsystem.indexOf(fSub)<0)return;
|
|
709
|
-
if(!isLevelEnabled(entry.level))return;
|
|
710
|
-
if(fSrch&&entry.message.toLowerCase().indexOf(fSrch)<0&&entry.subsystem.toLowerCase().indexOf(fSrch)<0)return;
|
|
711
|
-
// Remove empty msg if present
|
|
712
|
-
var em=list.querySelector('.empty-msg');if(em)em.remove();
|
|
713
|
-
var row=buildLogTabRow(entry);
|
|
714
|
-
list.appendChild(row);
|
|
715
|
-
if($('lA').checked)list.scrollTop=list.scrollHeight;
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
function hasActiveFilter(){
|
|
719
|
-
var fSub=$('lF').value;
|
|
720
|
-
var fSrch=$('lS').value;
|
|
721
|
-
var allLevels=['TRACE','DEBUG','INFO','WARN','ERROR','FATAL'];
|
|
722
|
-
var anyUnchecked=false;
|
|
723
|
-
for(var li=0;li<allLevels.length;li++){var cb=$('lv-'+allLevels[li]);if(cb&&!cb.checked){anyUnchecked=true;break}}
|
|
724
|
-
return !!(fSub||fSrch||anyUnchecked);
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
function refreshLogs(){
|
|
728
|
-
var fetchAll=$('lAll').checked||hasActiveFilter();
|
|
729
|
-
var limit=fetchAll?0:500;
|
|
730
|
-
fetch('/openalerts/logs?limit='+limit).then(function(r){return r.json()}).then(function(data){
|
|
731
|
-
var list=$('logList');
|
|
732
|
-
var entries=data.entries||[];
|
|
733
|
-
var fSub=$('lF').value, fSrch=$('lS').value.toLowerCase();
|
|
734
|
-
var truncated=data.truncated||false;
|
|
735
|
-
$('lT').classList.toggle('show',truncated);
|
|
736
|
-
|
|
737
|
-
// Populate subsystem dropdown dynamically
|
|
738
|
-
if(data.subsystems&&data.subsystems.length){
|
|
739
|
-
var sel=$('lF');
|
|
740
|
-
var cur=sel.value;
|
|
741
|
-
var existing={};
|
|
742
|
-
for(var oi=0;oi<sel.options.length;oi++)existing[sel.options[oi].value]=true;
|
|
743
|
-
var changed=false;
|
|
744
|
-
for(var si=0;si<data.subsystems.length;si++){
|
|
745
|
-
if(!existing[data.subsystems[si]]){
|
|
746
|
-
var opt=document.createElement('option');opt.value=data.subsystems[si];opt.textContent=data.subsystems[si];
|
|
747
|
-
sel.appendChild(opt);changed=true;
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
if(changed)sel.value=cur;
|
|
751
|
-
logSubsPopulated=true;
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
list.innerHTML='';
|
|
755
|
-
var shown=0,total=entries.length;
|
|
756
|
-
for(var i=0;i<entries.length;i++){
|
|
757
|
-
var e=entries[i];
|
|
758
|
-
if(fSub&&e.subsystem.indexOf(fSub)<0)continue;
|
|
759
|
-
if(!isLevelEnabled(e.level))continue;
|
|
760
|
-
if(fSrch&&e.message.toLowerCase().indexOf(fSrch)<0&&e.subsystem.toLowerCase().indexOf(fSrch)<0)continue;
|
|
761
|
-
list.appendChild(buildLogTabRow(e));
|
|
762
|
-
shown++;
|
|
763
|
-
}
|
|
764
|
-
if(!shown){list.innerHTML='<div class="empty-msg">No logs match your filters. '+(truncated?'Try checking "All logs" to search the full log file.':'')+'</div>';return}
|
|
765
|
-
if($('lA').checked)list.scrollTop=list.scrollHeight;
|
|
766
|
-
}).catch(function(){$('logList').innerHTML='<div class="empty-msg">Failed to load.</div>'});
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
// Export filtered logs
|
|
770
|
-
function exportLogs(){
|
|
771
|
-
var list=$('logList');
|
|
772
|
-
var rows=list.querySelectorAll('.log-e');
|
|
773
|
-
if(!rows.length){alert('No logs to export');return}
|
|
774
|
-
var lines=[];
|
|
775
|
-
rows.forEach(function(row){
|
|
776
|
-
var btn=row.querySelector('.log-copy');
|
|
777
|
-
if(btn)lines.push(btn.getAttribute('data-raw'));
|
|
778
|
-
});
|
|
779
|
-
var blob=new Blob([lines.join('\\n')],{type:'text/plain'});
|
|
780
|
-
var url=URL.createObjectURL(blob);
|
|
781
|
-
var a=document.createElement('a');
|
|
782
|
-
a.href=url;a.download='openalerts-logs-'+Date.now()+'.txt';
|
|
783
|
-
a.click();
|
|
784
|
-
URL.revokeObjectURL(url);
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
// Wire up event listeners
|
|
788
|
-
$('lR').addEventListener('click',refreshLogs);
|
|
789
|
-
$('lE').addEventListener('click',exportLogs);
|
|
790
|
-
$('lF').addEventListener('change',refreshLogs);
|
|
791
|
-
$('lAll').addEventListener('change',refreshLogs);
|
|
792
|
-
var sDb;$('lS').addEventListener('input',function(){clearTimeout(sDb);sDb=setTimeout(refreshLogs,300)});
|
|
793
|
-
|
|
794
|
-
// Level filter checkboxes
|
|
795
|
-
['TRACE','DEBUG','INFO','WARN','ERROR','FATAL'].forEach(function(lv){
|
|
796
|
-
var cb=$('lv-'+lv);
|
|
797
|
-
if(cb)cb.addEventListener('change',refreshLogs);
|
|
798
|
-
});
|
|
799
|
-
|
|
800
|
-
// Keyboard shortcuts
|
|
801
|
-
document.addEventListener('keydown',function(e){
|
|
802
|
-
if(e.ctrlKey&&e.key==='f'&&$('tab-logs').classList.contains('active')){
|
|
803
|
-
e.preventDefault();$('lS').focus();
|
|
804
|
-
}
|
|
805
|
-
if(e.ctrlKey&&e.key==='r'&&$('tab-logs').classList.contains('active')){
|
|
806
|
-
e.preventDefault();refreshLogs();
|
|
807
|
-
}
|
|
808
|
-
});
|
|
809
|
-
|
|
810
|
-
// Copy button handler (event delegation)
|
|
811
|
-
document.addEventListener('click',function(e){
|
|
812
|
-
if(e.target.classList.contains('log-copy')){
|
|
813
|
-
e.stopPropagation();
|
|
814
|
-
var raw=e.target.getAttribute('data-raw');
|
|
815
|
-
if(raw)cpToClip(raw,e.target);
|
|
816
|
-
}
|
|
817
|
-
});
|
|
818
|
-
|
|
819
|
-
// Fallback polling every 3s (SSE handles real-time now)
|
|
820
|
-
setInterval(function(){if($('tab-logs').classList.contains('active')&&$('lA').checked&&!evSrc)refreshLogs()},3000);
|
|
821
|
-
|
|
822
|
-
// ─── Health tab ──────────────────────
|
|
823
|
-
function refreshHealth(){
|
|
824
|
-
var s=window._ss;if(!s){pollState();setTimeout(refreshHealth,1000);return}
|
|
825
|
-
var hEl=$('hC'),st=s.stats||{},up=s.uptimeMs||0;
|
|
826
|
-
var html='';
|
|
827
|
-
html+='<div class="h-sec"><h3>System</h3><div class="h-grid">';
|
|
828
|
-
html+=hCard('Uptime',fU(up),'ok');
|
|
829
|
-
html+=hCard('Started At',s.startedAt?new Date(s.startedAt).toLocaleString():'--','');
|
|
830
|
-
html+=hCard('SSE Listeners',s.busListeners||0,'ok');
|
|
831
|
-
var te=(st.messageErrors||0)+(st.webhookErrors||0)+(st.toolErrors||0)+(st.agentErrors||0);
|
|
832
|
-
html+=hCard('Total Errors',te,te>0?'bad':'ok');
|
|
833
|
-
html+=hCard('Platform',s.platformConnected?'Connected':'Off',s.platformConnected?'ok':'');
|
|
834
|
-
// New cards
|
|
835
|
-
var stuckN=s.stuckSessions!=null?s.stuckSessions:(st.stuckSessions||0);
|
|
836
|
-
html+=hCard('Stuck Sessions',stuckN,stuckN>0?'bad':'ok');
|
|
837
|
-
var hbAgo=s.lastHeartbeatTs?fAgo(s.lastHeartbeatTs):'never';
|
|
838
|
-
var hbOk=s.lastHeartbeatTs&&(Date.now()-s.lastHeartbeatTs)<90000;
|
|
839
|
-
html+=hCard('Last Heartbeat',hbAgo,hbOk?'ok':'bad');
|
|
840
|
-
if(s.hourlyAlerts){
|
|
841
|
-
html+=hCard('Hourly Alert Cap',s.hourlyAlerts.count+'/5'+(s.hourlyAlerts.resetAt?' (reset '+fAgo(s.hourlyAlerts.resetAt)+')':''),'');
|
|
842
|
-
}
|
|
843
|
-
if(s.lastResetTs){
|
|
844
|
-
html+=hCard('Stats Reset',new Date(s.lastResetTs).toLocaleString(),'');
|
|
845
|
-
}
|
|
846
|
-
html+='</div></div>';
|
|
847
|
-
|
|
848
|
-
html+='<div class="h-sec"><h3>Stats</h3><table class="h-tbl">';
|
|
849
|
-
html+=hTr('Messages Processed',st.messagesProcessed||0)+hTr('Message Errors',st.messageErrors||0)+hTr('Webhook Errors',st.webhookErrors||0);
|
|
850
|
-
html+=hTr('Tool Calls',st.toolCalls||0)+hTr('Tool Errors',st.toolErrors||0)+hTr('Agent Starts',st.agentStarts||0)+hTr('Agent Errors',st.agentErrors||0)+hTr('Sessions',st.sessionsStarted||0);
|
|
851
|
-
html+=hTr('Stuck Sessions',stuckN);
|
|
852
|
-
html+='</table></div>';
|
|
853
|
-
|
|
854
|
-
html+='<div class="h-sec"><h3>Rules</h3><table class="h-tbl"><tr><th>Rule</th><th>Status</th><th>Last Fired</th></tr>';
|
|
855
|
-
var cds=s.cooldowns||{};
|
|
856
|
-
if(s.rules)for(var i=0;i<s.rules.length;i++){
|
|
857
|
-
var r=s.rules[i];
|
|
858
|
-
var cdTs=findLastFired(cds,r.id);
|
|
859
|
-
var lastFired=cdTs?fAgo(cdTs):'--';
|
|
860
|
-
html+='<tr><td>'+esc(r.id)+'</td><td>'+(r.status==='fired'?'<span style="color:#f85149;font-weight:700">FIRING</span>':'<span style="color:#3fb950">OK</span>')+'</td><td>'+lastFired+'</td></tr>';
|
|
861
|
-
}
|
|
862
|
-
html+='</table></div>';
|
|
863
|
-
|
|
864
|
-
if(s.recentAlerts&&s.recentAlerts.length){
|
|
865
|
-
html+='<div class="h-sec"><h3>Recent Alerts ('+s.recentAlerts.length+')</h3><table class="h-tbl">';
|
|
866
|
-
for(var j=0;j<s.recentAlerts.length;j++){var a=s.recentAlerts[j];html+='<tr><td style="color:'+(a.severity==='critical'?'#ff7b72':a.severity==='warn'?'#d29922':'#f85149')+'">['+((a.severity||'?').toUpperCase())+'] '+esc(a.ruleId||'')+'</td><td>'+esc(a.title||'')+' \\u2014 '+esc(a.detail||'')+' ('+fT(a.ts)+')</td></tr>'}
|
|
867
|
-
html+='</table></div>';
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
hEl.innerHTML=html;
|
|
871
|
-
}
|
|
872
|
-
function hCard(l,v,c){return'<div class="h-card"><div class="lb">'+esc(l)+'</div><div class="vl '+(c||'')+'">'+esc(String(v))+'</div></div>'}
|
|
873
|
-
function hTr(l,v){return'<tr><td>'+esc(l)+'</td><td><b>'+esc(String(v))+'</b></td></tr>'}
|
|
874
|
-
/** Find the most recent cooldown timestamp for a rule ID (handles contextual fingerprints like "llm-errors:telegram"). */
|
|
875
|
-
function findLastFired(cds,ruleId){var latest=0;for(var k in cds){if(k===ruleId||k.indexOf(ruleId+':')===0){if(cds[k]>latest)latest=cds[k]}}return latest||null}
|
|
876
|
-
|
|
877
|
-
// ─── Debug tab ──────────────────────
|
|
878
|
-
function refreshDebug(){
|
|
879
|
-
var s=window._ss;if(!s){pollState();setTimeout(refreshDebug,1000);return}
|
|
880
|
-
|
|
881
|
-
// State snapshot
|
|
882
|
-
var stHtml='';
|
|
883
|
-
stHtml+=hCard('Uptime',fU(s.uptimeMs||0),'ok');
|
|
884
|
-
stHtml+=hCard('Started At',s.startedAt?new Date(s.startedAt).toLocaleString():'--','');
|
|
885
|
-
stHtml+=hCard('SSE Listeners',s.busListeners||0,'ok');
|
|
886
|
-
stHtml+=hCard('Platform',s.platformConnected?'Connected':'Off',s.platformConnected?'ok':'');
|
|
887
|
-
stHtml+=hCard('Event Count',total,'ok');
|
|
888
|
-
$('dbState').innerHTML=stHtml;
|
|
889
|
-
|
|
890
|
-
// Recent events (last 20 from flows)
|
|
891
|
-
var evHtml='<div style="font-family:monospace;font-size:10px;line-height:1.6">';
|
|
892
|
-
var recentEvs=[];
|
|
893
|
-
for(var k in flows){
|
|
894
|
-
var f=flows[k];
|
|
895
|
-
if(f.body&&f.body.children){
|
|
896
|
-
for(var i=0;i<f.body.children.length;i++){
|
|
897
|
-
var child=f.body.children[i];
|
|
898
|
-
if(child.querySelector&&child.querySelector('.r-time')){
|
|
899
|
-
var timeEl=child.querySelector('.r-time');
|
|
900
|
-
var typeEl=child.querySelector('.r-type');
|
|
901
|
-
if(timeEl&&typeEl){
|
|
902
|
-
recentEvs.push({time:timeEl.textContent,type:typeEl.textContent,sid:k});
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
recentEvs=recentEvs.slice(-20);
|
|
909
|
-
if(recentEvs.length===0)evHtml+='<div style="color:#8b949e">No recent events</div>';
|
|
910
|
-
else for(var j=0;j<recentEvs.length;j++){
|
|
911
|
-
var ev=recentEvs[j];
|
|
912
|
-
evHtml+='<div><span style="color:#484f58">'+esc(ev.time)+'</span> <span style="color:#58a6ff">'+esc(ev.type)+'</span> <span style="color:#8b949e">'+esc(ev.sid.slice(0,12))+'</span></div>';
|
|
913
|
-
}
|
|
914
|
-
evHtml+='</div>';
|
|
915
|
-
$('dbEvents').innerHTML=evHtml;
|
|
916
|
-
|
|
917
|
-
// Rules status
|
|
918
|
-
var rulesHtml='<table class="h-tbl"><tr><th>Rule</th><th>Status</th><th>Last Fired</th></tr>';
|
|
919
|
-
var cds=s.cooldowns||{};
|
|
920
|
-
if(s.rules)for(var i=0;i<s.rules.length;i++){
|
|
921
|
-
var r=s.rules[i];
|
|
922
|
-
var cdTs=findLastFired(cds,r.id);
|
|
923
|
-
var lastFired=cdTs?fAgo(cdTs):'never';
|
|
924
|
-
rulesHtml+='<tr><td>'+esc(r.id)+'</td><td>'+(r.status==='fired'?'<span style="color:#f85149;font-weight:700">FIRING</span>':'<span style="color:#3fb950">OK</span>')+'</td><td>'+lastFired+'</td></tr>';
|
|
925
|
-
}
|
|
926
|
-
rulesHtml+='</table>';
|
|
927
|
-
$('dbRules').innerHTML=rulesHtml;
|
|
928
|
-
|
|
929
|
-
}
|
|
930
|
-
$('dbRefresh').addEventListener('click',refreshDebug);
|
|
931
|
-
|
|
932
|
-
// ─── Boot ──────────────────────
|
|
933
|
-
connectSSE();pollState();setInterval(pollState,4000);
|
|
934
|
-
})();
|
|
935
|
-
</script>
|
|
936
|
-
</body>
|
|
937
|
-
</html>`;
|
|
938
|
-
}
|