@formigio/fazemos-cli 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +296 -10
- package/dist/index.js.map +1 -1
- package/dist/monitor.d.ts +2 -0
- package/dist/monitor.js +533 -0
- package/dist/monitor.js.map +1 -0
- package/package.json +2 -1
package/dist/monitor.js
ADDED
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
import { createServer } from 'http';
|
|
2
|
+
import { getEnv, getToken, getActiveOrgId } from './config.js';
|
|
3
|
+
import { refreshSession } from './auth.js';
|
|
4
|
+
async function proxyApi(path, method = 'GET', body) {
|
|
5
|
+
const env = getEnv();
|
|
6
|
+
let token = getToken();
|
|
7
|
+
if (!token) {
|
|
8
|
+
token = await refreshSession();
|
|
9
|
+
if (!token)
|
|
10
|
+
throw new Error('Session expired');
|
|
11
|
+
}
|
|
12
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
13
|
+
if (token)
|
|
14
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
15
|
+
const orgId = getActiveOrgId();
|
|
16
|
+
if (orgId)
|
|
17
|
+
headers['X-Org-Id'] = orgId;
|
|
18
|
+
const options = { method, headers };
|
|
19
|
+
if (body && method !== 'GET')
|
|
20
|
+
options.body = JSON.stringify(body);
|
|
21
|
+
const res = await fetch(`${env.apiUrl}${path}`, options);
|
|
22
|
+
const text = await res.text();
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(text);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return text;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function readBody(req) {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
const chunks = [];
|
|
33
|
+
req.on('data', (c) => chunks.push(c));
|
|
34
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString()));
|
|
35
|
+
req.on('error', reject);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
export async function startMonitor(port) {
|
|
39
|
+
const server = createServer(async (req, res) => {
|
|
40
|
+
const url = new URL(req.url || '/', `http://localhost:${port}`);
|
|
41
|
+
// API proxy
|
|
42
|
+
if (url.pathname.startsWith('/api/')) {
|
|
43
|
+
try {
|
|
44
|
+
let body;
|
|
45
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
46
|
+
const raw = await readBody(req);
|
|
47
|
+
if (raw)
|
|
48
|
+
body = JSON.parse(raw);
|
|
49
|
+
}
|
|
50
|
+
const data = await proxyApi(url.pathname + url.search, req.method || 'GET', body);
|
|
51
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
52
|
+
res.end(JSON.stringify(data));
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
56
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
57
|
+
}
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// Serve the SPA
|
|
61
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
62
|
+
res.end(MONITOR_HTML);
|
|
63
|
+
});
|
|
64
|
+
server.listen(port, () => {
|
|
65
|
+
// printed by caller
|
|
66
|
+
});
|
|
67
|
+
return server;
|
|
68
|
+
}
|
|
69
|
+
const MONITOR_HTML = `<!DOCTYPE html>
|
|
70
|
+
<html lang="en">
|
|
71
|
+
<head>
|
|
72
|
+
<meta charset="utf-8">
|
|
73
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
74
|
+
<title>Fazemos Execution Monitor</title>
|
|
75
|
+
<style>
|
|
76
|
+
:root {
|
|
77
|
+
--bg: #0d1117; --surface: #161b22; --border: #30363d;
|
|
78
|
+
--text: #e6edf3; --text-dim: #8b949e; --text-muted: #484f58;
|
|
79
|
+
--green: #3fb950; --yellow: #d29922; --red: #f85149; --blue: #58a6ff; --cyan: #39d353;
|
|
80
|
+
--font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
|
81
|
+
--mono: 'SF Mono', 'Fira Code', 'Fira Mono', Menlo, Consolas, monospace;
|
|
82
|
+
}
|
|
83
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
84
|
+
body { background: var(--bg); color: var(--text); font-family: var(--font); font-size: 14px; }
|
|
85
|
+
|
|
86
|
+
.app { display: flex; height: 100vh; }
|
|
87
|
+
|
|
88
|
+
/* Sidebar */
|
|
89
|
+
.sidebar { width: 360px; min-width: 320px; border-right: 1px solid var(--border); display: flex; flex-direction: column; }
|
|
90
|
+
.sidebar-header { padding: 16px; border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 10px; }
|
|
91
|
+
.sidebar-header h1 { font-size: 16px; font-weight: 600; }
|
|
92
|
+
.sidebar-header .logo { color: var(--cyan); }
|
|
93
|
+
.filters { padding: 10px 16px; border-bottom: 1px solid var(--border); display: flex; gap: 6px; flex-wrap: wrap; }
|
|
94
|
+
.filters button {
|
|
95
|
+
background: var(--surface); border: 1px solid var(--border); color: var(--text-dim);
|
|
96
|
+
padding: 4px 10px; border-radius: 20px; font-size: 12px; cursor: pointer; transition: all 0.15s;
|
|
97
|
+
}
|
|
98
|
+
.filters button:hover { border-color: var(--text-dim); color: var(--text); }
|
|
99
|
+
.filters button.active { background: var(--blue); border-color: var(--blue); color: #fff; }
|
|
100
|
+
.exec-list { flex: 1; overflow-y: auto; }
|
|
101
|
+
.exec-item {
|
|
102
|
+
padding: 12px 16px; border-bottom: 1px solid var(--border); cursor: pointer; transition: background 0.1s;
|
|
103
|
+
display: flex; flex-direction: column; gap: 4px;
|
|
104
|
+
}
|
|
105
|
+
.exec-item:hover { background: var(--surface); }
|
|
106
|
+
.exec-item.selected { background: #1f2937; border-left: 3px solid var(--blue); }
|
|
107
|
+
.exec-item .row { display: flex; align-items: center; gap: 8px; }
|
|
108
|
+
.exec-item .agent { font-weight: 600; font-size: 13px; }
|
|
109
|
+
.exec-item .id { font-family: var(--mono); font-size: 11px; color: var(--text-muted); }
|
|
110
|
+
.exec-item .meta { font-size: 12px; color: var(--text-dim); }
|
|
111
|
+
|
|
112
|
+
.status-badge {
|
|
113
|
+
display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px;
|
|
114
|
+
border-radius: 12px; font-size: 11px; font-weight: 600; text-transform: uppercase;
|
|
115
|
+
}
|
|
116
|
+
.status-pending { background: #1c1e26; color: var(--text-dim); }
|
|
117
|
+
.status-running { background: #2a1f00; color: var(--yellow); }
|
|
118
|
+
.status-completed { background: #0d2818; color: var(--green); }
|
|
119
|
+
.status-failed { background: #2d0f0f; color: var(--red); }
|
|
120
|
+
.status-cancelled { background: #1c1e26; color: var(--text-muted); }
|
|
121
|
+
|
|
122
|
+
.pulse { display: inline-block; width: 6px; height: 6px; border-radius: 50%; }
|
|
123
|
+
.pulse-running { background: var(--yellow); animation: pulse 1.5s ease-in-out infinite; }
|
|
124
|
+
.pulse-pending { background: var(--text-dim); }
|
|
125
|
+
.pulse-completed { background: var(--green); }
|
|
126
|
+
.pulse-failed { background: var(--red); }
|
|
127
|
+
|
|
128
|
+
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }
|
|
129
|
+
|
|
130
|
+
/* Main panel */
|
|
131
|
+
.main { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
|
|
132
|
+
.main-empty { display: flex; align-items: center; justify-content: center; flex: 1; color: var(--text-dim); font-size: 15px; }
|
|
133
|
+
|
|
134
|
+
.detail-header { padding: 16px 20px; border-bottom: 1px solid var(--border); }
|
|
135
|
+
.detail-header h2 { font-size: 18px; margin-bottom: 8px; }
|
|
136
|
+
.detail-meta { display: flex; gap: 20px; flex-wrap: wrap; }
|
|
137
|
+
.detail-meta .item { display: flex; flex-direction: column; gap: 2px; }
|
|
138
|
+
.detail-meta .label { font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; }
|
|
139
|
+
.detail-meta .value { font-size: 13px; color: var(--text); }
|
|
140
|
+
.detail-meta .value.mono { font-family: var(--mono); }
|
|
141
|
+
|
|
142
|
+
.tabs { display: flex; border-bottom: 1px solid var(--border); padding: 0 20px; }
|
|
143
|
+
.tabs button {
|
|
144
|
+
background: none; border: none; border-bottom: 2px solid transparent;
|
|
145
|
+
color: var(--text-dim); padding: 10px 16px; cursor: pointer; font-size: 13px; font-weight: 500;
|
|
146
|
+
}
|
|
147
|
+
.tabs button:hover { color: var(--text); }
|
|
148
|
+
.tabs button.active { color: var(--blue); border-bottom-color: var(--blue); }
|
|
149
|
+
|
|
150
|
+
.tab-content { flex: 1; overflow: hidden; display: flex; flex-direction: column; }
|
|
151
|
+
|
|
152
|
+
/* Logs panel */
|
|
153
|
+
.logs-container {
|
|
154
|
+
flex: 1; overflow-y: auto; padding: 12px 20px; font-family: var(--mono); font-size: 12px;
|
|
155
|
+
line-height: 1.7; white-space: pre-wrap; word-break: break-all; background: var(--bg);
|
|
156
|
+
}
|
|
157
|
+
.logs-container::-webkit-scrollbar { width: 8px; }
|
|
158
|
+
.logs-container::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
|
|
159
|
+
.log-line { padding: 1px 0; }
|
|
160
|
+
.log-time { color: var(--text-muted); margin-right: 8px; }
|
|
161
|
+
.log-empty { color: var(--text-dim); font-style: italic; padding: 20px; font-family: var(--font); }
|
|
162
|
+
.autoscroll-bar {
|
|
163
|
+
padding: 6px 20px; border-top: 1px solid var(--border); display: flex; align-items: center;
|
|
164
|
+
justify-content: space-between; font-size: 12px; color: var(--text-dim);
|
|
165
|
+
}
|
|
166
|
+
.autoscroll-bar label { display: flex; align-items: center; gap: 6px; cursor: pointer; }
|
|
167
|
+
|
|
168
|
+
/* Info panel */
|
|
169
|
+
.info-panel { flex: 1; overflow-y: auto; padding: 20px; }
|
|
170
|
+
.info-grid { display: grid; grid-template-columns: 140px 1fr; gap: 8px 16px; font-size: 13px; }
|
|
171
|
+
.info-grid .label { color: var(--text-muted); }
|
|
172
|
+
.info-grid .value { color: var(--text); word-break: break-all; }
|
|
173
|
+
.info-grid .value.mono { font-family: var(--mono); font-size: 12px; }
|
|
174
|
+
|
|
175
|
+
/* Events panel */
|
|
176
|
+
.events-list { flex: 1; overflow-y: auto; padding: 12px 20px; }
|
|
177
|
+
.event-row { display: flex; gap: 12px; padding: 6px 0; border-bottom: 1px solid var(--border); font-size: 12px; }
|
|
178
|
+
.event-time { color: var(--text-muted); font-family: var(--mono); white-space: nowrap; min-width: 140px; }
|
|
179
|
+
.event-type { font-weight: 600; color: var(--blue); min-width: 120px; }
|
|
180
|
+
.event-detail { color: var(--text-dim); font-family: var(--mono); font-size: 11px; }
|
|
181
|
+
|
|
182
|
+
/* Agents bar */
|
|
183
|
+
.agents-bar { padding: 10px 16px; border-bottom: 1px solid var(--border); display: flex; gap: 8px; overflow-x: auto; }
|
|
184
|
+
.agent-chip {
|
|
185
|
+
display: flex; align-items: center; gap: 6px; padding: 4px 10px;
|
|
186
|
+
background: var(--surface); border: 1px solid var(--border); border-radius: 16px;
|
|
187
|
+
font-size: 12px; white-space: nowrap;
|
|
188
|
+
}
|
|
189
|
+
.agent-dot { width: 8px; height: 8px; border-radius: 50%; }
|
|
190
|
+
.agent-dot.idle { background: var(--green); }
|
|
191
|
+
.agent-dot.busy { background: var(--yellow); }
|
|
192
|
+
.agent-dot.error { background: var(--red); }
|
|
193
|
+
.agent-dot.offline { background: var(--text-muted); }
|
|
194
|
+
|
|
195
|
+
/* Refresh indicator */
|
|
196
|
+
.refresh-indicator { font-size: 11px; color: var(--text-muted); padding: 8px 16px; text-align: center; }
|
|
197
|
+
|
|
198
|
+
/* Cancel button */
|
|
199
|
+
.btn-cancel {
|
|
200
|
+
background: none; border: 1px solid var(--red); color: var(--red); padding: 4px 12px;
|
|
201
|
+
border-radius: 6px; font-size: 12px; cursor: pointer; margin-left: auto;
|
|
202
|
+
}
|
|
203
|
+
.btn-cancel:hover { background: var(--red); color: #fff; }
|
|
204
|
+
|
|
205
|
+
/* Error/loading output */
|
|
206
|
+
.error-msg { color: var(--red); background: #2d0f0f; padding: 12px 16px; border-radius: 6px; margin: 12px 20px; font-size: 13px; }
|
|
207
|
+
</style>
|
|
208
|
+
</head>
|
|
209
|
+
<body>
|
|
210
|
+
<div class="app" id="app">
|
|
211
|
+
<div class="sidebar">
|
|
212
|
+
<div class="sidebar-header">
|
|
213
|
+
<span class="logo">◆</span>
|
|
214
|
+
<h1>Execution Monitor</h1>
|
|
215
|
+
</div>
|
|
216
|
+
<div class="agents-bar" id="agents-bar"></div>
|
|
217
|
+
<div class="filters" id="filters">
|
|
218
|
+
<button class="active" data-status="">All</button>
|
|
219
|
+
<button data-status="running">Running</button>
|
|
220
|
+
<button data-status="pending">Pending</button>
|
|
221
|
+
<button data-status="completed">Completed</button>
|
|
222
|
+
<button data-status="failed">Failed</button>
|
|
223
|
+
</div>
|
|
224
|
+
<div class="exec-list" id="exec-list"></div>
|
|
225
|
+
<div class="refresh-indicator" id="refresh-indicator">Loading...</div>
|
|
226
|
+
</div>
|
|
227
|
+
<div class="main" id="main-panel">
|
|
228
|
+
<div class="main-empty">Select an execution to view details</div>
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
|
|
232
|
+
<script>
|
|
233
|
+
(function() {
|
|
234
|
+
// State
|
|
235
|
+
let executions = [];
|
|
236
|
+
let agents = [];
|
|
237
|
+
let selectedId = null;
|
|
238
|
+
let selectedExec = null;
|
|
239
|
+
let selectedOutput = '';
|
|
240
|
+
let selectedEvents = [];
|
|
241
|
+
let statusFilter = '';
|
|
242
|
+
let activeTab = 'logs';
|
|
243
|
+
let autoScroll = true;
|
|
244
|
+
let refreshInterval = null;
|
|
245
|
+
let detailInterval = null;
|
|
246
|
+
|
|
247
|
+
const POLL_MS = 3000;
|
|
248
|
+
const LIST_POLL_MS = 5000;
|
|
249
|
+
|
|
250
|
+
// API
|
|
251
|
+
async function fetchJSON(url) {
|
|
252
|
+
const res = await fetch(url);
|
|
253
|
+
return res.json();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function postJSON(url, body) {
|
|
257
|
+
const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
|
|
258
|
+
return res.json();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Formatters
|
|
262
|
+
function shortId(id) { return id ? id.slice(0, 8) : ''; }
|
|
263
|
+
function fmtTime(ts) { return ts ? new Date(ts).toLocaleString() : '—'; }
|
|
264
|
+
function fmtDuration(ms) {
|
|
265
|
+
if (!ms) return '—';
|
|
266
|
+
if (ms < 1000) return ms + 'ms';
|
|
267
|
+
if (ms < 60000) return (ms / 1000).toFixed(1) + 's';
|
|
268
|
+
return (ms / 60000).toFixed(1) + 'm';
|
|
269
|
+
}
|
|
270
|
+
function fmtCost(usd) { return usd ? '$' + parseFloat(usd).toFixed(4) : '—'; }
|
|
271
|
+
function escHtml(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
|
|
272
|
+
|
|
273
|
+
// Load executions list
|
|
274
|
+
async function loadExecutions() {
|
|
275
|
+
try {
|
|
276
|
+
const qs = statusFilter ? '?status=' + statusFilter : '';
|
|
277
|
+
const data = await fetchJSON('/api/executions' + qs);
|
|
278
|
+
executions = data.executions || [];
|
|
279
|
+
renderList();
|
|
280
|
+
document.getElementById('refresh-indicator').textContent =
|
|
281
|
+
'Updated ' + new Date().toLocaleTimeString() + ' · polling every ' + (LIST_POLL_MS/1000) + 's';
|
|
282
|
+
} catch (err) {
|
|
283
|
+
document.getElementById('refresh-indicator').textContent = 'Error: ' + err.message;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Load agents
|
|
288
|
+
async function loadAgents() {
|
|
289
|
+
try {
|
|
290
|
+
const data = await fetchJSON('/api/agents');
|
|
291
|
+
agents = data.agents || [];
|
|
292
|
+
renderAgents();
|
|
293
|
+
} catch (err) { /* ignore */ }
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Load execution detail
|
|
297
|
+
async function loadDetail(id) {
|
|
298
|
+
try {
|
|
299
|
+
const data = await fetchJSON('/api/executions/' + id);
|
|
300
|
+
selectedExec = data.execution;
|
|
301
|
+
selectedOutput = selectedExec.output_text || '';
|
|
302
|
+
renderDetail();
|
|
303
|
+
} catch (err) {
|
|
304
|
+
document.getElementById('main-panel').innerHTML = '<div class="error-msg">Failed to load: ' + escHtml(err.message) + '</div>';
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Load events for execution's agent
|
|
309
|
+
async function loadEvents(agentId, execId) {
|
|
310
|
+
try {
|
|
311
|
+
let url = '/api/agents/' + agentId + '/events?limit=50';
|
|
312
|
+
if (execId) url += '&execution_id=' + execId;
|
|
313
|
+
const data = await fetchJSON(url);
|
|
314
|
+
selectedEvents = data.events || [];
|
|
315
|
+
if (activeTab === 'events') renderDetail();
|
|
316
|
+
} catch (err) { /* ignore */ }
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Render agents bar
|
|
320
|
+
function renderAgents() {
|
|
321
|
+
const bar = document.getElementById('agents-bar');
|
|
322
|
+
if (!agents.length) { bar.innerHTML = '<span style="color:var(--text-dim);font-size:12px">No agents</span>'; return; }
|
|
323
|
+
bar.innerHTML = agents.map(a => {
|
|
324
|
+
const st = a.agent_status || 'offline';
|
|
325
|
+
const runs = a.total_executions ? ' (' + a.total_executions + ')' : '';
|
|
326
|
+
return '<div class="agent-chip"><div class="agent-dot ' + st + '"></div>' + escHtml(a.display_name) + runs + '</div>';
|
|
327
|
+
}).join('');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Render execution list
|
|
331
|
+
function renderList() {
|
|
332
|
+
const list = document.getElementById('exec-list');
|
|
333
|
+
if (!executions.length) {
|
|
334
|
+
list.innerHTML = '<div style="padding:20px;color:var(--text-dim);text-align:center">No executions found</div>';
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
list.innerHTML = executions.map(e => {
|
|
338
|
+
const sel = e.id === selectedId ? ' selected' : '';
|
|
339
|
+
const st = e.status || 'pending';
|
|
340
|
+
const time = e.started_at || e.queued_at || e.created_at;
|
|
341
|
+
const cost = e.cost_usd ? ' · ' + fmtCost(e.cost_usd) : '';
|
|
342
|
+
const dur = e.duration_ms ? ' · ' + fmtDuration(e.duration_ms) : '';
|
|
343
|
+
return '<div class="exec-item' + sel + '" data-id="' + e.id + '">'
|
|
344
|
+
+ '<div class="row"><span class="status-badge status-' + st + '"><span class="pulse pulse-' + st + '"></span>' + st + '</span>'
|
|
345
|
+
+ '<span class="agent">' + escHtml(e.agent_name || 'unknown') + '</span></div>'
|
|
346
|
+
+ '<div class="row"><span class="meta">' + escHtml(e.source_type || '') + cost + dur + '</span></div>'
|
|
347
|
+
+ '<div class="row"><span class="id">' + shortId(e.id) + '</span>'
|
|
348
|
+
+ '<span class="meta" style="margin-left:auto">' + fmtTime(time) + '</span></div>'
|
|
349
|
+
+ '</div>';
|
|
350
|
+
}).join('');
|
|
351
|
+
|
|
352
|
+
list.querySelectorAll('.exec-item').forEach(el => {
|
|
353
|
+
el.addEventListener('click', () => selectExecution(el.dataset.id));
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Select execution
|
|
358
|
+
function selectExecution(id) {
|
|
359
|
+
selectedId = id;
|
|
360
|
+
selectedExec = null;
|
|
361
|
+
selectedOutput = '';
|
|
362
|
+
selectedEvents = [];
|
|
363
|
+
activeTab = 'logs';
|
|
364
|
+
renderList();
|
|
365
|
+
loadDetail(id);
|
|
366
|
+
// Start polling detail
|
|
367
|
+
if (detailInterval) clearInterval(detailInterval);
|
|
368
|
+
detailInterval = setInterval(() => {
|
|
369
|
+
if (selectedId) loadDetail(selectedId);
|
|
370
|
+
}, POLL_MS);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Render detail panel
|
|
374
|
+
function renderDetail() {
|
|
375
|
+
const panel = document.getElementById('main-panel');
|
|
376
|
+
if (!selectedExec) { panel.innerHTML = '<div class="main-empty">Loading...</div>'; return; }
|
|
377
|
+
const e = selectedExec;
|
|
378
|
+
const st = e.status || 'pending';
|
|
379
|
+
const isRunning = st === 'running' || st === 'pending';
|
|
380
|
+
|
|
381
|
+
let cancelBtn = '';
|
|
382
|
+
if (isRunning) {
|
|
383
|
+
cancelBtn = '<button class="btn-cancel" id="btn-cancel">Cancel</button>';
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
let tabContent = '';
|
|
387
|
+
if (activeTab === 'logs') {
|
|
388
|
+
tabContent = renderLogsTab();
|
|
389
|
+
} else if (activeTab === 'info') {
|
|
390
|
+
tabContent = renderInfoTab();
|
|
391
|
+
} else if (activeTab === 'events') {
|
|
392
|
+
tabContent = renderEventsTab();
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
panel.innerHTML =
|
|
396
|
+
'<div class="detail-header">'
|
|
397
|
+
+ '<div style="display:flex;align-items:center;gap:12px">'
|
|
398
|
+
+ '<h2>' + escHtml(e.agent_name || 'Execution') + '</h2>'
|
|
399
|
+
+ '<span class="status-badge status-' + st + '"><span class="pulse pulse-' + st + '"></span>' + st + '</span>'
|
|
400
|
+
+ cancelBtn
|
|
401
|
+
+ '</div>'
|
|
402
|
+
+ '<div class="detail-meta" style="margin-top:10px">'
|
|
403
|
+
+ metaItem('ID', shortId(e.id), true) + metaItem('Source', e.source_type + ' ' + shortId(e.source_id))
|
|
404
|
+
+ metaItem('Cost', fmtCost(e.cost_usd)) + metaItem('Duration', fmtDuration(e.duration_ms))
|
|
405
|
+
+ metaItem('Tokens', (e.input_tokens || 0) + ' in / ' + (e.output_tokens || 0) + ' out')
|
|
406
|
+
+ '</div></div>'
|
|
407
|
+
+ '<div class="tabs" id="tabs">'
|
|
408
|
+
+ tabBtn('logs', 'Live Logs') + tabBtn('info', 'Details') + tabBtn('events', 'Events')
|
|
409
|
+
+ '</div>'
|
|
410
|
+
+ '<div class="tab-content">' + tabContent + '</div>';
|
|
411
|
+
|
|
412
|
+
// Tab listeners
|
|
413
|
+
panel.querySelectorAll('.tabs button').forEach(btn => {
|
|
414
|
+
btn.addEventListener('click', () => {
|
|
415
|
+
activeTab = btn.dataset.tab;
|
|
416
|
+
if (activeTab === 'events' && e.agent_member_id) loadEvents(e.agent_member_id, e.id);
|
|
417
|
+
renderDetail();
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
// Cancel listener
|
|
422
|
+
const cancelEl = document.getElementById('btn-cancel');
|
|
423
|
+
if (cancelEl) {
|
|
424
|
+
cancelEl.addEventListener('click', async () => {
|
|
425
|
+
try {
|
|
426
|
+
await postJSON('/api/executions/' + e.id + '/cancel', {});
|
|
427
|
+
loadDetail(e.id);
|
|
428
|
+
loadExecutions();
|
|
429
|
+
} catch (err) { alert('Cancel failed: ' + err.message); }
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Auto-scroll logs
|
|
434
|
+
if (activeTab === 'logs' && autoScroll) {
|
|
435
|
+
const lc = document.getElementById('logs-container');
|
|
436
|
+
if (lc) lc.scrollTop = lc.scrollHeight;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Auto-scroll checkbox
|
|
440
|
+
const asCb = document.getElementById('autoscroll-cb');
|
|
441
|
+
if (asCb) {
|
|
442
|
+
asCb.addEventListener('change', (ev) => { autoScroll = ev.target.checked; });
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function metaItem(label, value, mono) {
|
|
447
|
+
return '<div class="item"><span class="label">' + label + '</span><span class="value' + (mono ? ' mono' : '') + '">' + escHtml(String(value || '—')) + '</span></div>';
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function tabBtn(name, label) {
|
|
451
|
+
return '<button data-tab="' + name + '"' + (activeTab === name ? ' class="active"' : '') + '>' + label + '</button>';
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function renderLogsTab() {
|
|
455
|
+
if (!selectedOutput) {
|
|
456
|
+
const st = selectedExec?.status;
|
|
457
|
+
if (st === 'pending') return '<div class="log-empty">Execution is pending — logs will appear when it starts running.</div>';
|
|
458
|
+
if (st === 'running') return '<div class="log-empty">Waiting for output...</div>';
|
|
459
|
+
return '<div class="log-empty">No output recorded for this execution.</div>';
|
|
460
|
+
}
|
|
461
|
+
const lines = selectedOutput.split('\\n');
|
|
462
|
+
const html = lines.map((line, i) => {
|
|
463
|
+
return '<div class="log-line"><span class="log-time">' + String(i + 1).padStart(4, ' ') + '</span>' + escHtml(line) + '</div>';
|
|
464
|
+
}).join('');
|
|
465
|
+
return '<div class="logs-container" id="logs-container">' + html + '</div>'
|
|
466
|
+
+ '<div class="autoscroll-bar">'
|
|
467
|
+
+ '<label><input type="checkbox" id="autoscroll-cb"' + (autoScroll ? ' checked' : '') + '> Auto-scroll</label>'
|
|
468
|
+
+ '<span>' + lines.length + ' lines</span>'
|
|
469
|
+
+ '</div>';
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function renderInfoTab() {
|
|
473
|
+
const e = selectedExec;
|
|
474
|
+
const rows = [
|
|
475
|
+
['Execution ID', e.id, true],
|
|
476
|
+
['Agent', e.agent_name],
|
|
477
|
+
['Agent Member ID', e.agent_member_id, true],
|
|
478
|
+
['Source Type', e.source_type],
|
|
479
|
+
['Source ID', e.source_id, true],
|
|
480
|
+
['Status', e.status],
|
|
481
|
+
['Cost (USD)', fmtCost(e.cost_usd)],
|
|
482
|
+
['Input Tokens', e.input_tokens || '—'],
|
|
483
|
+
['Output Tokens', e.output_tokens || '—'],
|
|
484
|
+
['Duration', fmtDuration(e.duration_ms)],
|
|
485
|
+
['Fargate Task ARN', e.fargate_task_arn || '—', true],
|
|
486
|
+
['Queued', fmtTime(e.queued_at)],
|
|
487
|
+
['Started', fmtTime(e.started_at)],
|
|
488
|
+
['Completed', fmtTime(e.completed_at)],
|
|
489
|
+
];
|
|
490
|
+
if (e.error) rows.push(['Error', e.error]);
|
|
491
|
+
if (e.context) rows.push(['Context', JSON.stringify(e.context, null, 2), true]);
|
|
492
|
+
const html = rows.map(([label, value, mono]) =>
|
|
493
|
+
'<div class="label">' + escHtml(label) + '</div><div class="value' + (mono ? ' mono' : '') + '">' + escHtml(String(value ?? '—')) + '</div>'
|
|
494
|
+
).join('');
|
|
495
|
+
return '<div class="info-panel"><div class="info-grid">' + html + '</div></div>';
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function renderEventsTab() {
|
|
499
|
+
if (!selectedEvents.length) {
|
|
500
|
+
return '<div class="log-empty">No events found for this execution.</div>';
|
|
501
|
+
}
|
|
502
|
+
const html = selectedEvents.map(ev => {
|
|
503
|
+
const det = ev.details && Object.keys(ev.details).length > 0 ? JSON.stringify(ev.details) : '';
|
|
504
|
+
return '<div class="event-row">'
|
|
505
|
+
+ '<span class="event-time">' + fmtTime(ev.created_at) + '</span>'
|
|
506
|
+
+ '<span class="event-type">' + escHtml(ev.event_type) + '</span>'
|
|
507
|
+
+ (det ? '<span class="event-detail">' + escHtml(det) + '</span>' : '')
|
|
508
|
+
+ '</div>';
|
|
509
|
+
}).join('');
|
|
510
|
+
return '<div class="events-list">' + html + '</div>';
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Filter buttons
|
|
514
|
+
document.getElementById('filters').addEventListener('click', (e) => {
|
|
515
|
+
if (e.target.tagName !== 'BUTTON') return;
|
|
516
|
+
statusFilter = e.target.dataset.status;
|
|
517
|
+
document.querySelectorAll('#filters button').forEach(b => b.classList.remove('active'));
|
|
518
|
+
e.target.classList.add('active');
|
|
519
|
+
loadExecutions();
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
// Initial load
|
|
523
|
+
loadExecutions();
|
|
524
|
+
loadAgents();
|
|
525
|
+
|
|
526
|
+
// Polling
|
|
527
|
+
refreshInterval = setInterval(loadExecutions, LIST_POLL_MS);
|
|
528
|
+
setInterval(loadAgents, 15000);
|
|
529
|
+
})();
|
|
530
|
+
</script>
|
|
531
|
+
</body>
|
|
532
|
+
</html>`;
|
|
533
|
+
//# sourceMappingURL=monitor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"monitor.js","sourceRoot":"","sources":["../src/monitor.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formigio/fazemos-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "CLI for the Fazemos Team Accomplishment Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^20.17.0",
|
|
40
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
40
41
|
"tsx": "^4.19.2",
|
|
41
42
|
"typescript": "^5.6.3",
|
|
42
43
|
"vitest": "^4.1.0"
|