@cef-ai/cli 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/commands/dev/server.js +34 -6
- package/dist/commands/dev/{ui.js → static/app.js} +50 -401
- package/dist/commands/dev/static/index.html +205 -0
- package/dist/commands/dev/static/static/app.js +633 -0
- package/dist/commands/dev/static/static/index.html +205 -0
- package/dist/commands/dev/static/static/style.css +184 -0
- package/dist/commands/dev/static/style.css +184 -0
- package/dist/lib/config.js +14 -1
- package/dist/lib/runtime/agent-router.js +2 -13
- package/dist/lib/runtime/index.js +2 -4
- package/dist/lib/transpile.d.ts +1 -0
- package/dist/lib/transpile.js +9 -0
- package/example/.env.example +2 -2
- package/package.json +6 -5
- package/dist/commands/dev/ui.d.ts +0 -1
|
@@ -1,353 +1,3 @@
|
|
|
1
|
-
export function getDevUiHtml() {
|
|
2
|
-
return `<!DOCTYPE html>
|
|
3
|
-
<html lang="en">
|
|
4
|
-
<head>
|
|
5
|
-
<meta charset="utf-8">
|
|
6
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
7
|
-
<title>CEF Dev</title>
|
|
8
|
-
<style>
|
|
9
|
-
*{margin:0;padding:0;box-sizing:border-box}
|
|
10
|
-
:root{
|
|
11
|
-
--bg:#09090b;--surface:#111113;--surface2:#18181b;--surface3:#1e1e22;
|
|
12
|
-
--border:#27272a;--border2:#3f3f46;
|
|
13
|
-
--text:#fafafa;--text2:#a1a1aa;--text3:#71717a;
|
|
14
|
-
--accent:#3b82f6;--accent2:#2563eb;
|
|
15
|
-
--success:#22c55e;--warn:#f59e0b;--error:#ef4444;
|
|
16
|
-
--mono:'SF Mono','Fira Code','JetBrains Mono','Consolas',monospace;
|
|
17
|
-
--sans:system-ui,-apple-system,'Segoe UI',sans-serif;
|
|
18
|
-
--radius:8px;
|
|
19
|
-
}
|
|
20
|
-
html,body{height:100%;background:var(--bg);color:var(--text);font:13px/1.5 var(--sans);overflow:hidden}
|
|
21
|
-
::-webkit-scrollbar{width:6px;height:6px}
|
|
22
|
-
::-webkit-scrollbar-track{background:transparent}
|
|
23
|
-
::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}
|
|
24
|
-
::-webkit-scrollbar-thumb:hover{background:var(--border2)}
|
|
25
|
-
::selection{background:var(--accent);color:#fff}
|
|
26
|
-
|
|
27
|
-
button{font:12px/1 var(--sans);cursor:pointer;border:none;border-radius:6px;padding:7px 16px;font-weight:500;transition:all .12s ease}
|
|
28
|
-
button:hover{filter:brightness(1.1)}
|
|
29
|
-
button:active{transform:scale(.98)}
|
|
30
|
-
textarea,input[type=text]{font:12px/1.5 var(--mono);background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);padding:10px 12px;resize:vertical;width:100%;outline:none;transition:border-color .15s,box-shadow .15s}
|
|
31
|
-
textarea:focus,input[type=text]:focus{border-color:var(--accent);box-shadow:0 0 0 2px rgba(59,130,246,.15)}
|
|
32
|
-
pre{font:12px/1.5 var(--mono);white-space:pre-wrap;word-break:break-all}
|
|
33
|
-
table{width:100%;border-collapse:collapse;font:12px/1.4 var(--mono)}
|
|
34
|
-
th,td{text-align:left;padding:6px 10px;border-bottom:1px solid var(--border)}
|
|
35
|
-
th{color:var(--text3);font-weight:500;font-size:11px;text-transform:uppercase;letter-spacing:.3px;position:sticky;top:0;background:var(--surface2)}
|
|
36
|
-
td{color:var(--text2)}
|
|
37
|
-
tr:hover td{background:rgba(255,255,255,.02)}
|
|
38
|
-
|
|
39
|
-
/* ─── Layout ─── */
|
|
40
|
-
#app{display:grid;grid-template-columns:200px 1fr;grid-template-rows:48px 1fr auto;height:100vh}
|
|
41
|
-
|
|
42
|
-
/* Header */
|
|
43
|
-
#header{grid-column:1/-1;display:flex;align-items:center;padding:0 20px;background:var(--surface);border-bottom:1px solid var(--border);gap:10px}
|
|
44
|
-
#header h1{font-size:13px;font-weight:700;letter-spacing:1.2px;text-transform:uppercase;color:var(--text2)}
|
|
45
|
-
#header h1 span{color:var(--accent)}
|
|
46
|
-
.pill{font:10px/1 var(--mono);color:var(--text3);background:var(--surface2);padding:3px 10px;border-radius:12px;border:1px solid var(--border)}
|
|
47
|
-
|
|
48
|
-
/* Sidebar */
|
|
49
|
-
#sidebar{background:var(--surface);border-right:1px solid var(--border);overflow-y:auto;padding:4px 0}
|
|
50
|
-
.s-label{font:10px/1 var(--sans);color:var(--text3);text-transform:uppercase;letter-spacing:.8px;padding:16px 14px 6px;font-weight:600}
|
|
51
|
-
.s-item{display:flex;align-items:center;padding:6px 14px;cursor:pointer;font-size:12px;color:var(--text2);gap:8px;border-left:2px solid transparent;transition:all .1s}
|
|
52
|
-
.s-item:hover{background:var(--surface2);color:var(--text)}
|
|
53
|
-
.s-item.active{background:var(--surface2);color:var(--text);border-left-color:var(--accent)}
|
|
54
|
-
.s-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0}
|
|
55
|
-
.s-dot-eng{background:var(--accent)}
|
|
56
|
-
.s-dot-agent{background:var(--success)}
|
|
57
|
-
.s-dot-cubby{background:var(--warn)}
|
|
58
|
-
.s-task{color:var(--text3);font-weight:400}
|
|
59
|
-
|
|
60
|
-
/* Main */
|
|
61
|
-
#main{overflow-y:auto;padding:24px 28px;background:var(--bg)}
|
|
62
|
-
|
|
63
|
-
/* Panels */
|
|
64
|
-
.panel{display:none;max-width:600px;animation:fadeIn .15s ease}.panel.active{display:block}.panel.active.panel-wide{max-width:none}
|
|
65
|
-
@keyframes fadeIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
|
|
66
|
-
.panel-title{font-size:16px;font-weight:600;margin-bottom:20px;display:flex;align-items:center;gap:10px}
|
|
67
|
-
.field{margin-bottom:14px}
|
|
68
|
-
.field-label{display:block;font-size:10px;color:var(--text3);margin-bottom:6px;text-transform:uppercase;letter-spacing:.5px;font-weight:600}
|
|
69
|
-
.btn-primary{background:var(--accent);color:#fff}
|
|
70
|
-
.btn-primary:hover{background:var(--accent2)}
|
|
71
|
-
.btn-secondary{background:var(--surface2);color:var(--text2);border:1px solid var(--border)}
|
|
72
|
-
.btn-secondary:hover{background:var(--surface3,#3f3f46);color:var(--text1)}
|
|
73
|
-
.btn-danger{background:#991b1b;color:#fca5a5}
|
|
74
|
-
.btn-danger:hover{background:#b91c1c}
|
|
75
|
-
.btn-ghost{background:transparent;color:var(--text2);border:1px solid var(--border);padding:6px 12px}
|
|
76
|
-
.btn-ghost:hover{background:var(--surface2);color:var(--text)}
|
|
77
|
-
.btn-row{display:flex;gap:8px;margin:14px 0}
|
|
78
|
-
.result{margin-top:14px;padding:14px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);max-height:280px;overflow:auto}
|
|
79
|
-
.result-meta{font-size:11px;color:var(--text3);margin-top:6px;display:flex;gap:12px}
|
|
80
|
-
.badge{display:inline-block;font:10px/1 var(--mono);padding:2px 6px;border-radius:4px}
|
|
81
|
-
.badge-ok{background:#052e16;color:var(--success);border:1px solid #14532d}
|
|
82
|
-
.badge-err{background:#1c0a0a;color:var(--error);border:1px solid #450a0a}
|
|
83
|
-
.error-banner{background:#1c0a0a;border:1px solid #450a0a;color:#fca5a5;padding:10px 14px;border-radius:var(--radius);margin-top:10px;font-size:12px}
|
|
84
|
-
.success-banner{background:#052e16;border:1px solid #14532d;color:#86efac;padding:10px 14px;border-radius:var(--radius);margin-top:10px;font-size:12px}
|
|
85
|
-
.table-wrap{max-height:calc(100vh - 280px);overflow:auto;border:1px solid var(--border);border-radius:var(--radius);background:var(--surface)}
|
|
86
|
-
#cubby-sql-result .table-wrap{max-height:calc(100vh - 600px)}
|
|
87
|
-
|
|
88
|
-
/* Schema cards */
|
|
89
|
-
.schema-pair{display:flex;gap:12px;margin-bottom:16px}
|
|
90
|
-
.schema-card{flex:1;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px}
|
|
91
|
-
.schema-card .st-title{font:10px/1 var(--sans);text-transform:uppercase;letter-spacing:.6px;color:var(--text3);margin-bottom:8px;font-weight:600}
|
|
92
|
-
.schema-row{font:11px/1.8 var(--mono);display:flex;align-items:baseline;gap:6px;flex-wrap:wrap}
|
|
93
|
-
.schema-row .sk{color:var(--text)}
|
|
94
|
-
.schema-row .st{color:var(--accent);opacity:.7}
|
|
95
|
-
.schema-row .sr{font:9px/1 var(--sans);color:var(--error);background:#1c0a0a;padding:1px 4px;border-radius:3px}
|
|
96
|
-
.schema-row .sd{color:var(--text3);font-size:10px;font-style:italic}
|
|
97
|
-
|
|
98
|
-
/* Cubby tabs */
|
|
99
|
-
.tab-bar{display:flex;gap:0;margin-bottom:16px;border-bottom:1px solid var(--border)}
|
|
100
|
-
.tab{padding:7px 16px;font-size:12px;color:var(--text3);cursor:pointer;border-bottom:2px solid transparent;font-weight:500;transition:color .1s}
|
|
101
|
-
.tab:hover{color:var(--text2)}
|
|
102
|
-
.tab.active{color:var(--text);border-bottom-color:var(--accent)}
|
|
103
|
-
.cubby-table-item{display:flex;align-items:center;padding:10px 14px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);margin-bottom:8px;cursor:pointer;transition:border-color .1s}
|
|
104
|
-
.cubby-table-item:hover{border-color:var(--border2)}
|
|
105
|
-
.cubby-table-item .cti-name{font:13px/1 var(--mono);font-weight:500}
|
|
106
|
-
.cubby-table-item .cti-count{font-size:11px;color:var(--text3);margin-left:auto}
|
|
107
|
-
|
|
108
|
-
/* Stream push */
|
|
109
|
-
.stream-area{margin-top:20px;padding-top:20px;border-top:1px solid var(--border)}
|
|
110
|
-
.stream-area .tab-bar{margin-bottom:14px}
|
|
111
|
-
#file-drop-zone.drag-over{border-color:var(--accent);background:rgba(59,130,246,.05)}
|
|
112
|
-
.progress-bar{height:3px;background:var(--surface2);border-radius:2px;margin:8px 0;overflow:hidden}
|
|
113
|
-
.progress-bar .fill{height:100%;background:var(--accent);transition:width .1s;border-radius:2px}
|
|
114
|
-
.progress-text{font:11px var(--mono);color:var(--text3)}
|
|
115
|
-
|
|
116
|
-
/* ─── Bottom panel (resizable) ─── */
|
|
117
|
-
#bottom{grid-column:1/-1;display:flex;flex-direction:column;background:var(--surface);min-height:40px;max-height:60vh}
|
|
118
|
-
#bottom.collapsed{min-height:40px;max-height:40px}
|
|
119
|
-
#bottom.collapsed #log-container,#bottom.collapsed #flow-container{display:none!important}
|
|
120
|
-
|
|
121
|
-
#resize-handle{height:3px;background:transparent;cursor:ns-resize;flex-shrink:0;position:relative}
|
|
122
|
-
#resize-handle::after{content:'';position:absolute;left:50%;top:0;transform:translateX(-50%);width:40px;height:3px;background:var(--border);border-radius:2px;transition:background .15s}
|
|
123
|
-
#resize-handle:hover::after{background:var(--accent)}
|
|
124
|
-
|
|
125
|
-
.bottom-bar{display:flex;align-items:center;padding:0 12px;height:36px;flex-shrink:0;border-top:1px solid var(--border);gap:2px}
|
|
126
|
-
.btab{padding:4px 12px;font-size:11px;color:var(--text3);cursor:pointer;border-radius:4px;font-weight:500;transition:all .1s;user-select:none}
|
|
127
|
-
.btab:hover{color:var(--text2);background:var(--surface2)}
|
|
128
|
-
.btab.active{color:var(--text);background:var(--surface2)}
|
|
129
|
-
.bottom-actions{margin-left:auto;display:flex;align-items:center;gap:6px}
|
|
130
|
-
#log-filter{width:140px;font-size:11px;padding:4px 8px;border-radius:5px;background:var(--surface2);border:1px solid var(--border)}
|
|
131
|
-
.icon-btn{background:none;border:none;color:var(--text3);cursor:pointer;padding:4px;border-radius:4px;font-size:14px;line-height:1;display:flex;align-items:center}
|
|
132
|
-
.icon-btn:hover{color:var(--text);background:var(--surface2)}
|
|
133
|
-
|
|
134
|
-
#log-container,#flow-container{flex:1;overflow-y:auto;font:11px/1.7 var(--mono);padding:6px 14px}
|
|
135
|
-
.log-line{padding:1px 0;border-bottom:1px solid rgba(255,255,255,.02)}
|
|
136
|
-
.log-line .ts{color:var(--text3);margin-right:6px}
|
|
137
|
-
.log-line .agent{color:var(--accent);margin-right:4px}
|
|
138
|
-
.log-line.warn{color:var(--warn)}
|
|
139
|
-
.log-line.warn .ts{color:var(--warn);opacity:.5}
|
|
140
|
-
.log-line.error{color:var(--error)}
|
|
141
|
-
.log-line.error .ts{color:var(--error);opacity:.5}
|
|
142
|
-
|
|
143
|
-
.flow-entry{padding:2px 0;font-size:11px;line-height:1.6;white-space:pre}
|
|
144
|
-
.fe-time{color:var(--text3)}
|
|
145
|
-
.fe-type{font-weight:500}
|
|
146
|
-
.fe-eng{color:var(--accent)}
|
|
147
|
-
.fe-agent{color:var(--success)}
|
|
148
|
-
.fe-cubby{color:var(--warn)}
|
|
149
|
-
.fe-err{color:var(--error)}
|
|
150
|
-
.fe-ok{color:var(--success)}
|
|
151
|
-
.fe-dim{color:var(--text3)}
|
|
152
|
-
|
|
153
|
-
/* Empty state */
|
|
154
|
-
.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--text3);gap:8px}
|
|
155
|
-
.empty-state .empty-icon{font-size:32px;opacity:.3}
|
|
156
|
-
.empty{color:var(--text3);font-size:12px;padding:24px 0;text-align:center}
|
|
157
|
-
|
|
158
|
-
/* Graph panel */
|
|
159
|
-
#cubby-graph{position:relative}
|
|
160
|
-
#graph-canvas{width:100%;height:calc(100vh - 200px);min-height:400px;border:1px solid var(--border);border-radius:var(--radius);background:var(--surface);overflow:hidden}
|
|
161
|
-
#graph-detail{position:absolute;top:8px;right:8px;width:260px;background:var(--surface2);border:1px solid var(--border2);border-radius:var(--radius);padding:12px;display:none;font-size:11px;max-height:400px;overflow-y:auto}
|
|
162
|
-
#graph-detail .gd-type{font:10px/1 var(--sans);text-transform:uppercase;letter-spacing:.5px;font-weight:600;margin-bottom:8px;padding:2px 6px;border-radius:3px;display:inline-block}
|
|
163
|
-
.gd-row{display:flex;gap:6px;margin-bottom:4px;line-height:1.4}
|
|
164
|
-
.gd-key{color:var(--text3);min-width:80px;flex-shrink:0}
|
|
165
|
-
.gd-val{color:var(--text2);word-break:break-all}
|
|
166
|
-
#graph-legend{position:absolute;bottom:8px;left:8px;background:var(--surface2);border:1px solid var(--border);border-radius:6px;padding:8px 12px;font-size:10px}
|
|
167
|
-
#graph-legend .gl-row{display:flex;align-items:center;gap:6px;color:var(--text3);margin-bottom:3px}
|
|
168
|
-
#graph-legend .gl-row:last-child{margin-bottom:0}
|
|
169
|
-
.gl-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
|
|
170
|
-
#graph-stats{font:11px var(--mono);color:var(--text3);margin-bottom:8px}
|
|
171
|
-
</style>
|
|
172
|
-
<script src="https://unpkg.com/vis-network@9.1.9/standalone/umd/vis-network.min.js"></script>
|
|
173
|
-
</head>
|
|
174
|
-
<body>
|
|
175
|
-
<div id="app">
|
|
176
|
-
<div id="header">
|
|
177
|
-
<h1><span>CEF</span> Dev</h1>
|
|
178
|
-
<span class="pill" id="status-pill">connecting</span>
|
|
179
|
-
</div>
|
|
180
|
-
|
|
181
|
-
<div id="sidebar"></div>
|
|
182
|
-
|
|
183
|
-
<div id="main">
|
|
184
|
-
<div class="empty-state" id="empty-state">
|
|
185
|
-
<div class="empty-icon">◇</div>
|
|
186
|
-
<div>Select an item from the sidebar</div>
|
|
187
|
-
</div>
|
|
188
|
-
|
|
189
|
-
<div class="panel" id="panel-engagement">
|
|
190
|
-
<div class="panel-title"><span class="s-dot s-dot-eng"></span> <span id="eng-name"></span> <span style="font-size:10px;font-weight:400;color:var(--text3);background:var(--surface2);border:1px solid var(--border);border-radius:4px;padding:2px 7px;margin-left:4px">engagement</span></div>
|
|
191
|
-
<div id="eng-desc" style="font-size:11px;color:var(--text3);margin-bottom:14px"></div>
|
|
192
|
-
<div id="eng-schema-hints"></div>
|
|
193
|
-
<div class="field">
|
|
194
|
-
<label class="field-label">Payload</label>
|
|
195
|
-
<textarea id="eng-payload" rows="4">{}</textarea>
|
|
196
|
-
</div>
|
|
197
|
-
<div style="display:flex;gap:8px;align-items:flex-end;margin-bottom:10px;flex-wrap:wrap">
|
|
198
|
-
<div style="display:flex;gap:6px;align-items:flex-end">
|
|
199
|
-
<div class="field" style="margin:0">
|
|
200
|
-
<label class="field-label">Repeat</label>
|
|
201
|
-
<input type="number" id="eng-repeat" value="1" min="1" max="999" style="width:70px;padding:6px 8px">
|
|
202
|
-
</div>
|
|
203
|
-
<div class="field" style="margin:0">
|
|
204
|
-
<label class="field-label">Delay ms</label>
|
|
205
|
-
<input type="number" id="eng-delay" value="300" min="0" style="width:80px;padding:6px 8px">
|
|
206
|
-
</div>
|
|
207
|
-
</div>
|
|
208
|
-
<div class="field" style="margin:0;flex:1;min-width:180px">
|
|
209
|
-
<label class="field-label">or blast from file (JSON array)</label>
|
|
210
|
-
<input type="file" id="eng-file-blast" accept=".json" style="width:100%;padding:5px 0;font-size:11px;color:var(--text2);background:none;border:none;cursor:pointer">
|
|
211
|
-
</div>
|
|
212
|
-
</div>
|
|
213
|
-
<div class="btn-row">
|
|
214
|
-
<button class="btn-primary" id="eng-start">Start</button>
|
|
215
|
-
<button class="btn-danger" id="eng-stop" style="display:none">Stop</button>
|
|
216
|
-
</div>
|
|
217
|
-
<div id="eng-result"></div>
|
|
218
|
-
<div class="stream-area" id="eng-stream-area" style="display:none">
|
|
219
|
-
<div class="panel-title" style="font-size:14px">Stream Data</div>
|
|
220
|
-
<div class="tab-bar">
|
|
221
|
-
<div class="tab active" data-stab="single">Single</div>
|
|
222
|
-
<div class="tab" data-stab="bulk">Bulk</div>
|
|
223
|
-
<div class="tab" data-stab="file">File</div>
|
|
224
|
-
</div>
|
|
225
|
-
<div id="stream-single">
|
|
226
|
-
<div class="field">
|
|
227
|
-
<label class="field-label">Packet</label>
|
|
228
|
-
<textarea id="eng-stream-payload" rows="3">{ }</textarea>
|
|
229
|
-
</div>
|
|
230
|
-
<button class="btn-primary" id="eng-push">Push</button>
|
|
231
|
-
</div>
|
|
232
|
-
<div id="stream-bulk" style="display:none">
|
|
233
|
-
<div class="field">
|
|
234
|
-
<label class="field-label">JSON Lines (one event per line) or JSON array</label>
|
|
235
|
-
<textarea id="eng-bulk-payload" rows="6">{ }
|
|
236
|
-
{ }</textarea>
|
|
237
|
-
</div>
|
|
238
|
-
<div style="display:flex;align-items:center;gap:10px;margin-bottom:10px">
|
|
239
|
-
<label class="field-label" style="margin:0;white-space:nowrap">Delay (ms)</label>
|
|
240
|
-
<input type="text" id="eng-bulk-delay" value="100" style="width:70px;padding:6px 8px">
|
|
241
|
-
</div>
|
|
242
|
-
<div class="btn-row" style="margin-top:0">
|
|
243
|
-
<button class="btn-primary" id="eng-bulk-send">Send All</button>
|
|
244
|
-
<button class="btn-danger" id="eng-bulk-stop" style="display:none">Stop</button>
|
|
245
|
-
</div>
|
|
246
|
-
<div id="eng-bulk-progress"></div>
|
|
247
|
-
</div>
|
|
248
|
-
<div id="stream-file" style="display:none">
|
|
249
|
-
<div id="file-drop-zone" style="border:2px dashed var(--border);border-radius:var(--radius);padding:28px;text-align:center;cursor:pointer;transition:border-color .15s;margin-bottom:12px">
|
|
250
|
-
<div style="color:var(--text3);font-size:12px">Drop a <strong>.json</strong> or <strong>.jsonl</strong> file here, or click to browse</div>
|
|
251
|
-
<input type="file" id="eng-file-input" accept=".json,.jsonl" style="display:none">
|
|
252
|
-
<div id="file-info" style="margin-top:8px;font:11px var(--mono);color:var(--text2)"></div>
|
|
253
|
-
</div>
|
|
254
|
-
<div style="display:flex;align-items:center;gap:10px;margin-bottom:10px">
|
|
255
|
-
<label class="field-label" style="margin:0;white-space:nowrap">Delay (ms)</label>
|
|
256
|
-
<input type="text" id="eng-file-delay" value="100" style="width:70px;padding:6px 8px">
|
|
257
|
-
</div>
|
|
258
|
-
<div class="btn-row" style="margin-top:0">
|
|
259
|
-
<button class="btn-primary" id="eng-file-send" disabled>Send File</button>
|
|
260
|
-
<button class="btn-danger" id="eng-file-stop" style="display:none">Stop</button>
|
|
261
|
-
</div>
|
|
262
|
-
<div id="eng-file-progress"></div>
|
|
263
|
-
</div>
|
|
264
|
-
<div id="eng-push-result"></div>
|
|
265
|
-
</div>
|
|
266
|
-
</div>
|
|
267
|
-
|
|
268
|
-
<div class="panel" id="panel-agent">
|
|
269
|
-
<div class="panel-title"><span class="s-dot s-dot-agent"></span> <span id="agent-label"></span> <span style="font-size:10px;font-weight:400;color:var(--text3);background:var(--surface2);border:1px solid var(--border);border-radius:4px;padding:2px 7px;margin-left:4px">agent task</span></div>
|
|
270
|
-
<div id="agent-desc" style="font-size:11px;color:var(--text3);margin-bottom:14px"></div>
|
|
271
|
-
<div id="agent-schema-hints"></div>
|
|
272
|
-
<div class="field">
|
|
273
|
-
<label class="field-label">Payload</label>
|
|
274
|
-
<textarea id="agent-payload" rows="5">{}</textarea>
|
|
275
|
-
</div>
|
|
276
|
-
<div class="btn-row">
|
|
277
|
-
<button class="btn-primary" id="agent-call">Call</button>
|
|
278
|
-
<button class="btn-secondary" id="agent-batch" style="display:none">Run Batch</button>
|
|
279
|
-
</div>
|
|
280
|
-
<div id="agent-result"></div>
|
|
281
|
-
</div>
|
|
282
|
-
|
|
283
|
-
<div class="panel" id="panel-cubby">
|
|
284
|
-
<div class="panel-title"><span class="s-dot s-dot-cubby"></span> <span id="cubby-label"></span></div>
|
|
285
|
-
<div class="tab-bar">
|
|
286
|
-
<div class="tab active" data-tab="browse">Browse</div>
|
|
287
|
-
<div class="tab" data-tab="sql">SQL</div>
|
|
288
|
-
<div class="tab" data-tab="graph" id="graph-tab" style="display:none">Graph</div>
|
|
289
|
-
</div>
|
|
290
|
-
<div id="cubby-browse"><div id="cubby-tables"></div></div>
|
|
291
|
-
<div id="cubby-sql" style="display:none">
|
|
292
|
-
<div style="display:flex;gap:8px;align-items:flex-end;margin-bottom:10px">
|
|
293
|
-
<div class="field" style="flex:1;margin:0">
|
|
294
|
-
<label class="field-label">Saved Queries</label>
|
|
295
|
-
<select id="query-select" style="width:100%;background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);padding:7px 10px;font-size:12px;outline:none">
|
|
296
|
-
<option value="">— select a saved query —</option>
|
|
297
|
-
</select>
|
|
298
|
-
</div>
|
|
299
|
-
<button class="btn-ghost" id="query-delete" title="Delete selected query" style="display:none;padding:7px 10px">✕</button>
|
|
300
|
-
</div>
|
|
301
|
-
<div class="field">
|
|
302
|
-
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px">
|
|
303
|
-
<label class="field-label" style="margin:0">Query</label>
|
|
304
|
-
<div style="display:flex;gap:6px;align-items:center">
|
|
305
|
-
<input type="text" id="query-name" placeholder="Name to save as..." style="width:160px;padding:4px 8px;font-size:11px;border-radius:5px;background:var(--surface2);border:1px solid var(--border);color:var(--text)">
|
|
306
|
-
<button class="btn-ghost" id="query-save" style="padding:4px 10px;font-size:11px">Save</button>
|
|
307
|
-
</div>
|
|
308
|
-
</div>
|
|
309
|
-
<textarea id="cubby-sql-input" rows="4">SELECT * FROM </textarea>
|
|
310
|
-
</div>
|
|
311
|
-
<button class="btn-primary" id="cubby-run">Run</button>
|
|
312
|
-
<div id="cubby-sql-result"></div>
|
|
313
|
-
</div>
|
|
314
|
-
<div id="cubby-graph">
|
|
315
|
-
<div id="graph-stats"></div>
|
|
316
|
-
<div style="position:relative">
|
|
317
|
-
<div id="graph-canvas"></div>
|
|
318
|
-
<div id="graph-detail">
|
|
319
|
-
<div id="gd-type-badge" class="gd-type"></div>
|
|
320
|
-
<div id="gd-attrs"></div>
|
|
321
|
-
</div>
|
|
322
|
-
<div id="graph-legend">
|
|
323
|
-
<div class="gl-row"><div class="gl-dot" style="background:#6366f1"></div>artifact</div>
|
|
324
|
-
<div class="gl-row"><div class="gl-dot" style="background:#f59e0b"></div>work</div>
|
|
325
|
-
<div class="gl-row"><div class="gl-dot" style="background:#22c55e"></div>operator</div>
|
|
326
|
-
<div class="gl-row"><div class="gl-dot" style="background:#ec4899"></div>eval</div>
|
|
327
|
-
<div class="gl-row"><div class="gl-dot" style="background:#71717a"></div>artifact_map</div>
|
|
328
|
-
</div>
|
|
329
|
-
</div>
|
|
330
|
-
</div>
|
|
331
|
-
</div>
|
|
332
|
-
</div>
|
|
333
|
-
|
|
334
|
-
<div id="bottom" style="height:220px">
|
|
335
|
-
<div id="resize-handle"></div>
|
|
336
|
-
<div class="bottom-bar">
|
|
337
|
-
<div class="btab active" data-btab="logs">Logs</div>
|
|
338
|
-
<div class="btab" data-btab="flow">Flow</div>
|
|
339
|
-
<div class="bottom-actions">
|
|
340
|
-
<input type="text" id="log-filter" placeholder="Filter...">
|
|
341
|
-
<button class="icon-btn" id="btn-clear" title="Clear">✕</button>
|
|
342
|
-
<button class="icon-btn" id="btn-collapse" title="Collapse">▼</button>
|
|
343
|
-
</div>
|
|
344
|
-
</div>
|
|
345
|
-
<div id="log-container"></div>
|
|
346
|
-
<div id="flow-container" style="display:none"></div>
|
|
347
|
-
</div>
|
|
348
|
-
</div>
|
|
349
|
-
|
|
350
|
-
<script>
|
|
351
1
|
(function(){
|
|
352
2
|
const API='';
|
|
353
3
|
let topology=null,activeEngagement=null,seqNum=0,ws=null,logFilter='',blastPayloads=null,blastAbort=false,batchRunning=false,batchAbort=false;
|
|
@@ -358,16 +8,16 @@ tr:hover td{background:rgba(255,255,255,.02)}
|
|
|
358
8
|
const res=await fetch(API+path,opts);return res.json();
|
|
359
9
|
}
|
|
360
10
|
|
|
361
|
-
//
|
|
11
|
+
// WebSocket
|
|
362
12
|
function connectWs(){
|
|
363
13
|
const proto=location.protocol==='https:'?'wss:':'ws:';
|
|
364
14
|
ws=new WebSocket(proto+'//'+location.host+'/ws');
|
|
365
|
-
ws.onopen=()=>{$('status-pill').
|
|
366
|
-
ws.onclose=()=>{$('status-pill').
|
|
15
|
+
ws.onopen=()=>{$('status-dot').className='status-dot ok';$('status-pill').innerHTML='<span class="status-dot ok" id="status-dot"></span>'+location.host};
|
|
16
|
+
ws.onclose=()=>{$('status-dot').className='status-dot err';$('status-pill').innerHTML='<span class="status-dot err" id="status-dot"></span>reconnecting';setTimeout(connectWs,2000)};
|
|
367
17
|
ws.onmessage=(e)=>{const m=JSON.parse(e.data);if(m.type==='log')appendLog(m.data);if(m.type==='trace')appendTrace(m.data)};
|
|
368
18
|
}
|
|
369
19
|
|
|
370
|
-
//
|
|
20
|
+
// Logs
|
|
371
21
|
function appendLog(entry){
|
|
372
22
|
const c=$('log-container'),d=document.createElement('div');
|
|
373
23
|
d.className='log-line'+(entry.level!=='log'?' '+entry.level:'');
|
|
@@ -381,7 +31,7 @@ tr:hover td{background:rgba(255,255,255,.02)}
|
|
|
381
31
|
c.scrollTop=c.scrollHeight;
|
|
382
32
|
}
|
|
383
33
|
|
|
384
|
-
//
|
|
34
|
+
// Flow
|
|
385
35
|
function appendTrace(ev){
|
|
386
36
|
const c=$('flow-container'),d=document.createElement('div');
|
|
387
37
|
d.className='flow-entry';
|
|
@@ -389,7 +39,7 @@ tr:hover td{background:rgba(255,255,255,.02)}
|
|
|
389
39
|
let ind='',det='',cls='';
|
|
390
40
|
switch(ev.type){
|
|
391
41
|
case'engagement:start':cls='fe-eng';det=ev.callee||'';break;
|
|
392
|
-
case'agent:call':ind=' ';cls='fe-agent';det=ev.callee+(ev.caller?' <span class="fe-dim"
|
|
42
|
+
case'agent:call':ind=' ';cls='fe-agent';det=ev.callee+(ev.caller?' <span class="fe-dim">\u2190 '+esc(ev.caller)+'</span>':'');break;
|
|
393
43
|
case'agent:return':ind=' ';cls=ev.success?'fe-ok':'fe-err';det=ev.callee+' <span class="fe-dim">'+(ev.durationMs||0).toFixed(1)+'ms</span>'+(ev.success?'':' <span class="fe-err">'+esc(ev.error||'FAILED')+'</span>');break;
|
|
394
44
|
case'cubby:query':ind=' ';cls='fe-cubby';det=(ev.cubbyAlias||'')+' <span class="fe-dim">'+esc(ev.sql||'')+'</span>';break;
|
|
395
45
|
case'cubby:exec':ind=' ';cls='fe-cubby';det=(ev.cubbyAlias||'')+' <span class="fe-dim">'+esc(ev.sql||'')+'</span>'+(ev.rowsAffected!=null?' <span class="fe-dim">('+ev.rowsAffected+')</span>':'');break;
|
|
@@ -398,21 +48,29 @@ tr:hover td{background:rgba(255,255,255,.02)}
|
|
|
398
48
|
c.appendChild(d);c.scrollTop=c.scrollHeight;
|
|
399
49
|
}
|
|
400
50
|
|
|
401
|
-
//
|
|
402
|
-
async function loadTopology(){topology=await api('GET','/api/topology');renderSidebar()}
|
|
51
|
+
// Sidebar
|
|
52
|
+
async function loadTopology(){topology=await api('GET','/api/topology');renderSidebar();updateEmptyStats()}
|
|
53
|
+
function updateEmptyStats(){
|
|
54
|
+
if(!topology)return;
|
|
55
|
+
var el=$('empty-stats');if(!el)return;
|
|
56
|
+
el.innerHTML=
|
|
57
|
+
'<div class="stat-card"><div class="stat-num">'+topology.engagements.length+'</div><div class="stat-label">Engagements</div></div>'+
|
|
58
|
+
'<div class="stat-card"><div class="stat-num">'+topology.agents.reduce(function(s,a){return s+a.tasks.length},0)+'</div><div class="stat-label">Agent Tasks</div></div>'+
|
|
59
|
+
'<div class="stat-card"><div class="stat-num">'+topology.cubbies.length+'</div><div class="stat-label">Cubbies</div></div>';
|
|
60
|
+
}
|
|
403
61
|
function renderSidebar(){
|
|
404
62
|
const el=$('sidebar');let h='';
|
|
405
63
|
if(topology.engagements.length){
|
|
406
|
-
h+='<div class="s-label"
|
|
64
|
+
h+='<div class="s-label">▶ Engagements</div>';
|
|
407
65
|
for(const e of topology.engagements)h+='<div class="s-item" data-type="engagement" data-name="'+ea(e.name)+'"><span class="s-dot s-dot-eng"></span>'+esc(e.name)+'</div>';
|
|
408
66
|
}
|
|
409
67
|
if(topology.agents.length){
|
|
410
|
-
h+='<div class="s-label"
|
|
68
|
+
h+='<div class="s-label">⚙ Agents</div>';
|
|
411
69
|
for(const a of topology.agents)for(const t of a.tasks)
|
|
412
70
|
h+='<div class="s-item" data-type="agent" data-alias="'+ea(a.alias)+'" data-task="'+ea(t.alias)+'"><span class="s-dot s-dot-agent"></span>'+esc(a.alias)+'.<span class="s-task">'+esc(t.alias)+'</span></div>';
|
|
413
71
|
}
|
|
414
72
|
if(topology.cubbies.length){
|
|
415
|
-
h+='<div class="s-label"
|
|
73
|
+
h+='<div class="s-label">▤ Cubbies</div>';
|
|
416
74
|
for(const c of topology.cubbies)h+='<div class="s-item" data-type="cubby" data-alias="'+ea(c.alias)+'"><span class="s-dot s-dot-cubby"></span>'+esc(c.alias)+'</div>';
|
|
417
75
|
const graphCubbies=topology.cubbies.filter(c=>c.alias);
|
|
418
76
|
if(graphCubbies.length)h+='<div class="s-item" data-type="graph-viz" style="margin-top:4px"><span class="s-dot" style="background:#22c55e"></span> Graph Visualizer</div>';
|
|
@@ -431,7 +89,7 @@ tr:hover td{background:rgba(255,255,255,.02)}
|
|
|
431
89
|
});
|
|
432
90
|
}
|
|
433
91
|
|
|
434
|
-
//
|
|
92
|
+
// Panels
|
|
435
93
|
function hideAll(){document.querySelectorAll('.panel').forEach(p=>p.classList.remove('active'));$('empty-state').style.display='none'}
|
|
436
94
|
|
|
437
95
|
function showEngagement(name){
|
|
@@ -441,9 +99,9 @@ tr:hover td{background:rgba(255,255,255,.02)}
|
|
|
441
99
|
$('eng-desc').textContent=ec?.description||'';
|
|
442
100
|
if(params&¶ms.properties){
|
|
443
101
|
const s=schemaToSample(params);
|
|
444
|
-
if('streamId' in s&&!s.streamId)s.streamId=name.toLowerCase().replace(
|
|
102
|
+
if('streamId' in s&&!s.streamId)s.streamId=name.toLowerCase().replace(/\s+/g,'-')+'-stream';
|
|
445
103
|
$('eng-payload').value=JSON.stringify(s,null,2);
|
|
446
|
-
}else{$('eng-payload').value=JSON.stringify({streamId:name.toLowerCase().replace(
|
|
104
|
+
}else{$('eng-payload').value=JSON.stringify({streamId:name.toLowerCase().replace(/\s+/g,'-')+'-stream'},null,2)}
|
|
447
105
|
$('eng-schema-hints').innerHTML=renderSchemaHints(params);
|
|
448
106
|
if(activeEngagement&&activeEngagement.name===name){$('eng-stream-area').style.display='';$('eng-stop').style.display='';$('eng-start').style.display='none'}
|
|
449
107
|
else{$('eng-stream-area').style.display='none';$('eng-stop').style.display='none';$('eng-start').style.display=''}
|
|
@@ -508,7 +166,7 @@ tr:hover td{background:rgba(255,255,255,.02)}
|
|
|
508
166
|
}return o;
|
|
509
167
|
}
|
|
510
168
|
|
|
511
|
-
//
|
|
169
|
+
// Engagement actions
|
|
512
170
|
$('eng-file-blast').addEventListener('change',function(){
|
|
513
171
|
if(!this.files||!this.files[0]){blastPayloads=null;return;}
|
|
514
172
|
var reader=new FileReader();
|
|
@@ -539,7 +197,7 @@ tr:hover td{background:rgba(255,255,255,.02)}
|
|
|
539
197
|
for(var i=0;i<payloads.length;i++){
|
|
540
198
|
if(blastAbort)break;
|
|
541
199
|
var payload=Object.assign({},payloads[i]);
|
|
542
|
-
payload.streamId=name.toLowerCase().replace(
|
|
200
|
+
payload.streamId=name.toLowerCase().replace(/\s+/g,'-')+'-'+i+'-'+Date.now();
|
|
543
201
|
$('eng-result').innerHTML='<div class="success-banner">Firing '+(i+1)+'/'+payloads.length+'...</div>';
|
|
544
202
|
try{
|
|
545
203
|
const r=await api('POST','/api/trigger',{engagement:name,payload});
|
|
@@ -547,7 +205,7 @@ tr:hover td{background:rgba(255,255,255,.02)}
|
|
|
547
205
|
}catch(e){errors++;}
|
|
548
206
|
if(delay&&i<payloads.length-1)await new Promise(function(r){setTimeout(r,delay);});
|
|
549
207
|
}
|
|
550
|
-
var msg=blastAbort?'Aborted after '+(i)+'/'+payloads.length+'
|
|
208
|
+
var msg=blastAbort?'Aborted after '+(i)+'/'+payloads.length+' \u2014 '+ok+' ok':'Done \u2014 '+ok+'/'+payloads.length+' ok'+(errors?' ('+errors+' errors)':'');
|
|
551
209
|
$('eng-result').innerHTML='<div class="'+(errors||blastAbort?'error-banner':'success-banner')+'">'+esc(msg)+'</div>';
|
|
552
210
|
$('eng-stop').style.display='none';this.style.display='';
|
|
553
211
|
});
|
|
@@ -576,7 +234,7 @@ tr:hover td{background:rgba(255,255,255,.02)}
|
|
|
576
234
|
function parseLines(text){
|
|
577
235
|
const trimmed=text.trim();
|
|
578
236
|
if(trimmed.startsWith('[')){try{const arr=JSON.parse(trimmed);if(Array.isArray(arr))return arr.map(o=>JSON.stringify(o))}catch{}}
|
|
579
|
-
return trimmed.split('
|
|
237
|
+
return trimmed.split('\n').map(l=>l.trim()).filter(l=>l&&!l.startsWith('//'));
|
|
580
238
|
}
|
|
581
239
|
|
|
582
240
|
let bulkAbort=false;
|
|
@@ -643,7 +301,7 @@ tr:hover td{background:rgba(255,255,255,.02)}
|
|
|
643
301
|
});
|
|
644
302
|
$('eng-file-stop').addEventListener('click',()=>{bulkAbort=true});
|
|
645
303
|
|
|
646
|
-
//
|
|
304
|
+
// Agent actions
|
|
647
305
|
$('agent-call').addEventListener('click',async function(){
|
|
648
306
|
const lbl=$('agent-label'),alias=lbl.dataset.alias,task=lbl.dataset.task;
|
|
649
307
|
let payload;try{payload=JSON.parse($('agent-payload').value)}catch{payload={}}
|
|
@@ -665,7 +323,7 @@ tr:hover td{background:rgba(255,255,255,.02)}
|
|
|
665
323
|
var r=await api('POST','/api/cubbies/'+batchCubby+'/query',{sql:batchSql,params:[]});
|
|
666
324
|
if(r.error){$('agent-result').innerHTML='<div class="error-banner">Query failed: '+esc(r.error)+'</div>';return;}
|
|
667
325
|
var rows=r.rows||[];
|
|
668
|
-
if(!rows.length){$('agent-result').innerHTML='<div class="success-banner">No rows matched
|
|
326
|
+
if(!rows.length){$('agent-result').innerHTML='<div class="success-banner">No rows matched \u2014 nothing to run</div>';return;}
|
|
669
327
|
const ac=topology.agents.find(function(a){return a.alias===alias;}),tc=ac&&ac.tasks.find(function(t){return t.alias===task;});
|
|
670
328
|
const paramKeys=tc&&tc.parameters&&tc.parameters.properties?Object.keys(tc.parameters.properties):[];
|
|
671
329
|
batchRunning=true;batchAbort=false;
|
|
@@ -673,7 +331,7 @@ tr:hover td{background:rgba(255,255,255,.02)}
|
|
|
673
331
|
var ok=0,errors=0,i=0;
|
|
674
332
|
for(;i<rows.length;i++){
|
|
675
333
|
if(batchAbort)break;
|
|
676
|
-
$('agent-result').innerHTML='<div class="success-banner">Running '+(i+1)+'/'+rows.length+'
|
|
334
|
+
$('agent-result').innerHTML='<div class="success-banner">Running '+(i+1)+'/'+rows.length+' \u2014 '+ok+' ok'+( errors?' '+errors+' err':'')+'</div>';
|
|
677
335
|
var payload={};
|
|
678
336
|
for(var j=0;j<paramKeys.length&&j<rows[i].length;j++)payload[paramKeys[j]]=rows[i][j];
|
|
679
337
|
try{
|
|
@@ -684,11 +342,11 @@ tr:hover td{background:rgba(255,255,255,.02)}
|
|
|
684
342
|
}
|
|
685
343
|
batchRunning=false;
|
|
686
344
|
this.textContent='Run Batch';this.classList.replace('btn-danger','btn-secondary');
|
|
687
|
-
var msg=batchAbort?'Stopped after '+i+'/'+rows.length+'
|
|
345
|
+
var msg=batchAbort?'Stopped after '+i+'/'+rows.length+' \u2014 '+ok+' ok':'Done \u2014 '+ok+'/'+rows.length+' ok'+(errors?' ('+errors+' errors)':'');
|
|
688
346
|
$('agent-result').innerHTML='<div class="'+(errors||batchAbort?'error-banner':'success-banner')+'">'+esc(msg)+'</div>';
|
|
689
347
|
});
|
|
690
348
|
|
|
691
|
-
//
|
|
349
|
+
// Cubby tabs
|
|
692
350
|
document.querySelectorAll('#panel-cubby .tab').forEach(tab=>{
|
|
693
351
|
tab.addEventListener('click',()=>{
|
|
694
352
|
document.querySelectorAll('#panel-cubby .tab').forEach(t=>t.classList.remove('active'));tab.classList.add('active');
|
|
@@ -701,7 +359,7 @@ tr:hover td{background:rgba(255,255,255,.02)}
|
|
|
701
359
|
});
|
|
702
360
|
});
|
|
703
361
|
|
|
704
|
-
//
|
|
362
|
+
// Graph tab
|
|
705
363
|
const GRAPH_NODE_COLORS={
|
|
706
364
|
artifact:{bg:'#6366f1',border:'#818cf8'},
|
|
707
365
|
work:{bg:'#f59e0b',border:'#fbbf24'},
|
|
@@ -721,20 +379,20 @@ tr:hover td{background:rgba(255,255,255,.02)}
|
|
|
721
379
|
api('POST','/api/cubbies/'+alias+'/query',{sql:"SELECT id,version,type,ref,created_at,attribute FROM nodes"}),
|
|
722
380
|
api('POST','/api/cubbies/'+alias+'/query',{sql:"SELECT id,src,type,dst FROM edges"}),
|
|
723
381
|
]);
|
|
724
|
-
if(nr.error){canvas.innerHTML='<div class="empty">This cubby has no nodes table
|
|
382
|
+
if(nr.error){canvas.innerHTML='<div class="empty">This cubby has no nodes table \u2014 graph view requires nodes + edges tables</div>';return}
|
|
725
383
|
const rows=nr.rows||[],erows=er.rows||[];
|
|
726
|
-
if(!rows.length){canvas.innerHTML='<div class="empty">No nodes yet
|
|
384
|
+
if(!rows.length){canvas.innerHTML='<div class="empty">No nodes yet \u2014 run an ingest first</div>';return}
|
|
727
385
|
|
|
728
|
-
// Build vis nodes
|
|
386
|
+
// Build vis nodes
|
|
729
387
|
const visNodes=[],versionedNode={},latestVer={},nodeVisId={};
|
|
730
388
|
let idx=1;
|
|
731
389
|
for(const r of rows){
|
|
732
390
|
const[id,ver,type,ref,created_at,attr]=r;
|
|
733
391
|
const col=GRAPH_NODE_COLORS[type]||GRAPH_NODE_COLORS.artifact_map;
|
|
734
392
|
const a=safeJ(attr);
|
|
735
|
-
const label=type==='artifact'?(a.title||ref||id).slice(0,22)+'
|
|
393
|
+
const label=type==='artifact'?(a.title||ref||id).slice(0,22)+'\nv'+ver
|
|
736
394
|
:type==='operator'?(a.name||ref||'operator').slice(0,18)
|
|
737
|
-
:type==='work'?(a.type||'work')+'
|
|
395
|
+
:type==='work'?(a.type||'work')+'\nv'+ver
|
|
738
396
|
:(type+' v'+ver);
|
|
739
397
|
const shape=type==='operator'?'diamond':type==='work'?'box':'ellipse';
|
|
740
398
|
visNodes.push({id:idx,label,
|
|
@@ -750,16 +408,13 @@ tr:hover td{background:rgba(255,255,255,.02)}
|
|
|
750
408
|
}
|
|
751
409
|
|
|
752
410
|
// Build vis edges
|
|
753
|
-
// nodeVer[id] = version from the nodes table (single row per unique id for work/operator/eval)
|
|
754
411
|
const nodeVer={};
|
|
755
412
|
for(const r of rows)nodeVer[r[0]]=r[1];
|
|
756
413
|
|
|
757
414
|
const visEdges=[];
|
|
758
415
|
for(const r of erows){
|
|
759
416
|
const[eid,src,type,dst]=r;
|
|
760
|
-
// src: use its single vis node (work/operator each have one entry)
|
|
761
417
|
const sVisId=latestVer[src]?.visId;
|
|
762
|
-
// dst: for versioned nodes (artifact), prefer version matching src's version
|
|
763
418
|
const srcVersion=nodeVer[src];
|
|
764
419
|
const dVisId=(versionedNode[dst]&&srcVersion&&versionedNode[dst][srcVersion])
|
|
765
420
|
?versionedNode[dst][srcVersion]
|
|
@@ -772,7 +427,7 @@ tr:hover td{background:rgba(255,255,255,.02)}
|
|
|
772
427
|
smooth:{type:'curvedCW',roundness:0.2}});
|
|
773
428
|
}
|
|
774
429
|
|
|
775
|
-
// Synthetic PREV_VERSION dashed edges
|
|
430
|
+
// Synthetic PREV_VERSION dashed edges
|
|
776
431
|
for(const[nodeId,verMap]of Object.entries(versionedNode)){
|
|
777
432
|
const ntype=rows.find(function(r){return r[0]===nodeId;})?.[2];
|
|
778
433
|
if(ntype!=='artifact')continue;
|
|
@@ -793,14 +448,14 @@ tr:hover td{background:rgba(255,255,255,.02)}
|
|
|
793
448
|
const typeCounts={};
|
|
794
449
|
for(const n of visNodes)typeCounts[n._raw.type]=(typeCounts[n._raw.type]||0)+1;
|
|
795
450
|
|
|
796
|
-
$('graph-stats').innerHTML=visNodes.length+' nodes
|
|
451
|
+
$('graph-stats').innerHTML=visNodes.length+' nodes \u00b7 '+visEdges.length+' edges';
|
|
797
452
|
|
|
798
|
-
// Legend toggles
|
|
453
|
+
// Legend toggles
|
|
799
454
|
var legendHtml='<div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:8px">';
|
|
800
455
|
for(const[type,col]of Object.entries(GRAPH_NODE_COLORS)){
|
|
801
456
|
if(!typeCounts[type])continue;
|
|
802
457
|
const isHidden=hiddenTypes.has(type);
|
|
803
|
-
legendHtml+='<button onclick="window.graphToggleType(
|
|
458
|
+
legendHtml+='<button onclick="window.graphToggleType(\''+type+'\')" style="display:flex;align-items:center;gap:5px;background:'+(isHidden?'var(--surface2)':col.bg+'22')+';border:1px solid '+(isHidden?'var(--border)':col.bg+'66')+';color:'+(isHidden?'var(--text3)':col.bg)+';border-radius:4px;padding:2px 8px;font-size:10px;cursor:pointer">'
|
|
804
459
|
+'<span style="width:8px;height:8px;border-radius:50%;background:'+(isHidden?'var(--border)':col.bg)+';display:inline-block"></span>'+esc(type)+' <span style="opacity:.6">'+typeCounts[type]+'</span></button>';
|
|
805
460
|
}
|
|
806
461
|
legendHtml+='</div>';
|
|
@@ -835,9 +490,7 @@ tr:hover td{background:rgba(255,255,255,.02)}
|
|
|
835
490
|
window.graphToggleType=function(type){
|
|
836
491
|
if(hiddenTypes.has(type))hiddenTypes.delete(type);else hiddenTypes.add(type);
|
|
837
492
|
if(!graphVisNodes)return;
|
|
838
|
-
// Update node visibility in-place — no full reload
|
|
839
493
|
graphVisNodes.update(graphVisNodes.get().filter(function(n){return n._raw&&n._raw.type===type;}).map(function(n){return{id:n.id,hidden:hiddenTypes.has(type)};}));
|
|
840
|
-
// Refresh legend button states
|
|
841
494
|
document.querySelectorAll('#graph-stats button').forEach(function(btn){
|
|
842
495
|
var t=btn.textContent.trim().split(' ')[0];
|
|
843
496
|
var col=GRAPH_NODE_COLORS[t];if(!col)return;
|
|
@@ -857,7 +510,7 @@ tr:hover td{background:rgba(255,255,255,.02)}
|
|
|
857
510
|
let h='';
|
|
858
511
|
for(const row of t.rows){
|
|
859
512
|
const tn=row[0],cr=await api('POST','/api/cubbies/'+alias+'/query',{sql:'SELECT COUNT(*) FROM "'+tn+'"'});
|
|
860
|
-
h+='<div class="cubby-table-item" onclick="browseCubbyTable(
|
|
513
|
+
h+='<div class="cubby-table-item" onclick="browseCubbyTable(\''+ea(alias)+'\',\''+ea(tn)+'\')">';
|
|
861
514
|
h+='<span class="cti-name">'+esc(tn)+'</span><span class="cti-count">'+(cr.rows?.[0]?.[0]??'?')+' rows</span></div>';
|
|
862
515
|
}
|
|
863
516
|
el.innerHTML=h;
|
|
@@ -867,8 +520,8 @@ tr:hover td{background:rgba(255,255,255,.02)}
|
|
|
867
520
|
const el=$('cubby-tables');
|
|
868
521
|
try{const r=await api('POST','/api/cubbies/'+alias+'/query',{sql:'SELECT * FROM "'+table+'" LIMIT 100'});
|
|
869
522
|
el.innerHTML='<div style="display:flex;gap:8px;justify-content:flex-end;margin-bottom:8px">'
|
|
870
|
-
+'<button class="btn-ghost" onclick="loadCubbyBrowse(
|
|
871
|
-
+'<button class="btn-danger" style="font-size:11px;padding:5px 12px" onclick="clearCubbyTable(
|
|
523
|
+
+'<button class="btn-ghost" onclick="loadCubbyBrowse(\''+ea(alias)+'\')">\u2190 Back</button>'
|
|
524
|
+
+'<button class="btn-danger" style="font-size:11px;padding:5px 12px" onclick="clearCubbyTable(\''+ea(alias)+'\',\''+ea(table)+'\')">Clear table</button>'
|
|
872
525
|
+'</div>'+renderTable(r);
|
|
873
526
|
}catch(e){el.innerHTML='<div class="error-banner">'+esc(e.message)+'</div>'}
|
|
874
527
|
};
|
|
@@ -883,7 +536,7 @@ tr:hover td{background:rgba(255,255,255,.02)}
|
|
|
883
536
|
|
|
884
537
|
$('cubby-run').addEventListener('click',async function(){
|
|
885
538
|
const alias=$('cubby-label').dataset.alias,sql=$('cubby-sql-input').value.trim();if(!sql)return;
|
|
886
|
-
const isW
|
|
539
|
+
const isW=/^\s*(INSERT|UPDATE|DELETE|CREATE|DROP|ALTER)/i.test(sql),ep=isW?'exec':'query';
|
|
887
540
|
this.textContent='...';
|
|
888
541
|
try{const r=await api('POST','/api/cubbies/'+alias+'/'+ep,{sql});
|
|
889
542
|
if(r.error){$('cubby-sql-result').innerHTML='<div class="error-banner">'+esc(r.error)+'</div>'}
|
|
@@ -893,12 +546,12 @@ tr:hover td{background:rgba(255,255,255,.02)}
|
|
|
893
546
|
this.textContent='Run';
|
|
894
547
|
});
|
|
895
548
|
|
|
896
|
-
//
|
|
549
|
+
// Query template manager (stored in cubby saved_queries table)
|
|
897
550
|
async function qtRefreshSelect(alias){
|
|
898
551
|
var sel=$('query-select'),prev=sel.value;
|
|
899
552
|
try{
|
|
900
553
|
var r=await api('POST','/api/cubbies/'+alias+'/query',{sql:'SELECT name FROM saved_queries ORDER BY name',params:[]});
|
|
901
|
-
sel.innerHTML='<option value=""
|
|
554
|
+
sel.innerHTML='<option value="">\u2014 select a saved query \u2014</option>';
|
|
902
555
|
(r.rows||[]).forEach(function(row){var o=document.createElement('option');o.value=row[0];o.textContent=row[0];sel.appendChild(o);});
|
|
903
556
|
sel.value=prev||'';
|
|
904
557
|
$('query-delete').style.display=sel.value?'':'none';
|
|
@@ -929,7 +582,7 @@ tr:hover td{background:rgba(255,255,255,.02)}
|
|
|
929
582
|
}catch(e){}
|
|
930
583
|
});
|
|
931
584
|
|
|
932
|
-
//
|
|
585
|
+
// Bottom panel: tabs, resize, collapse
|
|
933
586
|
document.querySelectorAll('.btab').forEach(tab=>{
|
|
934
587
|
tab.addEventListener('click',()=>{
|
|
935
588
|
document.querySelectorAll('.btab').forEach(t=>t.classList.remove('active'));tab.classList.add('active');
|
|
@@ -965,7 +618,7 @@ tr:hover td{background:rgba(255,255,255,.02)}
|
|
|
965
618
|
ta.addEventListener('keydown',function(e){if(e.key==='Tab'){e.preventDefault();const s=this.selectionStart;this.value=this.value.substring(0,s)+' '+this.value.substring(this.selectionEnd);this.selectionStart=this.selectionEnd=s+2}});
|
|
966
619
|
});
|
|
967
620
|
|
|
968
|
-
//
|
|
621
|
+
// Helpers
|
|
969
622
|
function $(id){return document.getElementById(id)}
|
|
970
623
|
function renderTable(r){
|
|
971
624
|
if(!r.columns||!r.rows)return'';let h='<div class="table-wrap"><table><thead><tr>';
|
|
@@ -974,11 +627,7 @@ tr:hover td{background:rgba(255,255,255,.02)}
|
|
|
974
627
|
return h+'</tbody></table></div>';
|
|
975
628
|
}
|
|
976
629
|
function esc(s){if(s==null)return'';return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"')}
|
|
977
|
-
function ea(s){return String(s).replace(/'/g,"
|
|
630
|
+
function ea(s){return String(s).replace(/'/g,"\\'").replace(/"/g,'"')}
|
|
978
631
|
|
|
979
632
|
loadTopology();connectWs();
|
|
980
633
|
})();
|
|
981
|
-
</script>
|
|
982
|
-
</body>
|
|
983
|
-
</html>`;
|
|
984
|
-
}
|