agyqueue 0.1.0__py3-none-any.whl
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.
- agyqueue/__init__.py +1 -0
- agyqueue/client.py +129 -0
- agyqueue/config.py +72 -0
- agyqueue/dashboard.html +1155 -0
- agyqueue/mcp_server.py +438 -0
- agyqueue/models.py +38 -0
- agyqueue/notifications.py +187 -0
- agyqueue/storage.py +423 -0
- agyqueue/task_queue.py +111 -0
- agyqueue/worker.py +671 -0
- agyqueue-0.1.0.dist-info/METADATA +287 -0
- agyqueue-0.1.0.dist-info/RECORD +15 -0
- agyqueue-0.1.0.dist-info/WHEEL +5 -0
- agyqueue-0.1.0.dist-info/entry_points.txt +2 -0
- agyqueue-0.1.0.dist-info/top_level.txt +1 -0
agyqueue/dashboard.html
ADDED
|
@@ -0,0 +1,1155 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>AgyQueue Workflow Console</title>
|
|
7
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
8
|
+
<style>
|
|
9
|
+
:root {
|
|
10
|
+
--bg-primary: #0b0f19;
|
|
11
|
+
--bg-secondary: #111827;
|
|
12
|
+
--bg-tertiary: #1f2937;
|
|
13
|
+
--bg-elevated: #1e293b;
|
|
14
|
+
--text-primary: #f9fafb;
|
|
15
|
+
--text-secondary: #9ca3af;
|
|
16
|
+
--text-muted: #6b7280;
|
|
17
|
+
--accent-primary: #6366f1; /* Indigo */
|
|
18
|
+
--accent-hover: #4f46e5;
|
|
19
|
+
--success: #10b981;
|
|
20
|
+
--danger: #ef4444;
|
|
21
|
+
--warning: #f59e0b;
|
|
22
|
+
--border-color: #374151;
|
|
23
|
+
--sidebar-width: 280px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
* {
|
|
27
|
+
box-sizing: border-box;
|
|
28
|
+
margin: 0;
|
|
29
|
+
padding: 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
body {
|
|
33
|
+
font-family: 'Inter', sans-serif;
|
|
34
|
+
background-color: var(--bg-primary);
|
|
35
|
+
color: var(--text-primary);
|
|
36
|
+
display: flex;
|
|
37
|
+
height: 100vh;
|
|
38
|
+
overflow: hidden;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* Sidebar Styling */
|
|
42
|
+
.sidebar {
|
|
43
|
+
width: var(--sidebar-width);
|
|
44
|
+
background-color: var(--bg-secondary);
|
|
45
|
+
border-right: 1px solid var(--border-color);
|
|
46
|
+
display: flex;
|
|
47
|
+
flex-direction: column;
|
|
48
|
+
height: 100%;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.brand-header {
|
|
52
|
+
padding: 20px 24px;
|
|
53
|
+
border-bottom: 1px solid var(--border-color);
|
|
54
|
+
display: flex;
|
|
55
|
+
align-items: center;
|
|
56
|
+
gap: 12px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.brand-logo {
|
|
60
|
+
width: 28px;
|
|
61
|
+
height: 28px;
|
|
62
|
+
background: linear-gradient(135deg, var(--accent-primary), #3b82f6);
|
|
63
|
+
border-radius: 6px;
|
|
64
|
+
display: flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
justify-content: center;
|
|
67
|
+
font-weight: 700;
|
|
68
|
+
color: white;
|
|
69
|
+
font-size: 14px;
|
|
70
|
+
box-shadow: 0 0 12px rgba(99, 102, 241, 0.4);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.brand-title {
|
|
74
|
+
font-weight: 600;
|
|
75
|
+
font-size: 16px;
|
|
76
|
+
letter-spacing: -0.01em;
|
|
77
|
+
color: var(--text-primary);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.brand-badge {
|
|
81
|
+
font-size: 9px;
|
|
82
|
+
background-color: rgba(99, 102, 241, 0.2);
|
|
83
|
+
color: #a5b4fc;
|
|
84
|
+
padding: 1px 6px;
|
|
85
|
+
border-radius: 4px;
|
|
86
|
+
font-weight: 500;
|
|
87
|
+
border: 1px solid rgba(99, 102, 241, 0.3);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.sidebar-section {
|
|
91
|
+
padding: 20px 24px 10px;
|
|
92
|
+
font-size: 11px;
|
|
93
|
+
text-transform: uppercase;
|
|
94
|
+
letter-spacing: 0.05em;
|
|
95
|
+
color: var(--text-muted);
|
|
96
|
+
font-weight: 600;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.task-submitter-box {
|
|
100
|
+
padding: 0 20px 20px;
|
|
101
|
+
border-bottom: 1px solid var(--border-color);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.form-group {
|
|
105
|
+
margin-bottom: 14px;
|
|
106
|
+
display: flex;
|
|
107
|
+
flex-direction: column;
|
|
108
|
+
gap: 6px;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.form-group label {
|
|
112
|
+
font-size: 11px;
|
|
113
|
+
font-weight: 500;
|
|
114
|
+
color: var(--text-secondary);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.form-group textarea, .form-group select {
|
|
118
|
+
width: 100%;
|
|
119
|
+
background-color: var(--bg-primary);
|
|
120
|
+
border: 1px solid var(--border-color);
|
|
121
|
+
border-radius: 6px;
|
|
122
|
+
padding: 10px;
|
|
123
|
+
color: var(--text-primary);
|
|
124
|
+
font-family: inherit;
|
|
125
|
+
font-size: 12px;
|
|
126
|
+
outline: none;
|
|
127
|
+
transition: border-color 0.2s;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.form-group textarea:focus, .form-group select:focus {
|
|
131
|
+
border-color: var(--accent-primary);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.btn {
|
|
135
|
+
width: 100%;
|
|
136
|
+
background-color: var(--accent-primary);
|
|
137
|
+
color: white;
|
|
138
|
+
border: none;
|
|
139
|
+
border-radius: 6px;
|
|
140
|
+
padding: 10px;
|
|
141
|
+
font-size: 13px;
|
|
142
|
+
font-weight: 500;
|
|
143
|
+
cursor: pointer;
|
|
144
|
+
transition: background-color 0.2s;
|
|
145
|
+
display: flex;
|
|
146
|
+
align-items: center;
|
|
147
|
+
justify-content: center;
|
|
148
|
+
gap: 8px;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.btn:hover {
|
|
152
|
+
background-color: var(--accent-hover);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.btn-secondary {
|
|
156
|
+
background-color: transparent;
|
|
157
|
+
border: 1px solid var(--border-color);
|
|
158
|
+
color: var(--text-secondary);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.btn-secondary:hover {
|
|
162
|
+
background-color: var(--bg-tertiary);
|
|
163
|
+
color: var(--text-primary);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.btn-danger {
|
|
167
|
+
background-color: rgba(239, 68, 68, 0.15);
|
|
168
|
+
border: 1px solid rgba(239, 68, 68, 0.3);
|
|
169
|
+
color: #fca5a5;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.btn-danger:hover {
|
|
173
|
+
background-color: var(--danger);
|
|
174
|
+
color: white;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/* Main Console Workspace */
|
|
178
|
+
.workspace {
|
|
179
|
+
flex: 1;
|
|
180
|
+
display: flex;
|
|
181
|
+
flex-direction: column;
|
|
182
|
+
height: 100%;
|
|
183
|
+
background-color: var(--bg-primary);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.console-header {
|
|
187
|
+
height: 69px;
|
|
188
|
+
background-color: var(--bg-secondary);
|
|
189
|
+
border-bottom: 1px solid var(--border-color);
|
|
190
|
+
padding: 0 32px;
|
|
191
|
+
display: flex;
|
|
192
|
+
align-items: center;
|
|
193
|
+
justify-content: space-between;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.console-path {
|
|
197
|
+
font-size: 14px;
|
|
198
|
+
font-weight: 500;
|
|
199
|
+
display: flex;
|
|
200
|
+
align-items: center;
|
|
201
|
+
gap: 8px;
|
|
202
|
+
color: var(--text-secondary);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.console-path span.active {
|
|
206
|
+
color: var(--text-primary);
|
|
207
|
+
font-weight: 600;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/* Filter Controls */
|
|
211
|
+
.grid-filters {
|
|
212
|
+
padding: 20px 32px;
|
|
213
|
+
display: flex;
|
|
214
|
+
align-items: center;
|
|
215
|
+
justify-content: space-between;
|
|
216
|
+
background-color: var(--bg-primary);
|
|
217
|
+
border-bottom: 1px solid var(--border-color);
|
|
218
|
+
gap: 20px;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.search-wrapper {
|
|
222
|
+
position: relative;
|
|
223
|
+
flex: 1;
|
|
224
|
+
max-width: 400px;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.search-wrapper svg {
|
|
228
|
+
position: absolute;
|
|
229
|
+
left: 12px;
|
|
230
|
+
top: 50%;
|
|
231
|
+
transform: translateY(-50%);
|
|
232
|
+
width: 16px;
|
|
233
|
+
height: 16px;
|
|
234
|
+
stroke: var(--text-muted);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.search-input {
|
|
238
|
+
width: 100%;
|
|
239
|
+
background-color: var(--bg-secondary);
|
|
240
|
+
border: 1px solid var(--border-color);
|
|
241
|
+
border-radius: 6px;
|
|
242
|
+
padding: 8px 12px 8px 36px;
|
|
243
|
+
color: var(--text-primary);
|
|
244
|
+
font-size: 13px;
|
|
245
|
+
outline: none;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.search-input:focus {
|
|
249
|
+
border-color: var(--accent-primary);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.status-tabs {
|
|
253
|
+
display: flex;
|
|
254
|
+
background-color: var(--bg-secondary);
|
|
255
|
+
padding: 3px;
|
|
256
|
+
border-radius: 6px;
|
|
257
|
+
border: 1px solid var(--border-color);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.status-tab {
|
|
261
|
+
padding: 6px 12px;
|
|
262
|
+
font-size: 12px;
|
|
263
|
+
font-weight: 500;
|
|
264
|
+
color: var(--text-secondary);
|
|
265
|
+
border-radius: 4px;
|
|
266
|
+
cursor: pointer;
|
|
267
|
+
transition: all 0.2s;
|
|
268
|
+
display: flex;
|
|
269
|
+
align-items: center;
|
|
270
|
+
gap: 6px;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.status-tab.active {
|
|
274
|
+
background-color: var(--bg-tertiary);
|
|
275
|
+
color: var(--text-primary);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.status-count {
|
|
279
|
+
font-size: 10px;
|
|
280
|
+
background-color: rgba(255, 255, 255, 0.1);
|
|
281
|
+
padding: 1px 6px;
|
|
282
|
+
border-radius: 10px;
|
|
283
|
+
color: var(--text-secondary);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/* Workflows Grid Panel */
|
|
287
|
+
.content-panel {
|
|
288
|
+
flex: 1;
|
|
289
|
+
display: flex;
|
|
290
|
+
overflow: hidden;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.workflows-grid-container {
|
|
294
|
+
flex: 1;
|
|
295
|
+
overflow-y: auto;
|
|
296
|
+
border-right: 1px solid var(--border-color);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.workflow-table {
|
|
300
|
+
width: 100%;
|
|
301
|
+
border-collapse: collapse;
|
|
302
|
+
text-align: left;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.workflow-table th {
|
|
306
|
+
font-size: 11px;
|
|
307
|
+
text-transform: uppercase;
|
|
308
|
+
color: var(--text-muted);
|
|
309
|
+
font-weight: 600;
|
|
310
|
+
padding: 12px 24px;
|
|
311
|
+
border-bottom: 1px solid var(--border-color);
|
|
312
|
+
background-color: rgba(17, 24, 39, 0.4);
|
|
313
|
+
letter-spacing: 0.05em;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.workflow-table td {
|
|
317
|
+
padding: 16px 24px;
|
|
318
|
+
border-bottom: 1px solid var(--border-color);
|
|
319
|
+
font-size: 13px;
|
|
320
|
+
vertical-align: middle;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.workflow-row {
|
|
324
|
+
cursor: pointer;
|
|
325
|
+
transition: background-color 0.2s;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.workflow-row:hover {
|
|
329
|
+
background-color: rgba(31, 41, 55, 0.3);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.workflow-row.selected {
|
|
333
|
+
background-color: rgba(99, 102, 241, 0.08);
|
|
334
|
+
border-left: 3px solid var(--accent-primary);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.workflow-id-col {
|
|
338
|
+
font-family: 'JetBrains Mono', monospace;
|
|
339
|
+
font-weight: 500;
|
|
340
|
+
color: var(--accent-primary);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.workflow-type-badge {
|
|
344
|
+
font-family: 'JetBrains Mono', monospace;
|
|
345
|
+
font-size: 11px;
|
|
346
|
+
background-color: var(--bg-tertiary);
|
|
347
|
+
padding: 3px 8px;
|
|
348
|
+
border-radius: 4px;
|
|
349
|
+
color: var(--text-secondary);
|
|
350
|
+
border: 1px solid var(--border-color);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/* Badges Styling */
|
|
354
|
+
.status-badge {
|
|
355
|
+
font-size: 10px;
|
|
356
|
+
font-weight: 600;
|
|
357
|
+
text-transform: uppercase;
|
|
358
|
+
padding: 4px 10px;
|
|
359
|
+
border-radius: 12px;
|
|
360
|
+
display: inline-flex;
|
|
361
|
+
align-items: center;
|
|
362
|
+
gap: 6px;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.status-badge-queued { background-color: rgba(245, 158, 11, 0.1); color: var(--warning); border: 1px solid rgba(245, 158, 11, 0.2); }
|
|
366
|
+
.status-badge-running { background-color: rgba(59, 130, 246, 0.1); color: var(--accent-primary); border: 1px solid rgba(59, 130, 246, 0.2); }
|
|
367
|
+
.status-badge-waiting { background-color: rgba(129, 140, 248, 0.1); color: #818cf8; border: 1px solid rgba(129, 140, 248, 0.2); }
|
|
368
|
+
.status-badge-completed { background-color: rgba(16, 185, 129, 0.1); color: var(--success); border: 1px solid rgba(16, 185, 129, 0.2); }
|
|
369
|
+
.status-badge-failed { background-color: rgba(239, 68, 68, 0.1); color: var(--danger); border: 1px solid rgba(239, 68, 68, 0.2); }
|
|
370
|
+
.status-badge-cancelled { background-color: rgba(107, 114, 128, 0.1); color: var(--text-secondary); border: 1px solid rgba(107, 114, 128, 0.2); }
|
|
371
|
+
|
|
372
|
+
/* Details Pane Styling */
|
|
373
|
+
.details-pane {
|
|
374
|
+
width: 48%;
|
|
375
|
+
min-width: 480px;
|
|
376
|
+
background-color: var(--bg-secondary);
|
|
377
|
+
display: flex;
|
|
378
|
+
flex-direction: column;
|
|
379
|
+
overflow: hidden;
|
|
380
|
+
height: 100%;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.details-pane-placeholder {
|
|
384
|
+
flex: 1;
|
|
385
|
+
display: flex;
|
|
386
|
+
flex-direction: column;
|
|
387
|
+
align-items: center;
|
|
388
|
+
justify-content: center;
|
|
389
|
+
color: var(--text-muted);
|
|
390
|
+
gap: 16px;
|
|
391
|
+
padding: 40px;
|
|
392
|
+
text-align: center;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.details-pane-placeholder svg {
|
|
396
|
+
width: 48px;
|
|
397
|
+
height: 48px;
|
|
398
|
+
stroke: var(--text-muted);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.details-header {
|
|
402
|
+
padding: 24px;
|
|
403
|
+
border-bottom: 1px solid var(--border-color);
|
|
404
|
+
display: flex;
|
|
405
|
+
flex-direction: column;
|
|
406
|
+
gap: 16px;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.details-title-row {
|
|
410
|
+
display: flex;
|
|
411
|
+
align-items: center;
|
|
412
|
+
justify-content: space-between;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.details-workflow-id {
|
|
416
|
+
font-family: 'JetBrains Mono', monospace;
|
|
417
|
+
font-size: 18px;
|
|
418
|
+
font-weight: 600;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.details-actions {
|
|
422
|
+
display: flex;
|
|
423
|
+
gap: 8px;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.details-meta-grid {
|
|
427
|
+
display: grid;
|
|
428
|
+
grid-template-columns: repeat(2, 1fr);
|
|
429
|
+
gap: 16px;
|
|
430
|
+
background-color: rgba(17, 24, 39, 0.4);
|
|
431
|
+
padding: 16px;
|
|
432
|
+
border-radius: 8px;
|
|
433
|
+
border: 1px solid var(--border-color);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
.meta-item {
|
|
437
|
+
display: flex;
|
|
438
|
+
flex-direction: column;
|
|
439
|
+
gap: 4px;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.meta-label {
|
|
443
|
+
font-size: 10px;
|
|
444
|
+
text-transform: uppercase;
|
|
445
|
+
color: var(--text-muted);
|
|
446
|
+
font-weight: 600;
|
|
447
|
+
letter-spacing: 0.05em;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
.meta-value {
|
|
451
|
+
font-size: 13px;
|
|
452
|
+
font-weight: 500;
|
|
453
|
+
color: var(--text-primary);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.meta-value-mono {
|
|
457
|
+
font-family: 'JetBrains Mono', monospace;
|
|
458
|
+
color: var(--accent-primary);
|
|
459
|
+
cursor: pointer;
|
|
460
|
+
text-decoration: underline;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
.details-body {
|
|
464
|
+
flex: 1;
|
|
465
|
+
overflow-y: auto;
|
|
466
|
+
padding: 24px;
|
|
467
|
+
display: flex;
|
|
468
|
+
flex-direction: column;
|
|
469
|
+
gap: 24px;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/* Timeline Event History */
|
|
473
|
+
.history-timeline {
|
|
474
|
+
display: flex;
|
|
475
|
+
flex-direction: column;
|
|
476
|
+
position: relative;
|
|
477
|
+
padding-left: 20px;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
.history-timeline::before {
|
|
481
|
+
content: '';
|
|
482
|
+
position: absolute;
|
|
483
|
+
left: 5px;
|
|
484
|
+
top: 6px;
|
|
485
|
+
bottom: 6px;
|
|
486
|
+
width: 2px;
|
|
487
|
+
background-color: var(--border-color);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
.timeline-event {
|
|
491
|
+
position: relative;
|
|
492
|
+
margin-bottom: 20px;
|
|
493
|
+
display: flex;
|
|
494
|
+
flex-direction: column;
|
|
495
|
+
gap: 4px;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
.timeline-event::before {
|
|
499
|
+
content: '';
|
|
500
|
+
position: absolute;
|
|
501
|
+
left: -20px;
|
|
502
|
+
top: 4px;
|
|
503
|
+
width: 10px;
|
|
504
|
+
height: 10px;
|
|
505
|
+
border-radius: 50%;
|
|
506
|
+
background-color: var(--bg-tertiary);
|
|
507
|
+
border: 2px solid var(--border-color);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
.timeline-event.active::before {
|
|
511
|
+
background-color: var(--accent-primary);
|
|
512
|
+
border-color: #818cf8;
|
|
513
|
+
box-shadow: 0 0 8px rgba(99, 102, 241, 0.6);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
.timeline-event.completed::before {
|
|
517
|
+
background-color: var(--success);
|
|
518
|
+
border-color: #34d399;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
.timeline-event.failed::before {
|
|
522
|
+
background-color: var(--danger);
|
|
523
|
+
border-color: #f87171;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
.event-header {
|
|
527
|
+
display: flex;
|
|
528
|
+
align-items: center;
|
|
529
|
+
justify-content: space-between;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
.event-name {
|
|
533
|
+
font-size: 13px;
|
|
534
|
+
font-weight: 600;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
.event-time {
|
|
538
|
+
font-size: 11px;
|
|
539
|
+
color: var(--text-muted);
|
|
540
|
+
font-family: 'JetBrains Mono', monospace;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
.event-desc {
|
|
544
|
+
font-size: 12px;
|
|
545
|
+
color: var(--text-secondary);
|
|
546
|
+
line-height: 1.4;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/* Tabs Styling */
|
|
550
|
+
.details-tabs {
|
|
551
|
+
display: flex;
|
|
552
|
+
border-bottom: 1px solid var(--border-color);
|
|
553
|
+
background-color: rgba(17, 24, 39, 0.4);
|
|
554
|
+
padding: 0 24px;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
.tab-btn {
|
|
558
|
+
padding: 14px 16px;
|
|
559
|
+
font-size: 13px;
|
|
560
|
+
font-weight: 500;
|
|
561
|
+
color: var(--text-secondary);
|
|
562
|
+
background: none;
|
|
563
|
+
border: none;
|
|
564
|
+
border-bottom: 2px solid transparent;
|
|
565
|
+
cursor: pointer;
|
|
566
|
+
transition: all 0.2s;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
.tab-btn.active {
|
|
570
|
+
color: var(--accent-primary);
|
|
571
|
+
border-bottom-color: var(--accent-primary);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
.tab-content {
|
|
575
|
+
display: none;
|
|
576
|
+
padding: 20px;
|
|
577
|
+
background-color: var(--bg-primary);
|
|
578
|
+
border: 1px solid var(--border-color);
|
|
579
|
+
border-radius: 8px;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
.tab-content.active {
|
|
583
|
+
display: block;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/* Output Viewports */
|
|
587
|
+
.code-block {
|
|
588
|
+
font-family: 'JetBrains Mono', monospace;
|
|
589
|
+
font-size: 12.5px;
|
|
590
|
+
line-height: 1.6;
|
|
591
|
+
background-color: #05070c;
|
|
592
|
+
border: 1px solid var(--border-color);
|
|
593
|
+
border-radius: 6px;
|
|
594
|
+
padding: 16px;
|
|
595
|
+
overflow-x: auto;
|
|
596
|
+
color: #cbd5e1;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
.result-markdown {
|
|
600
|
+
line-height: 1.6;
|
|
601
|
+
color: #cbd5e1;
|
|
602
|
+
font-size: 13.5px;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
.result-markdown h1, .result-markdown h2, .result-markdown h3 {
|
|
606
|
+
color: white;
|
|
607
|
+
margin-top: 20px;
|
|
608
|
+
margin-bottom: 8px;
|
|
609
|
+
font-weight: 600;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
.result-markdown h1 { font-size: 1.4em; border-bottom: 1px solid var(--border-color); padding-bottom: 6px; }
|
|
613
|
+
.result-markdown h2 { font-size: 1.2em; }
|
|
614
|
+
.result-markdown p { margin-bottom: 12px; }
|
|
615
|
+
.result-markdown code {
|
|
616
|
+
font-family: 'JetBrains Mono', monospace;
|
|
617
|
+
background-color: var(--bg-tertiary);
|
|
618
|
+
padding: 2px 6px;
|
|
619
|
+
border-radius: 4px;
|
|
620
|
+
color: #fb7185;
|
|
621
|
+
font-size: 0.9em;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
.result-markdown pre {
|
|
625
|
+
background-color: #05070c;
|
|
626
|
+
border: 1px solid var(--border-color);
|
|
627
|
+
padding: 14px;
|
|
628
|
+
border-radius: 6px;
|
|
629
|
+
margin: 12px 0;
|
|
630
|
+
overflow-x: auto;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
.result-markdown pre code {
|
|
634
|
+
background: none;
|
|
635
|
+
color: inherit;
|
|
636
|
+
padding: 0;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
.progress-indicator {
|
|
640
|
+
display: flex;
|
|
641
|
+
align-items: center;
|
|
642
|
+
justify-content: space-between;
|
|
643
|
+
font-size: 12px;
|
|
644
|
+
font-weight: 500;
|
|
645
|
+
color: var(--text-secondary);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
.progress-bar-glow {
|
|
649
|
+
height: 6px;
|
|
650
|
+
background-color: var(--bg-tertiary);
|
|
651
|
+
border-radius: 3px;
|
|
652
|
+
overflow: hidden;
|
|
653
|
+
box-shadow: inset 0 1px 2px rgba(0,0,0,0.4);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
.progress-fill {
|
|
657
|
+
height: 100%;
|
|
658
|
+
background: linear-gradient(90deg, var(--accent-primary), var(--success));
|
|
659
|
+
width: 0%;
|
|
660
|
+
transition: width 0.3s ease;
|
|
661
|
+
box-shadow: 0 0 8px rgba(99, 102, 241, 0.5);
|
|
662
|
+
}
|
|
663
|
+
</style>
|
|
664
|
+
</head>
|
|
665
|
+
<body>
|
|
666
|
+
|
|
667
|
+
<!-- Left Sidebar: Form & Namespace Branding -->
|
|
668
|
+
<div class="sidebar">
|
|
669
|
+
<div class="brand-header">
|
|
670
|
+
<div class="brand-logo">Q</div>
|
|
671
|
+
<span class="brand-title">AgyQueue</span>
|
|
672
|
+
<span class="brand-badge">Console</span>
|
|
673
|
+
</div>
|
|
674
|
+
|
|
675
|
+
<div class="sidebar-section">Namespace</div>
|
|
676
|
+
<div style="padding: 0 20px 20px; border-bottom: 1px solid var(--border-color);">
|
|
677
|
+
<select style="width: 100%; background-color: var(--bg-primary); border: 1px solid var(--border-color); color: white; padding: 8px; border-radius: 6px; font-size: 12px; outline: none;">
|
|
678
|
+
<option value="default">default-namespace</option>
|
|
679
|
+
<option value="production">production-namespace</option>
|
|
680
|
+
</select>
|
|
681
|
+
</div>
|
|
682
|
+
|
|
683
|
+
<div class="sidebar-section">Start Workflow</div>
|
|
684
|
+
<form class="task-submitter-box" id="workflowForm">
|
|
685
|
+
<div class="form-group">
|
|
686
|
+
<label for="promptInput">Task/Prompt payload</label>
|
|
687
|
+
<textarea id="promptInput" rows="4" required placeholder="Describe task instructions..."></textarea>
|
|
688
|
+
</div>
|
|
689
|
+
<div class="form-group">
|
|
690
|
+
<label for="typeSelect">Workflow Executor</label>
|
|
691
|
+
<select id="typeSelect">
|
|
692
|
+
<option value="generic">generic_runner</option>
|
|
693
|
+
<option value="manifest_compliance">manifest_compliance</option>
|
|
694
|
+
<option value="fastapi_gen">fastapi_gen</option>
|
|
695
|
+
<option value="multi_agent">multi_agent_orchestrator</option>
|
|
696
|
+
</select>
|
|
697
|
+
</div>
|
|
698
|
+
<button type="submit" class="btn">
|
|
699
|
+
<svg width="14" height="14" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
|
|
700
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.348a1.125 1.125 0 010 1.971l-11.54 6.347a1.125 1.125 0 01-1.667-.985V5.653z" />
|
|
701
|
+
</svg>
|
|
702
|
+
Run Workflow
|
|
703
|
+
</button>
|
|
704
|
+
</form>
|
|
705
|
+
</div>
|
|
706
|
+
|
|
707
|
+
<!-- Main Workspace Area -->
|
|
708
|
+
<div class="workspace">
|
|
709
|
+
<!-- Header -->
|
|
710
|
+
<div class="console-header">
|
|
711
|
+
<div class="console-path">
|
|
712
|
+
Workflows / <span class="active">runs</span>
|
|
713
|
+
</div>
|
|
714
|
+
<div style="font-size: 12px; color: var(--text-muted); font-family: 'JetBrains Mono', monospace;" id="liveClock">
|
|
715
|
+
UTC: --:--:--
|
|
716
|
+
</div>
|
|
717
|
+
</div>
|
|
718
|
+
|
|
719
|
+
<!-- Grid Filter / Actions Bar -->
|
|
720
|
+
<div class="grid-filters">
|
|
721
|
+
<div class="search-wrapper">
|
|
722
|
+
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
723
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
|
724
|
+
</svg>
|
|
725
|
+
<input type="text" class="search-input" id="searchInput" placeholder="Search by Workflow Run ID..." oninput="filterGrid()">
|
|
726
|
+
</div>
|
|
727
|
+
|
|
728
|
+
<div class="status-tabs" id="statusTabs">
|
|
729
|
+
<div class="status-tab active" data-status="all" onclick="filterStatus('all')">All <span class="status-count" id="count-all">0</span></div>
|
|
730
|
+
<div class="status-tab" data-status="running" onclick="filterStatus('running')">Running <span class="status-count" id="count-running">0</span></div>
|
|
731
|
+
<div class="status-tab" data-status="completed" onclick="filterStatus('completed')">Completed <span class="status-count" id="count-completed">0</span></div>
|
|
732
|
+
<div class="status-tab" data-status="failed" onclick="filterStatus('failed')">Failed <span class="status-count" id="count-failed">0</span></div>
|
|
733
|
+
</div>
|
|
734
|
+
</div>
|
|
735
|
+
|
|
736
|
+
<!-- Panel Grid & Details -->
|
|
737
|
+
<div class="content-panel">
|
|
738
|
+
<!-- Workflows Grid Table -->
|
|
739
|
+
<div class="workflows-grid-container">
|
|
740
|
+
<table class="workflow-table">
|
|
741
|
+
<thead>
|
|
742
|
+
<tr>
|
|
743
|
+
<th>Workflow Run ID</th>
|
|
744
|
+
<th>Type</th>
|
|
745
|
+
<th>Status</th>
|
|
746
|
+
<th>Progress</th>
|
|
747
|
+
<th>Start Time</th>
|
|
748
|
+
</tr>
|
|
749
|
+
</thead>
|
|
750
|
+
<tbody id="workflowGridBody">
|
|
751
|
+
<!-- Dynamic grid rows -->
|
|
752
|
+
</tbody>
|
|
753
|
+
</table>
|
|
754
|
+
</div>
|
|
755
|
+
|
|
756
|
+
<!-- Side Details Pane -->
|
|
757
|
+
<div class="details-pane" id="detailsPane">
|
|
758
|
+
<div class="details-pane-placeholder" id="detailsPlaceholder">
|
|
759
|
+
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
|
760
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12h3.75M9 15h3.375c.9 0 1.625-.725 1.625-1.625V11.25M9 12V9c0-.9.725-1.625 1.625-1.625h2.75C14.275 7.375 15 8.1 15 9v2.25m-6 3h3.75m-3.75 3h.008v.008H12v-.008zm1.5-6h.008v.008H13.5V9.75zm1.5 6h.008v.008H15v-.008zm1.5-3h.008v.008H16.5v-.008z" />
|
|
761
|
+
</svg>
|
|
762
|
+
<p>Select a workflow run from the grid to inspect event history, payloads, and execution logs.</p>
|
|
763
|
+
</div>
|
|
764
|
+
|
|
765
|
+
<div id="detailsWorkspace" style="display: none; height: 100%; flex-direction: column;">
|
|
766
|
+
<!-- Header -->
|
|
767
|
+
<div class="details-header">
|
|
768
|
+
<div class="details-title-row">
|
|
769
|
+
<span class="details-workflow-id" id="detWorkflowId">agy-xxxx</span>
|
|
770
|
+
<div class="details-actions">
|
|
771
|
+
<button class="btn btn-secondary btn-danger" id="detCancelBtn" style="padding: 6px 12px; font-size: 11px;">Cancel Run</button>
|
|
772
|
+
</div>
|
|
773
|
+
</div>
|
|
774
|
+
|
|
775
|
+
<div class="details-meta-grid">
|
|
776
|
+
<div class="meta-item">
|
|
777
|
+
<span class="meta-label">Executor Type</span>
|
|
778
|
+
<span class="meta-value" id="detMetaType">generic</span>
|
|
779
|
+
</div>
|
|
780
|
+
<div class="meta-item">
|
|
781
|
+
<span class="meta-label">Status</span>
|
|
782
|
+
<div><span class="status-badge" id="detMetaBadge">COMPLETED</span></div>
|
|
783
|
+
</div>
|
|
784
|
+
<div class="meta-item">
|
|
785
|
+
<span class="meta-label">Parent Task</span>
|
|
786
|
+
<span class="meta-value" id="detMetaParent">None</span>
|
|
787
|
+
</div>
|
|
788
|
+
<div class="meta-item">
|
|
789
|
+
<span class="meta-label">Start Time</span>
|
|
790
|
+
<span class="meta-value" id="detMetaStart">--/-- --:--</span>
|
|
791
|
+
</div>
|
|
792
|
+
</div>
|
|
793
|
+
</div>
|
|
794
|
+
|
|
795
|
+
<!-- Tabs Selector -->
|
|
796
|
+
<div class="details-tabs">
|
|
797
|
+
<button class="tab-btn active" onclick="switchTab('history')">History Events</button>
|
|
798
|
+
<button class="tab-btn" onclick="switchTab('result')">Result Output</button>
|
|
799
|
+
<button class="tab-btn" onclick="switchTab('payload')">JSON Payload</button>
|
|
800
|
+
</div>
|
|
801
|
+
|
|
802
|
+
<!-- Details Body Content -->
|
|
803
|
+
<div class="details-body">
|
|
804
|
+
<!-- History Timeline Tab -->
|
|
805
|
+
<div class="tab-content active" id="tab-history">
|
|
806
|
+
<div class="history-timeline" id="detTimeline">
|
|
807
|
+
<!-- Dynamic timeline nodes -->
|
|
808
|
+
</div>
|
|
809
|
+
</div>
|
|
810
|
+
|
|
811
|
+
<!-- Result Output Tab -->
|
|
812
|
+
<div class="tab-content" id="tab-result">
|
|
813
|
+
<div class="result-markdown" id="detResult">
|
|
814
|
+
<!-- Markdown content -->
|
|
815
|
+
</div>
|
|
816
|
+
</div>
|
|
817
|
+
|
|
818
|
+
<!-- Raw JSON Payload Tab -->
|
|
819
|
+
<div class="tab-content" id="tab-payload">
|
|
820
|
+
<pre class="code-block" id="detPayload">{}</pre>
|
|
821
|
+
</div>
|
|
822
|
+
</div>
|
|
823
|
+
</div>
|
|
824
|
+
</div>
|
|
825
|
+
</div>
|
|
826
|
+
</div>
|
|
827
|
+
|
|
828
|
+
<script>
|
|
829
|
+
let currentTaskId = null;
|
|
830
|
+
let pollIntervalId = null;
|
|
831
|
+
let allTasks = [];
|
|
832
|
+
let selectedStatusFilter = 'all';
|
|
833
|
+
|
|
834
|
+
// Update live clock
|
|
835
|
+
setInterval(() => {
|
|
836
|
+
document.getElementById('liveClock').innerText = 'UTC: ' + new Date().toISOString().replace('T', ' ').substring(0, 19);
|
|
837
|
+
}, 1000);
|
|
838
|
+
|
|
839
|
+
// Fetch task list and reload grid
|
|
840
|
+
async function fetchTaskList() {
|
|
841
|
+
try {
|
|
842
|
+
const response = await fetch('/api/tasks');
|
|
843
|
+
allTasks = await response.json();
|
|
844
|
+
|
|
845
|
+
// Update filter badge counts
|
|
846
|
+
updateCounts();
|
|
847
|
+
|
|
848
|
+
// Render table based on current filters
|
|
849
|
+
renderGrid();
|
|
850
|
+
} catch (err) {
|
|
851
|
+
console.error("Error fetching tasks:", err);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// Calculate and update dashboard badge counts
|
|
856
|
+
function updateCounts() {
|
|
857
|
+
document.getElementById('count-all').innerText = allTasks.length;
|
|
858
|
+
document.getElementById('count-running').innerText = allTasks.filter(t => ['RUNNING', 'WAITING'].includes(t.status)).length;
|
|
859
|
+
document.getElementById('count-completed').innerText = allTasks.filter(t => t.status === 'COMPLETED').length;
|
|
860
|
+
document.getElementById('count-failed').innerText = allTasks.filter(t => t.status === 'FAILED').length;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// Render rows based on filter state
|
|
864
|
+
function renderGrid() {
|
|
865
|
+
const searchVal = document.getElementById('searchInput').value.toLowerCase();
|
|
866
|
+
const body = document.getElementById('workflowGridBody');
|
|
867
|
+
body.innerHTML = '';
|
|
868
|
+
|
|
869
|
+
const filtered = allTasks.filter(task => {
|
|
870
|
+
// Search filter
|
|
871
|
+
const matchesSearch = task.task_id.toLowerCase().includes(searchVal);
|
|
872
|
+
if (!matchesSearch) return false;
|
|
873
|
+
|
|
874
|
+
// Status tab filter
|
|
875
|
+
if (selectedStatusFilter === 'all') return true;
|
|
876
|
+
if (selectedStatusFilter === 'running') return ['RUNNING', 'WAITING', 'QUEUED'].includes(task.status);
|
|
877
|
+
return task.status.toLowerCase() === selectedStatusFilter;
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
if (filtered.length === 0) {
|
|
881
|
+
body.innerHTML = `<tr><td colspan="5" style="text-align: center; color: var(--text-muted); padding: 30px;">No matching workflows found.</td></tr>`;
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
filtered.forEach(task => {
|
|
886
|
+
const isSelected = task.task_id === currentTaskId ? 'selected' : '';
|
|
887
|
+
const progress = task.progress || 0;
|
|
888
|
+
const formattedTime = task.created_at ? task.created_at.substring(11, 19) : '--:--:--';
|
|
889
|
+
|
|
890
|
+
const row = document.createElement('tr');
|
|
891
|
+
row.className = `workflow-row ${isSelected}`;
|
|
892
|
+
row.onclick = () => selectWorkflow(task.task_id);
|
|
893
|
+
row.innerHTML = `
|
|
894
|
+
<td class="workflow-id-col">${task.task_id}</td>
|
|
895
|
+
<td><span class="workflow-type-badge">${task.task_type}</span></td>
|
|
896
|
+
<td><span class="status-badge status-badge-${task.status.toLowerCase()}">${task.status}</span></td>
|
|
897
|
+
<td style="width: 160px;">
|
|
898
|
+
<div class="progress-indicator" style="margin-bottom: 4px;">
|
|
899
|
+
<span>Progress</span>
|
|
900
|
+
<span>${progress}%</span>
|
|
901
|
+
</div>
|
|
902
|
+
<div class="progress-bar-glow">
|
|
903
|
+
<div class="progress-fill" style="width: ${progress}%"></div>
|
|
904
|
+
</div>
|
|
905
|
+
</td>
|
|
906
|
+
<td style="color: var(--text-secondary); font-family: 'JetBrains Mono', monospace;">${formattedTime}</td>
|
|
907
|
+
`;
|
|
908
|
+
body.appendChild(row);
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// Handle navigation to workflow selection
|
|
913
|
+
async function selectWorkflow(taskId) {
|
|
914
|
+
currentTaskId = taskId;
|
|
915
|
+
|
|
916
|
+
// Re-render table rows to update highlight
|
|
917
|
+
renderGrid();
|
|
918
|
+
|
|
919
|
+
document.getElementById('detailsPlaceholder').style.display = 'none';
|
|
920
|
+
document.getElementById('detailsWorkspace').style.display = 'flex';
|
|
921
|
+
|
|
922
|
+
await refreshWorkflowDetails();
|
|
923
|
+
|
|
924
|
+
// Set details refreshing
|
|
925
|
+
if (pollIntervalId) clearInterval(pollIntervalId);
|
|
926
|
+
pollIntervalId = setInterval(refreshWorkflowDetails, 1500);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// Fetch detailed task state from endpoint
|
|
930
|
+
async function refreshWorkflowDetails() {
|
|
931
|
+
if (!currentTaskId) return;
|
|
932
|
+
|
|
933
|
+
try {
|
|
934
|
+
const response = await fetch(`/api/tasks/${currentTaskId}`);
|
|
935
|
+
if (!response.ok) {
|
|
936
|
+
if (response.status === 404) {
|
|
937
|
+
alert("Task not found");
|
|
938
|
+
clearInterval(pollIntervalId);
|
|
939
|
+
location.reload();
|
|
940
|
+
}
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
const task = await response.json();
|
|
944
|
+
|
|
945
|
+
document.getElementById('detWorkflowId').innerText = task.task_id;
|
|
946
|
+
document.getElementById('detMetaType').innerText = task.task_type;
|
|
947
|
+
|
|
948
|
+
const badge = document.getElementById('detMetaBadge');
|
|
949
|
+
badge.innerText = task.status;
|
|
950
|
+
badge.className = `status-badge status-badge-${task.status.toLowerCase()}`;
|
|
951
|
+
|
|
952
|
+
// Parent Task ID navigation
|
|
953
|
+
const parentCell = document.getElementById('detMetaParent');
|
|
954
|
+
if (task.parent_id) {
|
|
955
|
+
parentCell.innerText = task.parent_id;
|
|
956
|
+
parentCell.className = "meta-value meta-value-mono";
|
|
957
|
+
parentCell.onclick = () => selectWorkflow(task.parent_id);
|
|
958
|
+
} else {
|
|
959
|
+
parentCell.innerText = "None";
|
|
960
|
+
parentCell.className = "meta-value";
|
|
961
|
+
parentCell.onclick = null;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
document.getElementById('detMetaStart').innerText = task.updated_at ? task.updated_at.replace('T', ' ').substring(0, 19) : '--/-- --:--';
|
|
965
|
+
|
|
966
|
+
// Show/hide cancel button
|
|
967
|
+
const cancelBtn = document.getElementById('detCancelBtn');
|
|
968
|
+
if (['QUEUED', 'RUNNING', 'WAITING'].includes(task.status)) {
|
|
969
|
+
cancelBtn.style.display = 'block';
|
|
970
|
+
cancelBtn.onclick = () => cancelWorkflow(task.task_id);
|
|
971
|
+
} else {
|
|
972
|
+
cancelBtn.style.display = 'none';
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// Update raw payload view
|
|
976
|
+
document.getElementById('detPayload').innerText = JSON.stringify(task, null, 2);
|
|
977
|
+
|
|
978
|
+
// Update result report
|
|
979
|
+
const resultTab = document.getElementById('detResult');
|
|
980
|
+
if (task.status === 'COMPLETED' && task.result) {
|
|
981
|
+
resultTab.innerHTML = renderMarkdown(task.result);
|
|
982
|
+
} else if (task.status === 'FAILED' && task.error) {
|
|
983
|
+
resultTab.innerHTML = `<h3 style="color: var(--danger);">Execution Failed</h3><pre class="code-block" style="margin-top: 10px; border-color: rgba(239, 68, 68, 0.4);">${task.error}</pre>`;
|
|
984
|
+
} else if (task.status === 'CANCELLED') {
|
|
985
|
+
resultTab.innerHTML = `<h3 style="color: var(--text-secondary);">Workflow Aborted</h3><p style="margin-top: 8px;">Cancelled by client command. ${task.error || ''}</p>`;
|
|
986
|
+
} else {
|
|
987
|
+
resultTab.innerHTML = `<p style="color: var(--text-muted);">Workflow is still running. Reports are available upon completion.</p>`;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
// Build history event log to show execution milestones
|
|
991
|
+
buildTimelineEvents(task);
|
|
992
|
+
|
|
993
|
+
} catch (err) {
|
|
994
|
+
console.error("Error refreshing task details:", err);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// Render vertical timeline history events list
|
|
999
|
+
function buildTimelineEvents(task) {
|
|
1000
|
+
const timeline = document.getElementById('detTimeline');
|
|
1001
|
+
timeline.innerHTML = '';
|
|
1002
|
+
|
|
1003
|
+
// Event 1: Workflow Started
|
|
1004
|
+
const ev1 = createTimelineNode("WorkflowExecutionStarted", task.updated_at, "Task enqueued inside the AgyQueue task broker. Status transitioned to QUEUED.", "completed");
|
|
1005
|
+
timeline.appendChild(ev1);
|
|
1006
|
+
|
|
1007
|
+
// Event 2: Worker Picked Up (if running or completed)
|
|
1008
|
+
if (task.status !== 'QUEUED') {
|
|
1009
|
+
const ev2 = createTimelineNode("WorkflowTaskStarted", task.updated_at, "Background worker picked up payload and initialized isolation workspace worktree directory.", "completed");
|
|
1010
|
+
timeline.appendChild(ev2);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// Event 3: Workflow Step updates
|
|
1014
|
+
if (task.step && task.step !== "Queued in AgyQueue") {
|
|
1015
|
+
const isFinal = ['COMPLETED', 'FAILED', 'CANCELLED'].includes(task.status);
|
|
1016
|
+
const ev3 = createTimelineNode("WorkflowTaskScheduled", task.updated_at, `Current executing task step log: "${task.step}" (Progress: ${task.progress}%)`, isFinal ? "completed" : "active");
|
|
1017
|
+
timeline.appendChild(ev3);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// Event 4: Terminal execution event
|
|
1021
|
+
if (task.status === 'COMPLETED') {
|
|
1022
|
+
const ev4 = createTimelineNode("WorkflowExecutionCompleted", task.updated_at, "Task finished execution successfully. Results aggregated and saved to persistent store.", "completed");
|
|
1023
|
+
timeline.appendChild(ev4);
|
|
1024
|
+
} else if (task.status === 'FAILED') {
|
|
1025
|
+
const ev4 = createTimelineNode("WorkflowExecutionFailed", task.updated_at, `Execution crashed. Error message: "${task.error || 'Unknown execution error'}"`, "failed");
|
|
1026
|
+
timeline.appendChild(ev4);
|
|
1027
|
+
} else if (task.status === 'CANCELLED') {
|
|
1028
|
+
const ev4 = createTimelineNode("WorkflowExecutionCancelled", task.updated_at, "Workflow execution terminated cleanly via API cancellation signal.", "failed");
|
|
1029
|
+
timeline.appendChild(ev4);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
function createTimelineNode(name, time, description, stateClass) {
|
|
1034
|
+
const node = document.createElement('div');
|
|
1035
|
+
node.className = `timeline-event ${stateClass}`;
|
|
1036
|
+
node.innerHTML = `
|
|
1037
|
+
<div class="event-header">
|
|
1038
|
+
<span class="event-name">${name}</span>
|
|
1039
|
+
<span class="event-time">${time ? time.substring(11, 19) : ''}</span>
|
|
1040
|
+
</div>
|
|
1041
|
+
<div class="event-desc">${description}</div>
|
|
1042
|
+
`;
|
|
1043
|
+
return node;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// Cancel workflow request
|
|
1047
|
+
async function cancelWorkflow(taskId) {
|
|
1048
|
+
if (!confirm(`Abort workflow execution run ${taskId}?`)) return;
|
|
1049
|
+
try {
|
|
1050
|
+
const response = await fetch(`/api/tasks/${taskId}/cancel`, { method: 'POST' });
|
|
1051
|
+
if (response.ok) {
|
|
1052
|
+
await refreshWorkflowDetails();
|
|
1053
|
+
await fetchTaskList();
|
|
1054
|
+
} else {
|
|
1055
|
+
alert("Failed to abort workflow");
|
|
1056
|
+
}
|
|
1057
|
+
} catch (err) {
|
|
1058
|
+
console.error("Error cancelling workflow:", err);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// Form Submit
|
|
1063
|
+
document.getElementById('workflowForm').onsubmit = async (e) => {
|
|
1064
|
+
e.preventDefault();
|
|
1065
|
+
const prompt = document.getElementById('promptInput').value;
|
|
1066
|
+
const task_type = document.getElementById('typeSelect').value;
|
|
1067
|
+
|
|
1068
|
+
try {
|
|
1069
|
+
const response = await fetch('/api/tasks', {
|
|
1070
|
+
method: 'POST',
|
|
1071
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1072
|
+
body: JSON.stringify({ prompt, task_type })
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
if (response.ok) {
|
|
1076
|
+
const task = await response.json();
|
|
1077
|
+
document.getElementById('promptInput').value = '';
|
|
1078
|
+
await fetchTaskList();
|
|
1079
|
+
selectWorkflow(task.task_id);
|
|
1080
|
+
} else {
|
|
1081
|
+
alert("Failed to submit task");
|
|
1082
|
+
}
|
|
1083
|
+
} catch (err) {
|
|
1084
|
+
console.error("Error submitting task:", err);
|
|
1085
|
+
}
|
|
1086
|
+
};
|
|
1087
|
+
|
|
1088
|
+
// Filter by Status Tab
|
|
1089
|
+
function filterStatus(status) {
|
|
1090
|
+
selectedStatusFilter = status;
|
|
1091
|
+
|
|
1092
|
+
const tabs = document.querySelectorAll('.status-tab');
|
|
1093
|
+
tabs.forEach(tab => {
|
|
1094
|
+
if (tab.getAttribute('data-status') === status) {
|
|
1095
|
+
tab.classList.add('active');
|
|
1096
|
+
} else {
|
|
1097
|
+
tab.classList.remove('active');
|
|
1098
|
+
}
|
|
1099
|
+
});
|
|
1100
|
+
|
|
1101
|
+
renderGrid();
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// Search text filter
|
|
1105
|
+
function filterGrid() {
|
|
1106
|
+
renderGrid();
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// Tab selection switching
|
|
1110
|
+
function switchTab(tabId) {
|
|
1111
|
+
const tabs = document.querySelectorAll('.tab-btn');
|
|
1112
|
+
tabs.forEach(btn => {
|
|
1113
|
+
if (btn.innerText.toLowerCase().includes(tabId)) {
|
|
1114
|
+
btn.classList.add('active');
|
|
1115
|
+
} else {
|
|
1116
|
+
btn.classList.remove('active');
|
|
1117
|
+
}
|
|
1118
|
+
});
|
|
1119
|
+
|
|
1120
|
+
const contents = document.querySelectorAll('.tab-content');
|
|
1121
|
+
contents.forEach(content => {
|
|
1122
|
+
if (content.id === `tab-${tabId}`) {
|
|
1123
|
+
content.classList.add('active');
|
|
1124
|
+
} else {
|
|
1125
|
+
content.classList.remove('active');
|
|
1126
|
+
}
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
// Render basic markdown formatting
|
|
1131
|
+
function renderMarkdown(md) {
|
|
1132
|
+
if (!md) return "";
|
|
1133
|
+
return md
|
|
1134
|
+
.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">") // escape HTML
|
|
1135
|
+
.replace(/^# (.*$)/gim, '<h1>$1</h1>')
|
|
1136
|
+
.replace(/^## (.*$)/gim, '<h2>$1</h2>')
|
|
1137
|
+
.replace(/^### (.*$)/gim, '<h3>$1</h3>')
|
|
1138
|
+
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
|
1139
|
+
.replace(/\*(.*?)\*/g, '<em>$1</em>')
|
|
1140
|
+
.replace(/`([^`]+)`/g, '<code>$1</code>')
|
|
1141
|
+
.replace(/- \[(x| )\] (.*$)/gim, (m, check, text) => {
|
|
1142
|
+
const checked = check === 'x' ? 'checked disabled' : 'disabled';
|
|
1143
|
+
return `<div><input type="checkbox" ${checked}> ${text}</div>`;
|
|
1144
|
+
})
|
|
1145
|
+
.replace(/^- (.*$)/gim, '<li>$1</li>')
|
|
1146
|
+
.replace(/\n\n/g, '<p></p>')
|
|
1147
|
+
.replace(/\n/g, '<br>');
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
// Initial trigger
|
|
1151
|
+
fetchTaskList();
|
|
1152
|
+
setInterval(fetchTaskList, 3000); // refresh list every 3s
|
|
1153
|
+
</script>
|
|
1154
|
+
</body>
|
|
1155
|
+
</html>
|