ddapm-test-agent 1.36.0__py3-none-any.whl → 1.37.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.
@@ -0,0 +1,640 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ <div class="tracer-flares-page">
5
+ <div class="page-header">
6
+ <h2>Tracer Flares</h2>
7
+ <p>View tracer flares sent to the test agent</p>
8
+ </div>
9
+
10
+ <!-- Flare Generator Controls -->
11
+ <div class="flare-generator">
12
+ {% if active_flare %}
13
+ <div class="active-flare-status">
14
+ <div class="status-header">
15
+ <h3>🟢 Flare Collection Active</h3>
16
+ <div class="flare-details">
17
+ <span>Case ID: {{ active_flare.case_id }}</span>
18
+ <span>UUID: {{ active_flare.uuid[:8] }}...</span>
19
+ <span id="duration">Duration: <span id="duration-text">0s</span></span>
20
+ </div>
21
+ </div>
22
+ <div class="flare-actions">
23
+ <button onclick="stopFlare()" class="btn btn-danger">Stop & Upload Flare</button>
24
+ </div>
25
+ </div>
26
+ {% else %}
27
+ <div class="flare-generator-controls">
28
+ <h3>Tracer Flare Generator</h3>
29
+ <p>Start collecting debug logs and generate a tracer flare when ready.</p>
30
+ <div class="generator-actions">
31
+ <button onclick="startFlare()" class="btn btn-primary">Start Flare Collection</button>
32
+ </div>
33
+ </div>
34
+ {% endif %}
35
+ </div>
36
+
37
+ <div class="stats-summary">
38
+ <div class="stat-card">
39
+ <h3>Total Flares</h3>
40
+ <div class="stat-number">{{ total_flares }}</div>
41
+ </div>
42
+ {% if active_flare %}
43
+ <div class="stat-card active">
44
+ <h3>Collection Status</h3>
45
+ <div class="stat-status">ACTIVE</div>
46
+ </div>
47
+ {% endif %}
48
+ <div class="stat-card">
49
+ <h3>Auto-Refresh</h3>
50
+ <div class="auto-refresh-controls">
51
+ <div class="stat-status" id="refresh-status">ON</div>
52
+ <button onclick="toggleAutoRefresh()" class="btn btn-small" id="refresh-toggle-btn">Pause</button>
53
+ </div>
54
+ </div>
55
+ </div>
56
+
57
+ {% if tracer_flares %}
58
+ <div class="flares-container">
59
+ {% for flare in tracer_flares %}
60
+ <div class="flare-card">
61
+ <div class="flare-header">
62
+ <h4>Flare {{ loop.index }}</h4>
63
+ <div class="flare-meta">
64
+ <span class="case-id">Case ID: {{ flare.case_id or 'N/A' }}</span>
65
+ <span class="source">Source: {{ flare.source or 'N/A' }}</span>
66
+ </div>
67
+ </div>
68
+
69
+ <div class="flare-details">
70
+ <div class="detail-row">
71
+ <strong>Email:</strong> {{ flare.email or 'N/A' }}
72
+ </div>
73
+ <div class="detail-row">
74
+ <strong>Hostname:</strong> {{ flare.hostname or 'N/A' }}
75
+ </div>
76
+ <div class="detail-row">
77
+ <strong>UUID:</strong> {{ flare.uuid or 'N/A' }}
78
+ </div>
79
+ <div class="detail-row">
80
+ <strong>Session Token:</strong> {{ flare.session_token or 'default' }}
81
+ </div>
82
+ {% if flare.flare_file %}
83
+ <div class="detail-row">
84
+ <strong>Flare File:</strong>
85
+ <span class="file-info">{{ (flare.flare_file|length / 1024)|round(2) }} KB (base64 encoded)</span>
86
+ <button class="btn btn-primary" onclick="downloadTracerFlare('{{ loop.index0 }}', '{{ flare.case_id or 'unknown' }}')">Download ZIP</button>
87
+ </div>
88
+ {% endif %}
89
+ </div>
90
+
91
+ {% if flare.error %}
92
+ <div class="flare-error">
93
+ <strong>Error:</strong> {{ flare.error }}
94
+ </div>
95
+ {% endif %}
96
+ </div>
97
+ {% endfor %}
98
+ </div>
99
+ {% else %}
100
+ <div class="empty-state">
101
+ <h3>No Tracer Flares</h3>
102
+ <p>No tracer flares have been received yet.</p>
103
+ <div class="help-text">
104
+ <h4>To generate a tracer flare:</h4>
105
+ <ol>
106
+ <li>Set up Remote Config with AGENT_TASK configuration</li>
107
+ <li>The tracer will send the flare to <code>/tracer_flare/v1</code></li>
108
+ <li>Flares will appear here once received</li>
109
+ </ol>
110
+ </div>
111
+ </div>
112
+ {% endif %}
113
+ </div>
114
+
115
+ <style>
116
+ .tracer-flares-page {
117
+ padding: 20px;
118
+ }
119
+
120
+ .stats-summary {
121
+ display: flex;
122
+ gap: 20px;
123
+ margin-bottom: 30px;
124
+ }
125
+
126
+ .stat-card {
127
+ background: #f8f9fa;
128
+ border: 1px solid #dee2e6;
129
+ border-radius: 8px;
130
+ padding: 20px;
131
+ text-align: center;
132
+ min-width: 150px;
133
+ }
134
+
135
+ .stat-card h3 {
136
+ margin: 0 0 10px 0;
137
+ color: #495057;
138
+ font-size: 14px;
139
+ font-weight: 600;
140
+ }
141
+
142
+ .stat-number {
143
+ font-size: 32px;
144
+ font-weight: bold;
145
+ color: #007bff;
146
+ }
147
+
148
+ .flares-container {
149
+ display: flex;
150
+ flex-direction: column;
151
+ gap: 20px;
152
+ }
153
+
154
+ .flare-card {
155
+ background: #ffffff;
156
+ border: 1px solid #dee2e6;
157
+ border-radius: 8px;
158
+ padding: 20px;
159
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
160
+ }
161
+
162
+ .flare-header {
163
+ display: flex;
164
+ justify-content: space-between;
165
+ align-items: center;
166
+ margin-bottom: 15px;
167
+ padding-bottom: 10px;
168
+ border-bottom: 1px solid #dee2e6;
169
+ }
170
+
171
+ .flare-header h4 {
172
+ margin: 0;
173
+ color: #495057;
174
+ }
175
+
176
+ .flare-meta {
177
+ display: flex;
178
+ gap: 15px;
179
+ font-size: 12px;
180
+ color: #6c757d;
181
+ }
182
+
183
+ .case-id, .source {
184
+ background: #e9ecef;
185
+ padding: 4px 8px;
186
+ border-radius: 4px;
187
+ }
188
+
189
+ .flare-details {
190
+ display: grid;
191
+ grid-template-columns: 1fr 1fr;
192
+ gap: 10px;
193
+ margin-bottom: 15px;
194
+ }
195
+
196
+ .detail-row {
197
+ padding: 8px 0;
198
+ border-bottom: 1px solid #f8f9fa;
199
+ }
200
+
201
+ .detail-row strong {
202
+ color: #495057;
203
+ margin-right: 8px;
204
+ }
205
+
206
+ .file-info {
207
+ color: #28a745;
208
+ font-family: monospace;
209
+ }
210
+
211
+ .flare-error {
212
+ background: #f8d7da;
213
+ border: 1px solid #f5c6cb;
214
+ border-radius: 4px;
215
+ padding: 10px;
216
+ color: #721c24;
217
+ }
218
+
219
+ .empty-state {
220
+ text-align: center;
221
+ padding: 60px 20px;
222
+ background: #f8f9fa;
223
+ border: 1px solid #dee2e6;
224
+ border-radius: 8px;
225
+ }
226
+
227
+ .empty-state h3 {
228
+ margin: 0 0 10px 0;
229
+ color: #6c757d;
230
+ }
231
+
232
+ .empty-state p {
233
+ color: #6c757d;
234
+ margin-bottom: 30px;
235
+ }
236
+
237
+ .help-text {
238
+ text-align: left;
239
+ max-width: 500px;
240
+ margin: 0 auto;
241
+ background: #ffffff;
242
+ padding: 20px;
243
+ border-radius: 4px;
244
+ border: 1px solid #dee2e6;
245
+ }
246
+
247
+ .help-text h4 {
248
+ margin-top: 0;
249
+ color: #495057;
250
+ }
251
+
252
+ .help-text ol {
253
+ margin: 0;
254
+ padding-left: 20px;
255
+ }
256
+
257
+ .help-text li {
258
+ margin-bottom: 8px;
259
+ color: #495057;
260
+ }
261
+
262
+ .help-text code {
263
+ background: #f8f9fa;
264
+ padding: 2px 4px;
265
+ border-radius: 3px;
266
+ font-family: monospace;
267
+ color: #e83e8c;
268
+ }
269
+
270
+ /* Flare Generator Styles */
271
+ .flare-generator {
272
+ margin-bottom: 30px;
273
+ }
274
+
275
+ .flare-generator-controls {
276
+ background: #f8f9fa;
277
+ border: 1px solid #dee2e6;
278
+ border-radius: 8px;
279
+ padding: 25px;
280
+ text-align: center;
281
+ }
282
+
283
+ .flare-generator-controls h3 {
284
+ margin: 0 0 10px 0;
285
+ color: #495057;
286
+ }
287
+
288
+ .flare-generator-controls p {
289
+ color: #6c757d;
290
+ margin: 0 0 20px 0;
291
+ }
292
+
293
+ .generator-actions .btn {
294
+ padding: 12px 24px;
295
+ font-size: 16px;
296
+ font-weight: 600;
297
+ }
298
+
299
+ .active-flare-status {
300
+ background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%);
301
+ border: 2px solid #28a745;
302
+ border-radius: 8px;
303
+ padding: 20px;
304
+ }
305
+
306
+ .status-header {
307
+ display: flex;
308
+ justify-content: space-between;
309
+ align-items: center;
310
+ margin-bottom: 15px;
311
+ }
312
+
313
+ .status-header h3 {
314
+ margin: 0;
315
+ color: #155724;
316
+ font-size: 18px;
317
+ }
318
+
319
+ .flare-details {
320
+ display: flex;
321
+ gap: 15px;
322
+ font-size: 13px;
323
+ color: #155724;
324
+ }
325
+
326
+ .flare-details span {
327
+ background: rgba(21, 87, 36, 0.1);
328
+ padding: 4px 8px;
329
+ border-radius: 4px;
330
+ font-family: monospace;
331
+ }
332
+
333
+ .flare-actions {
334
+ text-align: center;
335
+ }
336
+
337
+ .flare-actions .btn {
338
+ padding: 10px 20px;
339
+ font-size: 14px;
340
+ font-weight: 600;
341
+ }
342
+
343
+ .btn {
344
+ border: none;
345
+ border-radius: 6px;
346
+ cursor: pointer;
347
+ text-decoration: none;
348
+ display: inline-block;
349
+ transition: all 0.2s ease;
350
+ }
351
+
352
+ .btn-primary {
353
+ background: #007bff;
354
+ color: white;
355
+ }
356
+
357
+ .btn-primary:hover {
358
+ background: #0056b3;
359
+ transform: translateY(-1px);
360
+ }
361
+
362
+ .btn-danger {
363
+ background: #dc3545;
364
+ color: white;
365
+ }
366
+
367
+ .btn-danger:hover {
368
+ background: #c82333;
369
+ transform: translateY(-1px);
370
+ }
371
+
372
+ .stat-card.active {
373
+ background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%);
374
+ border: 1px solid #28a745;
375
+ }
376
+
377
+ .stat-status {
378
+ font-size: 20px;
379
+ font-weight: bold;
380
+ color: #28a745;
381
+ }
382
+
383
+ .auto-refresh-controls {
384
+ display: flex;
385
+ flex-direction: column;
386
+ align-items: center;
387
+ gap: 8px;
388
+ }
389
+
390
+ .auto-refresh-controls .stat-status {
391
+ font-size: 16px;
392
+ margin-bottom: 0;
393
+ }
394
+
395
+ .btn-small {
396
+ padding: 4px 8px;
397
+ font-size: 12px;
398
+ border-radius: 4px;
399
+ }
400
+
401
+ .status-messages {
402
+ position: fixed;
403
+ top: 20px;
404
+ right: 20px;
405
+ z-index: 1000;
406
+ }
407
+
408
+ .status-message {
409
+ padding: 12px 20px;
410
+ margin-bottom: 10px;
411
+ border-radius: 6px;
412
+ max-width: 350px;
413
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
414
+ }
415
+
416
+ .status-success {
417
+ background: #d4edda;
418
+ color: #155724;
419
+ border: 1px solid #c3e6cb;
420
+ }
421
+
422
+ .status-error {
423
+ background: #f8d7da;
424
+ color: #721c24;
425
+ border: 1px solid #f5c6cb;
426
+ }
427
+ </style>
428
+
429
+ <script>
430
+ let durationTimer = null;
431
+ let startTime = null;
432
+
433
+ // Status message functions
434
+ function showStatus(message, isError = false) {
435
+ const statusContainer = document.getElementById('status-messages') || createStatusContainer();
436
+ const statusDiv = document.createElement('div');
437
+ statusDiv.className = `status-message ${isError ? 'status-error' : 'status-success'}`;
438
+ statusDiv.textContent = message;
439
+
440
+ statusContainer.appendChild(statusDiv);
441
+
442
+ // Auto-remove after 5 seconds
443
+ setTimeout(() => {
444
+ if (statusContainer.contains(statusDiv)) {
445
+ statusContainer.removeChild(statusDiv);
446
+ }
447
+ }, 5000);
448
+ }
449
+
450
+ function createStatusContainer() {
451
+ const container = document.createElement('div');
452
+ container.id = 'status-messages';
453
+ container.className = 'status-messages';
454
+ document.body.appendChild(container);
455
+ return container;
456
+ }
457
+
458
+ async function startFlare() {
459
+ try {
460
+ const token = getCurrentSessionToken();
461
+ const response = await fetch('/tracerflares/start', {
462
+ method: 'POST',
463
+ headers: {
464
+ 'Content-Type': 'application/x-www-form-urlencoded',
465
+ },
466
+ body: `token=${encodeURIComponent(token || '')}`
467
+ });
468
+
469
+ const result = await response.json();
470
+ if (response.ok) {
471
+ showStatus(result.message);
472
+ // Refresh page to show active flare status
473
+ setTimeout(() => {
474
+ window.location.reload();
475
+ }, 1000);
476
+ } else {
477
+ showStatus(result.error || 'Error starting flare collection', true);
478
+ }
479
+ } catch (error) {
480
+ showStatus('Network error: ' + error.message, true);
481
+ }
482
+ }
483
+
484
+ async function stopFlare() {
485
+ if (!confirm('Are you sure you want to stop the flare collection and upload the flare?')) {
486
+ return;
487
+ }
488
+
489
+ try {
490
+ const token = getCurrentSessionToken();
491
+ const response = await fetch('/tracerflares/stop', {
492
+ method: 'POST',
493
+ headers: {
494
+ 'Content-Type': 'application/x-www-form-urlencoded',
495
+ },
496
+ body: `token=${encodeURIComponent(token || '')}`
497
+ });
498
+
499
+ const result = await response.json();
500
+ if (response.ok) {
501
+ showStatus(result.message);
502
+ // Refresh page to hide active flare status and potentially show new flare
503
+ // Give more time for the flare to be processed and stored
504
+ setTimeout(() => {
505
+ window.location.reload();
506
+ }, 3000);
507
+ } else {
508
+ showStatus(result.error || 'Error stopping flare collection', true);
509
+ }
510
+ } catch (error) {
511
+ showStatus('Network error: ' + error.message, true);
512
+ }
513
+ }
514
+
515
+ function getCurrentSessionToken() {
516
+ const urlParams = new URLSearchParams(window.location.search);
517
+ return urlParams.get('token');
518
+ }
519
+
520
+ function updateDuration() {
521
+ if (startTime) {
522
+ const now = Date.now();
523
+ const elapsed = Math.floor((now - startTime) / 1000);
524
+ const minutes = Math.floor(elapsed / 60);
525
+ const seconds = elapsed % 60;
526
+
527
+ const durationText = minutes > 0
528
+ ? `${minutes}m ${seconds}s`
529
+ : `${seconds}s`;
530
+
531
+ const durationElement = document.getElementById('duration-text');
532
+ if (durationElement) {
533
+ durationElement.textContent = durationText;
534
+ }
535
+ }
536
+ }
537
+
538
+ // Auto-refresh functionality
539
+ let autoRefreshInterval = null;
540
+
541
+ function startAutoRefresh() {
542
+ // Refresh the page every 10 seconds to show new flares
543
+ autoRefreshInterval = setInterval(() => {
544
+ console.log('Auto-refreshing tracer flares page...');
545
+ window.location.reload();
546
+ }, 10000); // 10 seconds
547
+ }
548
+
549
+ function stopAutoRefresh() {
550
+ if (autoRefreshInterval) {
551
+ clearInterval(autoRefreshInterval);
552
+ autoRefreshInterval = null;
553
+ }
554
+ }
555
+
556
+ function toggleAutoRefresh() {
557
+ if (autoRefreshInterval) {
558
+ stopAutoRefresh();
559
+ document.getElementById('refresh-status').textContent = 'OFF';
560
+ document.getElementById('refresh-toggle-btn').textContent = 'Resume';
561
+ console.log('Auto-refresh paused');
562
+ } else {
563
+ startAutoRefresh();
564
+ document.getElementById('refresh-status').textContent = 'ON';
565
+ document.getElementById('refresh-toggle-btn').textContent = 'Pause';
566
+ console.log('Auto-refresh resumed');
567
+ }
568
+ }
569
+
570
+ // Initialize duration timer if there's an active flare
571
+ document.addEventListener('DOMContentLoaded', function() {
572
+ const durationElement = document.getElementById('duration-text');
573
+ if (durationElement) {
574
+ // Calculate start time from server data (assuming active_flare.start_time is available)
575
+ {% if active_flare %}
576
+ startTime = {{ active_flare.start_time * 1000 }};
577
+ updateDuration();
578
+ durationTimer = setInterval(updateDuration, 1000);
579
+ {% endif %}
580
+ }
581
+
582
+ // Start auto-refresh when page loads
583
+ startAutoRefresh();
584
+
585
+ // Stop auto-refresh when user is about to leave page
586
+ window.addEventListener('beforeunload', stopAutoRefresh);
587
+ });
588
+
589
+ function downloadTracerFlare(flareIndex, caseId) {
590
+ // Find the flare data from the page DOM
591
+ const flareCards = document.querySelectorAll('.flare-card');
592
+ if (flareIndex >= flareCards.length) {
593
+ showStatus('Flare not found', true);
594
+ return;
595
+ }
596
+
597
+ const flareCard = flareCards[flareIndex];
598
+ const fileInfoSpan = flareCard.querySelector('.file-info');
599
+ if (!fileInfoSpan) {
600
+ showStatus('No flare file found', true);
601
+ return;
602
+ }
603
+
604
+ // Get the flare data - we need to make a request to get the full base64 data
605
+ // since only a preview is shown in the template
606
+ console.log(`DEBUG: Downloading flare with case_id='${caseId}'`);
607
+
608
+ fetch(`/tracerflares/download?case_id=${encodeURIComponent(caseId)}`, {
609
+ method: 'GET'
610
+ })
611
+ .then(response => {
612
+ if (!response.ok) {
613
+ return response.json().then(errorData => {
614
+ throw new Error(`HTTP ${response.status}: ${errorData.error || response.statusText}`);
615
+ }).catch(() => {
616
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
617
+ });
618
+ }
619
+ return response.blob();
620
+ })
621
+ .then(blob => {
622
+ // Create download link
623
+ const url = URL.createObjectURL(blob);
624
+ const link = document.createElement('a');
625
+ link.href = url;
626
+ link.download = `tracer_flare_${caseId}_${Date.now()}.zip`;
627
+ document.body.appendChild(link);
628
+ link.click();
629
+ document.body.removeChild(link);
630
+ URL.revokeObjectURL(url);
631
+ showStatus('Flare downloaded successfully');
632
+ })
633
+ .catch(error => {
634
+ console.error('Download error:', error);
635
+ showStatus('Error downloading flare: ' + error.message, true);
636
+ });
637
+ }
638
+
639
+ </script>
640
+ {% endblock %}
@@ -0,0 +1,24 @@
1
+ {% extends "base.html" %}
2
+ {% from "macros.html" import page_layout, empty_state, action_button %}
3
+
4
+ {% block content %}
5
+ {% call page_layout("Traces") %}
6
+ {% if traces %}
7
+ <div class="traces-list">
8
+ {% for trace_id, spans in traces.items() %}
9
+ <div class="trace-item">
10
+ <div class="trace-header">
11
+ <h3>Trace {{ trace_id }}</h3>
12
+ <span class="span-count">{{ spans|length }} spans</span>
13
+ </div>
14
+ <div class="trace-actions">
15
+ {{ action_button("/traces/" + trace_id, "View Details") }}
16
+ </div>
17
+ </div>
18
+ {% endfor %}
19
+ </div>
20
+ {% else %}
21
+ {{ empty_state("No traces available") }}
22
+ {% endif %}
23
+ {% endcall %}
24
+ {% endblock %}