x-ipe 1.0.24__py3-none-any.whl → 1.0.25__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.
- x_ipe/app.py +25 -3
- x_ipe/handlers/terminal_handlers.py +6 -0
- x_ipe/handlers/voice_handlers.py +5 -0
- x_ipe/resources/copilot-instructions.md +19 -6
- x_ipe/resources/skills/lesson-learned/SKILL.md +208 -0
- x_ipe/resources/skills/lesson-learned/references/examples.md +238 -0
- x_ipe/resources/skills/project-quality-board-management/SKILL.md +135 -298
- x_ipe/resources/skills/project-quality-board-management/references/evaluation-principles.md +213 -0
- x_ipe/resources/skills/project-quality-board-management/references/evaluation-procedures.md +214 -0
- x_ipe/resources/skills/project-quality-board-management/templates/quality-report.md +70 -18
- x_ipe/resources/skills/task-execution-guideline/SKILL.md +2 -2
- x_ipe/resources/skills/task-execution-guideline/templates/task-record.yaml +1 -1
- x_ipe/resources/skills/task-type-code-implementation/SKILL.md +72 -270
- x_ipe/resources/skills/task-type-code-implementation/references/implementation-guidelines.md +432 -0
- x_ipe/resources/skills/task-type-code-refactor-v2/SKILL.md +127 -353
- x_ipe/resources/skills/task-type-code-refactor-v2/references/refactoring-techniques.md +373 -0
- x_ipe/resources/skills/task-type-feature-breakdown/SKILL.md +31 -243
- x_ipe/resources/skills/task-type-feature-breakdown/references/breakdown-guidelines.md +330 -0
- x_ipe/resources/skills/task-type-feature-refinement/SKILL.md +27 -180
- x_ipe/resources/skills/task-type-feature-refinement/references/specification-writing-guide.md +267 -0
- x_ipe/resources/skills/task-type-idea-mockup/SKILL.md +38 -276
- x_ipe/resources/skills/task-type-idea-mockup/references/mockup-guidelines.md +299 -0
- x_ipe/resources/skills/task-type-idea-to-architecture/SKILL.md +20 -218
- x_ipe/resources/skills/task-type-idea-to-architecture/references/architecture-patterns.md +342 -0
- x_ipe/resources/skills/task-type-ideation/SKILL.md +10 -266
- x_ipe/resources/skills/task-type-ideation/references/folder-naming-guide.md +55 -0
- x_ipe/resources/skills/task-type-ideation/references/tool-usage-guide.md +236 -0
- x_ipe/resources/skills/task-type-ideation-v2/SKILL.md +488 -0
- x_ipe/resources/skills/task-type-ideation-v2/references/examples.md +377 -0
- x_ipe/resources/skills/task-type-ideation-v2/references/folder-naming-guide.md +74 -0
- x_ipe/resources/skills/task-type-ideation-v2/references/tool-usage-guide.md +145 -0
- x_ipe/resources/skills/task-type-ideation-v2/references/visualization-guide.md +160 -0
- x_ipe/resources/skills/task-type-ideation-v2/templates/idea-summary.md +86 -0
- x_ipe/resources/skills/task-type-refactoring-analysis/SKILL.md +83 -145
- x_ipe/resources/skills/task-type-refactoring-analysis/references/output-schema.md +172 -0
- x_ipe/resources/skills/task-type-technical-design/SKILL.md +28 -214
- x_ipe/resources/skills/task-type-technical-design/references/design-templates.md +422 -0
- x_ipe/resources/skills/task-type-test-generation/SKILL.md +47 -332
- x_ipe/resources/skills/task-type-test-generation/references/test-patterns.md +368 -0
- x_ipe/resources/skills/tool-tracing-creator/SKILL.md +312 -0
- x_ipe/resources/skills/tool-tracing-creator/references/examples.md +324 -0
- x_ipe/resources/skills/tool-tracing-instrumentation/SKILL.md +373 -0
- x_ipe/resources/skills/tool-tracing-instrumentation/references/examples.md +264 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/SKILL.md +486 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/10. example-gate-conditions.md +73 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/11. reference-quality-standards.md +127 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/2. reference-section-order.md +127 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/3. example-step-based-code-review.md +84 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/4. example-step-based-feature-implementation.md +113 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/5. example-function-based-validation.md +73 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/6. example-function-based-analysis.md +94 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/7. example-task-io-code-implementation.md +36 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/8. example-structured-summary.md +43 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/9. example-dor-dod.md +77 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/examples.md +429 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/skill-general-guidelines-v2.md +611 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/skill-meta-x-ipe-meta.md +153 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/skill-meta-x-ipe-task-based.md +324 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/skill-meta-x-ipe-task-category.md +109 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/skill-meta-x-ipe-tool.md +205 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/x-ipe-meta.md +334 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/x-ipe-task-based.md +279 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/x-ipe-tool.md +175 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/x-ipe-workflow-orchestration.md +329 -0
- x_ipe/resources/skills/x-ipe-task-based-ideation/SKILL.md +487 -0
- x_ipe/resources/skills/x-ipe-task-based-ideation/references/examples.md +377 -0
- x_ipe/resources/skills/x-ipe-task-based-ideation/references/folder-naming-guide.md +74 -0
- x_ipe/resources/skills/x-ipe-task-based-ideation/references/tool-usage-guide.md +145 -0
- x_ipe/resources/skills/x-ipe-task-based-ideation/references/visualization-guide.md +160 -0
- x_ipe/resources/skills/x-ipe-task-based-ideation/templates/idea-summary.md +86 -0
- x_ipe/routes/__init__.py +2 -0
- x_ipe/routes/ideas_routes.py +17 -0
- x_ipe/routes/kb_routes.py +80 -0
- x_ipe/routes/main_routes.py +18 -0
- x_ipe/routes/project_routes.py +7 -0
- x_ipe/routes/proxy_routes.py +2 -0
- x_ipe/routes/quality_evaluation_routes.py +193 -0
- x_ipe/routes/settings_routes.py +6 -0
- x_ipe/routes/tools_routes.py +6 -0
- x_ipe/routes/tracing_routes.py +232 -0
- x_ipe/routes/uiux_feedback_routes.py +30 -0
- x_ipe/services/__init__.py +5 -0
- x_ipe/services/config_service.py +6 -0
- x_ipe/services/file_service.py +20 -0
- x_ipe/services/homepage_service.py +160 -0
- x_ipe/services/ideas_service.py +19 -0
- x_ipe/services/kb_service.py +378 -0
- x_ipe/services/proxy_service.py +4 -0
- x_ipe/services/settings_service.py +13 -0
- x_ipe/services/skills_service.py +4 -0
- x_ipe/services/terminal_service.py +24 -0
- x_ipe/services/themes_service.py +4 -0
- x_ipe/services/tools_config_service.py +4 -0
- x_ipe/services/tracing_service.py +333 -0
- x_ipe/services/uiux_feedback_service.py +32 -0
- x_ipe/services/voice_input_service_v2.py +11 -0
- x_ipe/static/css/base.css +7 -0
- x_ipe/static/css/homepage-infinity.css +330 -0
- x_ipe/static/css/kb-core.css +301 -0
- x_ipe/static/css/quality-evaluation.css +345 -0
- x_ipe/static/css/sidebar.css +14 -4
- x_ipe/static/css/terminal.css +1 -0
- x_ipe/static/css/tracing-dashboard.css +796 -0
- x_ipe/static/css/workplace.css +20 -0
- x_ipe/static/img/homepage-infinity-loop.png +0 -0
- x_ipe/static/js/features/homepage-infinity.js +314 -0
- x_ipe/static/js/features/kb-core.js +371 -0
- x_ipe/static/js/features/quality-evaluation.js +387 -0
- x_ipe/static/js/features/sidebar.js +255 -12
- x_ipe/static/js/features/tracing-dashboard.js +855 -0
- x_ipe/static/js/features/tracing-graph.js +1031 -0
- x_ipe/static/js/features/tree-search.js +6 -2
- x_ipe/static/js/features/workplace.js +200 -6
- x_ipe/static/js/init.js +76 -0
- x_ipe/static/js/uiux-feedback.js +18 -2
- x_ipe/templates/base.html +19 -0
- x_ipe/templates/index.html +7 -1
- x_ipe/templates/knowledge-base.html +110 -0
- x_ipe/templates/workplace.html +4 -0
- x_ipe/tracing/__init__.py +37 -0
- x_ipe/tracing/buffer.py +135 -0
- x_ipe/tracing/context.py +125 -0
- x_ipe/tracing/decorator.py +288 -0
- x_ipe/tracing/middleware.py +197 -0
- x_ipe/tracing/parser.py +235 -0
- x_ipe/tracing/redactor.py +111 -0
- x_ipe/tracing/writer.py +122 -0
- {x_ipe-1.0.24.dist-info → x_ipe-1.0.25.dist-info}/METADATA +2 -2
- {x_ipe-1.0.24.dist-info → x_ipe-1.0.25.dist-info}/RECORD +132 -62
- x_ipe/resources/skills/x-ipe-skill-creator/SKILL.md +0 -329
- x_ipe/resources/skills/x-ipe-skill-creator/references/output-patterns.md +0 -169
- x_ipe/resources/skills/x-ipe-skill-creator/references/skill-structure.md +0 -162
- x_ipe/resources/skills/x-ipe-skill-creator/references/workflows.md +0 -110
- x_ipe/resources/skills/x-ipe-skill-creator/templates/references/examples.md +0 -113
- x_ipe/resources/skills/x-ipe-skill-creator/templates/skill-category-skill.md +0 -296
- x_ipe/resources/skills/x-ipe-skill-creator/templates/task-type-skill.md +0 -269
- {x_ipe-1.0.24.dist-info → x_ipe-1.0.25.dist-info}/WHEEL +0 -0
- {x_ipe-1.0.24.dist-info → x_ipe-1.0.25.dist-info}/entry_points.txt +0 -0
- {x_ipe-1.0.24.dist-info → x_ipe-1.0.25.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,855 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FEATURE-023-B: Tracing Dashboard UI
|
|
3
|
+
* Matches mockup tracing-dashboard-v4.html
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// =============================================================================
|
|
7
|
+
// TracingDashboard - Main Dashboard Component
|
|
8
|
+
// =============================================================================
|
|
9
|
+
|
|
10
|
+
class TracingDashboard {
|
|
11
|
+
constructor(container) {
|
|
12
|
+
this.container = container;
|
|
13
|
+
this.timerInterval = null;
|
|
14
|
+
this.pollInterval = null;
|
|
15
|
+
this.selectedTraceId = null;
|
|
16
|
+
this.stopAt = null;
|
|
17
|
+
this.config = {};
|
|
18
|
+
this.currentFilter = 'all';
|
|
19
|
+
this.searchQuery = '';
|
|
20
|
+
this.traces = [];
|
|
21
|
+
this.graphView = null; // FEATURE-023-C: DAG visualization
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// -------------------------------------------------------------------------
|
|
25
|
+
// Lifecycle
|
|
26
|
+
// -------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
async init() {
|
|
29
|
+
this.render();
|
|
30
|
+
this.bindEvents();
|
|
31
|
+
await this.fetchStatus();
|
|
32
|
+
await this.refreshTraceList();
|
|
33
|
+
this.startPolling();
|
|
34
|
+
this.initGraphView(); // FEATURE-023-C: Initialize graph view
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// FEATURE-023-C: Initialize graph view
|
|
38
|
+
initGraphView() {
|
|
39
|
+
const graphContainer = this.container.querySelector('.trace-graph-container');
|
|
40
|
+
if (graphContainer && typeof TracingGraphView !== 'undefined') {
|
|
41
|
+
this.graphView = new TracingGraphView(graphContainer);
|
|
42
|
+
this.graphView.showEmpty();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
destroy() {
|
|
47
|
+
this.stopCountdown();
|
|
48
|
+
this.stopPolling();
|
|
49
|
+
if (this.graphView) {
|
|
50
|
+
this.graphView.destroy();
|
|
51
|
+
this.graphView = null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// -------------------------------------------------------------------------
|
|
56
|
+
// API Methods
|
|
57
|
+
// -------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
async fetchStatus() {
|
|
60
|
+
try {
|
|
61
|
+
const response = await fetch('/api/tracing/status');
|
|
62
|
+
if (!response.ok) throw new Error('Failed to fetch status');
|
|
63
|
+
|
|
64
|
+
this.config = await response.json();
|
|
65
|
+
this.updateUIFromStatus();
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error('Error fetching tracing status:', error);
|
|
68
|
+
this.showToast('Failed to load tracing status', 'error');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async startTracing(durationMinutes) {
|
|
73
|
+
try {
|
|
74
|
+
const response = await fetch('/api/tracing/start', {
|
|
75
|
+
method: 'POST',
|
|
76
|
+
headers: { 'Content-Type': 'application/json' },
|
|
77
|
+
body: JSON.stringify({ duration_minutes: durationMinutes })
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (!response.ok) throw new Error('Failed to start tracing');
|
|
81
|
+
|
|
82
|
+
const data = await response.json();
|
|
83
|
+
this.stopAt = new Date(data.stop_at);
|
|
84
|
+
this.startCountdown();
|
|
85
|
+
this.updateDurationButtons(durationMinutes);
|
|
86
|
+
this.showToast(`Tracing started for ${durationMinutes} minutes`, 'success');
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error('Error starting tracing:', error);
|
|
89
|
+
this.showToast('Failed to start tracing', 'error');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async stopTracing() {
|
|
94
|
+
try {
|
|
95
|
+
const response = await fetch('/api/tracing/stop', {
|
|
96
|
+
method: 'POST'
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
if (!response.ok) throw new Error('Failed to stop tracing');
|
|
100
|
+
|
|
101
|
+
this.stopAt = null;
|
|
102
|
+
this.stopCountdown();
|
|
103
|
+
this.updateDurationButtons(null);
|
|
104
|
+
this.updateTimerDisplay();
|
|
105
|
+
this.showToast('Tracing stopped', 'success');
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error('Error stopping tracing:', error);
|
|
108
|
+
this.showToast('Failed to stop tracing', 'error');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async refreshTraceList() {
|
|
113
|
+
try {
|
|
114
|
+
const response = await fetch('/api/tracing/logs');
|
|
115
|
+
if (!response.ok) throw new Error('Failed to fetch trace logs');
|
|
116
|
+
|
|
117
|
+
this.traces = await response.json();
|
|
118
|
+
this.renderTraceList();
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.error('Error fetching trace logs:', error);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// -------------------------------------------------------------------------
|
|
125
|
+
// Rendering
|
|
126
|
+
// -------------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
render() {
|
|
129
|
+
this.container.innerHTML = `
|
|
130
|
+
<div class="tracing-dashboard">
|
|
131
|
+
<!-- Header -->
|
|
132
|
+
<div class="tracing-header">
|
|
133
|
+
<div class="tracing-header-left">
|
|
134
|
+
<h2>
|
|
135
|
+
<i class="bi bi-graph-up"></i>
|
|
136
|
+
Application Tracing
|
|
137
|
+
</h2>
|
|
138
|
+
</div>
|
|
139
|
+
<div class="tracing-header-controls">
|
|
140
|
+
<div class="tracing-control">
|
|
141
|
+
<!-- Countdown Timer (left side like mockup) -->
|
|
142
|
+
<div class="countdown-container inactive">
|
|
143
|
+
<svg class="countdown-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
144
|
+
<circle cx="12" cy="12" r="10"/>
|
|
145
|
+
<path d="M12 6v6l4 2"/>
|
|
146
|
+
</svg>
|
|
147
|
+
<span class="countdown-text">--:--</span>
|
|
148
|
+
<span class="countdown-label">Inactive</span>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
<button class="stop-btn hidden">Stop</button>
|
|
152
|
+
|
|
153
|
+
<span class="tracing-label">Start Tracing</span>
|
|
154
|
+
<div class="duration-toggle">
|
|
155
|
+
<button class="duration-option" data-minutes="3">3 min</button>
|
|
156
|
+
<button class="duration-option" data-minutes="15">15 min</button>
|
|
157
|
+
<button class="duration-option" data-minutes="30">30 min</button>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
|
|
161
|
+
<button class="icon-btn btn-config" title="Configuration">
|
|
162
|
+
<i class="bi bi-gear"></i>
|
|
163
|
+
</button>
|
|
164
|
+
<button class="icon-btn btn-ignored" title="Ignored APIs">
|
|
165
|
+
<i class="bi bi-slash-circle"></i>
|
|
166
|
+
</button>
|
|
167
|
+
<button class="icon-btn btn-clear" title="Clear All Traces">
|
|
168
|
+
<i class="bi bi-trash"></i>
|
|
169
|
+
</button>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
|
|
173
|
+
<!-- Main Content -->
|
|
174
|
+
<div class="tracing-content">
|
|
175
|
+
<!-- Sidebar -->
|
|
176
|
+
<div class="trace-list-sidebar">
|
|
177
|
+
<div class="trace-list-header">
|
|
178
|
+
<div class="trace-list-title">Trace Logs</div>
|
|
179
|
+
<div class="trace-search-box">
|
|
180
|
+
<svg class="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
181
|
+
<circle cx="11" cy="11" r="8"/>
|
|
182
|
+
<path d="m21 21-4.35-4.35"/>
|
|
183
|
+
</svg>
|
|
184
|
+
<input type="text" placeholder="Search traces..." id="trace-search">
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
<div class="filter-row">
|
|
188
|
+
<button class="filter-chip active" data-filter="all">All</button>
|
|
189
|
+
<button class="filter-chip" data-filter="success">
|
|
190
|
+
<span class="dot success"></span>Success
|
|
191
|
+
</button>
|
|
192
|
+
<button class="filter-chip" data-filter="error">
|
|
193
|
+
<span class="dot error"></span>Errors
|
|
194
|
+
</button>
|
|
195
|
+
</div>
|
|
196
|
+
<div class="trace-list-items">
|
|
197
|
+
<div class="trace-list-empty">
|
|
198
|
+
<div class="trace-list-empty-icon">📭</div>
|
|
199
|
+
<div>No traces captured</div>
|
|
200
|
+
<div>Start tracing to begin</div>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<!-- Detail Panel / Graph Container (FEATURE-023-C) -->
|
|
206
|
+
<div class="trace-detail-panel">
|
|
207
|
+
<div class="trace-graph-container">
|
|
208
|
+
<!-- Graph will be initialized here by TracingGraphView -->
|
|
209
|
+
</div>
|
|
210
|
+
<div class="trace-graph-zoom-controls">
|
|
211
|
+
<button class="trace-graph-zoom-btn" data-action="zoom-in" title="Zoom In">
|
|
212
|
+
<i class="bi bi-zoom-in"></i>
|
|
213
|
+
</button>
|
|
214
|
+
<button class="trace-graph-zoom-btn" data-action="zoom-out" title="Zoom Out">
|
|
215
|
+
<i class="bi bi-zoom-out"></i>
|
|
216
|
+
</button>
|
|
217
|
+
<button class="trace-graph-zoom-btn" data-action="zoom-reset" title="Fit to View">
|
|
218
|
+
<i class="bi bi-arrows-fullscreen"></i>
|
|
219
|
+
</button>
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
renderTraceList() {
|
|
228
|
+
const listContainer = this.container.querySelector('.trace-list-items');
|
|
229
|
+
|
|
230
|
+
// Filter traces
|
|
231
|
+
let filteredTraces = this.traces;
|
|
232
|
+
if (this.currentFilter === 'success') {
|
|
233
|
+
filteredTraces = this.traces.filter(t => !t.has_error);
|
|
234
|
+
} else if (this.currentFilter === 'error') {
|
|
235
|
+
filteredTraces = this.traces.filter(t => t.has_error);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Search filter
|
|
239
|
+
if (this.searchQuery) {
|
|
240
|
+
const query = this.searchQuery.toLowerCase();
|
|
241
|
+
filteredTraces = filteredTraces.filter(t =>
|
|
242
|
+
(t.api || t.path || '').toLowerCase().includes(query) ||
|
|
243
|
+
(t.trace_id || '').toLowerCase().includes(query)
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (!filteredTraces || filteredTraces.length === 0) {
|
|
248
|
+
listContainer.innerHTML = `
|
|
249
|
+
<div class="trace-list-empty">
|
|
250
|
+
<div class="trace-list-empty-icon">📭</div>
|
|
251
|
+
<div>${this.searchQuery || this.currentFilter !== 'all' ? 'No matching traces' : 'No traces captured'}</div>
|
|
252
|
+
<div>${!this.searchQuery && this.currentFilter === 'all' ? 'Start tracing to begin' : 'Try adjusting your filters'}</div>
|
|
253
|
+
</div>
|
|
254
|
+
`;
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Sort by timestamp descending (newest first)
|
|
259
|
+
filteredTraces.sort((a, b) => {
|
|
260
|
+
const timeA = new Date(a.timestamp || a.created || 0);
|
|
261
|
+
const timeB = new Date(b.timestamp || b.created || 0);
|
|
262
|
+
return timeB - timeA;
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
listContainer.innerHTML = filteredTraces.map(trace => {
|
|
266
|
+
const traceId = trace.trace_id || trace.id || 'unknown';
|
|
267
|
+
const api = trace.api || trace.path || trace.name || '/unknown';
|
|
268
|
+
const timestamp = trace.timestamp || trace.created || '';
|
|
269
|
+
const hasError = trace.has_error || trace.status === 'error';
|
|
270
|
+
const statusClass = hasError ? 'error' : 'success';
|
|
271
|
+
const isSelected = traceId === this.selectedTraceId;
|
|
272
|
+
const method = trace.method || 'GET';
|
|
273
|
+
const duration = trace.duration_ms || trace.duration || 0;
|
|
274
|
+
const nestedCount = trace.nested_count || trace.spans || 0;
|
|
275
|
+
|
|
276
|
+
// Format timestamp
|
|
277
|
+
let formattedTime = '';
|
|
278
|
+
if (timestamp) {
|
|
279
|
+
const date = new Date(timestamp);
|
|
280
|
+
formattedTime = date.toLocaleTimeString();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Truncate trace ID
|
|
284
|
+
const shortId = traceId.substring(0, 8);
|
|
285
|
+
|
|
286
|
+
// Extract just the path from API (e.g., "GET /api/projects" -> "/api/projects")
|
|
287
|
+
const apiPath = api.includes(' ') ? api.split(' ').slice(1).join(' ') : api;
|
|
288
|
+
|
|
289
|
+
return `
|
|
290
|
+
<div class="trace-item ${statusClass} ${isSelected ? 'selected' : ''}"
|
|
291
|
+
data-trace-id="${traceId}" data-api-path="${this.escapeHtml(apiPath)}">
|
|
292
|
+
<div class="trace-id-row">
|
|
293
|
+
<span class="trace-id">${shortId}...</span>
|
|
294
|
+
<button class="trace-block-btn" title="Add to ignored APIs" data-api="${this.escapeHtml(apiPath)}">
|
|
295
|
+
<i class="bi bi-slash-circle"></i>
|
|
296
|
+
</button>
|
|
297
|
+
<span class="trace-status-dot ${statusClass}"></span>
|
|
298
|
+
</div>
|
|
299
|
+
<div class="trace-entry-api">
|
|
300
|
+
<span class="method-badge ${method.toLowerCase()}">${method}</span>
|
|
301
|
+
<span class="trace-path">${this.escapeHtml(api)}</span>
|
|
302
|
+
${nestedCount > 0 ? `<span class="trace-nested">+${nestedCount}</span>` : ''}
|
|
303
|
+
</div>
|
|
304
|
+
<div class="trace-meta">
|
|
305
|
+
<span>
|
|
306
|
+
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
307
|
+
<circle cx="12" cy="12" r="10"/>
|
|
308
|
+
<polyline points="12 6 12 12 16 14"/>
|
|
309
|
+
</svg>
|
|
310
|
+
${formattedTime}
|
|
311
|
+
</span>
|
|
312
|
+
${duration > 0 ? `<span>${duration}ms</span>` : ''}
|
|
313
|
+
</div>
|
|
314
|
+
</div>
|
|
315
|
+
`;
|
|
316
|
+
}).join('');
|
|
317
|
+
|
|
318
|
+
// Rebind click events
|
|
319
|
+
listContainer.querySelectorAll('.trace-item').forEach(item => {
|
|
320
|
+
item.addEventListener('click', (e) => {
|
|
321
|
+
// Don't select trace if clicking the block button
|
|
322
|
+
if (e.target.closest('.trace-block-btn')) return;
|
|
323
|
+
this.selectTrace(item.dataset.traceId);
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// Bind block button events
|
|
328
|
+
listContainer.querySelectorAll('.trace-block-btn').forEach(btn => {
|
|
329
|
+
btn.addEventListener('click', (e) => {
|
|
330
|
+
e.stopPropagation();
|
|
331
|
+
const apiPath = btn.dataset.api;
|
|
332
|
+
this.addToIgnoredApis(apiPath);
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// -------------------------------------------------------------------------
|
|
338
|
+
// Timer Logic
|
|
339
|
+
// -------------------------------------------------------------------------
|
|
340
|
+
|
|
341
|
+
startCountdown() {
|
|
342
|
+
this.stopCountdown();
|
|
343
|
+
this.updateTimerDisplay();
|
|
344
|
+
|
|
345
|
+
this.timerInterval = setInterval(() => {
|
|
346
|
+
this.updateTimerDisplay();
|
|
347
|
+
|
|
348
|
+
// Auto-stop when timer reaches 0
|
|
349
|
+
if (this.stopAt && new Date() >= this.stopAt) {
|
|
350
|
+
this.stopCountdown();
|
|
351
|
+
this.stopAt = null;
|
|
352
|
+
this.updateDurationButtons(null);
|
|
353
|
+
this.updateTimerDisplay();
|
|
354
|
+
this.showToast('Tracing completed', 'success');
|
|
355
|
+
}
|
|
356
|
+
}, 1000);
|
|
357
|
+
|
|
358
|
+
// Show stop button
|
|
359
|
+
const stopBtn = this.container.querySelector('.stop-btn');
|
|
360
|
+
if (stopBtn) stopBtn.classList.remove('hidden');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
stopCountdown() {
|
|
364
|
+
if (this.timerInterval) {
|
|
365
|
+
clearInterval(this.timerInterval);
|
|
366
|
+
this.timerInterval = null;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Hide stop button
|
|
370
|
+
const stopBtn = this.container.querySelector('.stop-btn');
|
|
371
|
+
if (stopBtn) stopBtn.classList.add('hidden');
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
updateTimerDisplay() {
|
|
375
|
+
const container = this.container.querySelector('.countdown-container');
|
|
376
|
+
const timerText = this.container.querySelector('.countdown-text');
|
|
377
|
+
const timerLabel = this.container.querySelector('.countdown-label');
|
|
378
|
+
if (!container || !timerText) return;
|
|
379
|
+
|
|
380
|
+
if (!this.stopAt) {
|
|
381
|
+
timerText.textContent = '--:--';
|
|
382
|
+
if (timerLabel) timerLabel.textContent = 'Inactive';
|
|
383
|
+
container.classList.remove('warning');
|
|
384
|
+
container.classList.add('inactive');
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const now = new Date();
|
|
389
|
+
const remaining = Math.max(0, Math.floor((this.stopAt - now) / 1000));
|
|
390
|
+
|
|
391
|
+
const minutes = Math.floor(remaining / 60);
|
|
392
|
+
const seconds = remaining % 60;
|
|
393
|
+
timerText.textContent = `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
|
|
394
|
+
if (timerLabel) timerLabel.textContent = 'remaining';
|
|
395
|
+
|
|
396
|
+
container.classList.remove('inactive');
|
|
397
|
+
|
|
398
|
+
// Warning state when < 1 minute
|
|
399
|
+
if (remaining < 60 && remaining > 0) {
|
|
400
|
+
container.classList.add('warning');
|
|
401
|
+
} else {
|
|
402
|
+
container.classList.remove('warning');
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
updateUIFromStatus() {
|
|
407
|
+
const stopAtStr = this.config.stop_at;
|
|
408
|
+
if (stopAtStr && this.config.active) {
|
|
409
|
+
this.stopAt = new Date(stopAtStr);
|
|
410
|
+
if (this.stopAt > new Date()) {
|
|
411
|
+
this.startCountdown();
|
|
412
|
+
// Estimate which duration was selected
|
|
413
|
+
const remaining = (this.stopAt - new Date()) / 1000 / 60;
|
|
414
|
+
if (remaining <= 3) this.updateDurationButtons(3);
|
|
415
|
+
else if (remaining <= 15) this.updateDurationButtons(15);
|
|
416
|
+
else this.updateDurationButtons(30);
|
|
417
|
+
} else {
|
|
418
|
+
this.stopAt = null;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
this.updateTimerDisplay();
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
updateDurationButtons(activeMinutes) {
|
|
425
|
+
this.container.querySelectorAll('.duration-option').forEach(btn => {
|
|
426
|
+
const minutes = parseInt(btn.dataset.minutes);
|
|
427
|
+
btn.classList.toggle('active', minutes === activeMinutes);
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// -------------------------------------------------------------------------
|
|
432
|
+
// Polling
|
|
433
|
+
// -------------------------------------------------------------------------
|
|
434
|
+
|
|
435
|
+
startPolling() {
|
|
436
|
+
this.pollInterval = setInterval(() => {
|
|
437
|
+
if (this.stopAt) {
|
|
438
|
+
this.refreshTraceList();
|
|
439
|
+
}
|
|
440
|
+
}, 5000);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
stopPolling() {
|
|
444
|
+
if (this.pollInterval) {
|
|
445
|
+
clearInterval(this.pollInterval);
|
|
446
|
+
this.pollInterval = null;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// -------------------------------------------------------------------------
|
|
451
|
+
// Event Handling
|
|
452
|
+
// -------------------------------------------------------------------------
|
|
453
|
+
|
|
454
|
+
bindEvents() {
|
|
455
|
+
// Duration buttons
|
|
456
|
+
this.container.querySelectorAll('.duration-option').forEach(btn => {
|
|
457
|
+
btn.addEventListener('click', () => {
|
|
458
|
+
const minutes = parseInt(btn.dataset.minutes);
|
|
459
|
+
this.startTracing(minutes);
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
// Stop button
|
|
464
|
+
const stopBtn = this.container.querySelector('.stop-btn');
|
|
465
|
+
if (stopBtn) {
|
|
466
|
+
stopBtn.addEventListener('click', () => this.stopTracing());
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Config button
|
|
470
|
+
const configBtn = this.container.querySelector('.btn-config');
|
|
471
|
+
if (configBtn) {
|
|
472
|
+
configBtn.addEventListener('click', () => this.openConfigModal());
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Ignored APIs button
|
|
476
|
+
const ignoredBtn = this.container.querySelector('.btn-ignored');
|
|
477
|
+
if (ignoredBtn) {
|
|
478
|
+
ignoredBtn.addEventListener('click', () => this.openIgnoredModal());
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Clear all traces button
|
|
482
|
+
const clearBtn = this.container.querySelector('.btn-clear');
|
|
483
|
+
if (clearBtn) {
|
|
484
|
+
clearBtn.addEventListener('click', () => this.clearAllTraces());
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Filter chips
|
|
488
|
+
this.container.querySelectorAll('.filter-chip').forEach(chip => {
|
|
489
|
+
chip.addEventListener('click', () => {
|
|
490
|
+
this.container.querySelectorAll('.filter-chip').forEach(c => c.classList.remove('active'));
|
|
491
|
+
chip.classList.add('active');
|
|
492
|
+
this.currentFilter = chip.dataset.filter;
|
|
493
|
+
this.renderTraceList();
|
|
494
|
+
});
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
// Search
|
|
498
|
+
const searchInput = this.container.querySelector('#trace-search');
|
|
499
|
+
if (searchInput) {
|
|
500
|
+
searchInput.addEventListener('input', (e) => {
|
|
501
|
+
this.searchQuery = e.target.value;
|
|
502
|
+
this.renderTraceList();
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// FEATURE-023-C: Zoom controls
|
|
507
|
+
this.container.querySelectorAll('.trace-graph-zoom-btn').forEach(btn => {
|
|
508
|
+
btn.addEventListener('click', () => {
|
|
509
|
+
if (!this.graphView) return;
|
|
510
|
+
const action = btn.dataset.action;
|
|
511
|
+
if (action === 'zoom-in') this.graphView.zoomIn();
|
|
512
|
+
else if (action === 'zoom-out') this.graphView.zoomOut();
|
|
513
|
+
else if (action === 'zoom-reset') this.graphView.resetZoom();
|
|
514
|
+
});
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
selectTrace(traceId) {
|
|
519
|
+
this.selectedTraceId = traceId;
|
|
520
|
+
|
|
521
|
+
// Update selection UI
|
|
522
|
+
this.container.querySelectorAll('.trace-item').forEach(item => {
|
|
523
|
+
item.classList.toggle('selected', item.dataset.traceId === traceId);
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
// FEATURE-023-C: Load trace into graph view
|
|
527
|
+
if (this.graphView) {
|
|
528
|
+
this.graphView.loadTrace(traceId);
|
|
529
|
+
} else {
|
|
530
|
+
// Fallback if graph view not initialized
|
|
531
|
+
const graphContainer = this.container.querySelector('.trace-graph-container');
|
|
532
|
+
if (graphContainer) {
|
|
533
|
+
graphContainer.innerHTML = `
|
|
534
|
+
<div class="trace-graph-loading">
|
|
535
|
+
<div class="spinner-border spinner-border-sm" role="status"></div>
|
|
536
|
+
<span>Loading trace: ${traceId.substring(0, 12)}...</span>
|
|
537
|
+
</div>
|
|
538
|
+
`;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// -------------------------------------------------------------------------
|
|
544
|
+
// Modals
|
|
545
|
+
// -------------------------------------------------------------------------
|
|
546
|
+
|
|
547
|
+
openConfigModal() {
|
|
548
|
+
const modal = new TracingConfigModal(async (config) => {
|
|
549
|
+
await this.updateConfig(config);
|
|
550
|
+
});
|
|
551
|
+
modal.open(this.config);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
async updateConfig(config) {
|
|
555
|
+
try {
|
|
556
|
+
const response = await fetch('/api/tracing/config', {
|
|
557
|
+
method: 'POST',
|
|
558
|
+
headers: { 'Content-Type': 'application/json' },
|
|
559
|
+
body: JSON.stringify(config)
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
if (response.ok) {
|
|
563
|
+
this.config = { ...this.config, ...config };
|
|
564
|
+
this.showToast('Configuration saved', 'success');
|
|
565
|
+
} else {
|
|
566
|
+
this.showToast('Failed to save configuration', 'error');
|
|
567
|
+
}
|
|
568
|
+
} catch (error) {
|
|
569
|
+
console.error('Error updating config:', error);
|
|
570
|
+
this.showToast('Failed to save configuration', 'error');
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
openIgnoredModal() {
|
|
575
|
+
const modal = new TracingIgnoredModal(async (patterns) => {
|
|
576
|
+
await this.updateIgnoredApis(patterns);
|
|
577
|
+
});
|
|
578
|
+
modal.open(this.config.ignored_apis || []);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
async updateIgnoredApis(patterns) {
|
|
582
|
+
try {
|
|
583
|
+
const response = await fetch('/api/tracing/ignored', {
|
|
584
|
+
method: 'POST',
|
|
585
|
+
headers: { 'Content-Type': 'application/json' },
|
|
586
|
+
body: JSON.stringify({ patterns })
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
if (response.ok) {
|
|
590
|
+
this.config.ignored_apis = patterns;
|
|
591
|
+
this.showToast('Ignored APIs updated', 'success');
|
|
592
|
+
} else {
|
|
593
|
+
this.showToast('Failed to update ignored APIs', 'error');
|
|
594
|
+
}
|
|
595
|
+
} catch (error) {
|
|
596
|
+
console.error('Error updating ignored APIs:', error);
|
|
597
|
+
this.showToast('Failed to update ignored APIs', 'error');
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
async addToIgnoredApis(apiPath) {
|
|
602
|
+
if (!apiPath) return;
|
|
603
|
+
|
|
604
|
+
// Get current ignored APIs
|
|
605
|
+
const currentIgnored = this.config.ignored_apis || [];
|
|
606
|
+
|
|
607
|
+
// Check if already in list
|
|
608
|
+
if (currentIgnored.includes(apiPath)) {
|
|
609
|
+
this.showToast('API already in ignored list', 'info');
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Add to list and save
|
|
614
|
+
const newIgnored = [...currentIgnored, apiPath];
|
|
615
|
+
await this.updateIgnoredApis(newIgnored);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
async clearAllTraces() {
|
|
619
|
+
if (!confirm('Are you sure you want to delete all trace logs? This cannot be undone.')) {
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
try {
|
|
624
|
+
const response = await fetch('/api/tracing/logs', {
|
|
625
|
+
method: 'DELETE'
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
if (response.ok) {
|
|
629
|
+
this.traces = [];
|
|
630
|
+
this.renderTraceList();
|
|
631
|
+
this.graph.showEmpty();
|
|
632
|
+
this.showToast('All traces cleared', 'success');
|
|
633
|
+
} else {
|
|
634
|
+
this.showToast('Failed to clear traces', 'error');
|
|
635
|
+
}
|
|
636
|
+
} catch (error) {
|
|
637
|
+
console.error('Error clearing traces:', error);
|
|
638
|
+
this.showToast('Failed to clear traces', 'error');
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// -------------------------------------------------------------------------
|
|
643
|
+
// Utilities
|
|
644
|
+
// -------------------------------------------------------------------------
|
|
645
|
+
|
|
646
|
+
showToast(message, type = 'success') {
|
|
647
|
+
const toast = document.createElement('div');
|
|
648
|
+
toast.className = `tracing-toast ${type}`;
|
|
649
|
+
toast.textContent = message;
|
|
650
|
+
document.body.appendChild(toast);
|
|
651
|
+
|
|
652
|
+
setTimeout(() => {
|
|
653
|
+
toast.remove();
|
|
654
|
+
}, 3000);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
escapeHtml(text) {
|
|
658
|
+
const div = document.createElement('div');
|
|
659
|
+
div.textContent = text;
|
|
660
|
+
return div.innerHTML;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
// =============================================================================
|
|
666
|
+
// TracingConfigModal - Configuration Modal
|
|
667
|
+
// =============================================================================
|
|
668
|
+
|
|
669
|
+
class TracingConfigModal {
|
|
670
|
+
constructor(onSave) {
|
|
671
|
+
this.onSave = onSave;
|
|
672
|
+
this.overlay = null;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
open(currentConfig) {
|
|
676
|
+
this.overlay = document.createElement('div');
|
|
677
|
+
this.overlay.className = 'tracing-modal-overlay';
|
|
678
|
+
this.overlay.innerHTML = `
|
|
679
|
+
<div class="tracing-modal">
|
|
680
|
+
<div class="tracing-modal-header">
|
|
681
|
+
<h3>Tracing Configuration</h3>
|
|
682
|
+
<button class="tracing-modal-close">×</button>
|
|
683
|
+
</div>
|
|
684
|
+
<div class="tracing-modal-body">
|
|
685
|
+
<div class="tracing-form-group">
|
|
686
|
+
<label>Retention Hours</label>
|
|
687
|
+
<input type="number" id="retention-hours" min="1" max="168"
|
|
688
|
+
value="${currentConfig.retention_hours || 24}">
|
|
689
|
+
</div>
|
|
690
|
+
<div class="tracing-form-group">
|
|
691
|
+
<label>Log Path</label>
|
|
692
|
+
<input type="text" id="log-path"
|
|
693
|
+
value="${currentConfig.log_path || 'instance/traces/'}">
|
|
694
|
+
</div>
|
|
695
|
+
</div>
|
|
696
|
+
<div class="tracing-modal-footer">
|
|
697
|
+
<button class="btn-secondary btn-cancel">Cancel</button>
|
|
698
|
+
<button class="btn-primary btn-save">Save</button>
|
|
699
|
+
</div>
|
|
700
|
+
</div>
|
|
701
|
+
`;
|
|
702
|
+
|
|
703
|
+
document.body.appendChild(this.overlay);
|
|
704
|
+
this.bindEvents();
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
bindEvents() {
|
|
708
|
+
this.overlay.querySelector('.tracing-modal-close').addEventListener('click', () => this.close());
|
|
709
|
+
this.overlay.querySelector('.btn-cancel').addEventListener('click', () => this.close());
|
|
710
|
+
this.overlay.querySelector('.btn-save').addEventListener('click', () => this.handleSave());
|
|
711
|
+
this.overlay.addEventListener('click', (e) => {
|
|
712
|
+
if (e.target === this.overlay) this.close();
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
handleSave() {
|
|
717
|
+
const retentionHours = parseInt(this.overlay.querySelector('#retention-hours').value);
|
|
718
|
+
const logPath = this.overlay.querySelector('#log-path').value;
|
|
719
|
+
|
|
720
|
+
this.onSave({ retention_hours: retentionHours, log_path: logPath });
|
|
721
|
+
this.close();
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
close() {
|
|
725
|
+
if (this.overlay) {
|
|
726
|
+
this.overlay.remove();
|
|
727
|
+
this.overlay = null;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
// =============================================================================
|
|
734
|
+
// TracingIgnoredModal - Ignored APIs Modal
|
|
735
|
+
// =============================================================================
|
|
736
|
+
|
|
737
|
+
class TracingIgnoredModal {
|
|
738
|
+
constructor(onSave) {
|
|
739
|
+
this.onSave = onSave;
|
|
740
|
+
this.overlay = null;
|
|
741
|
+
this.patterns = [];
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
open(patterns) {
|
|
745
|
+
this.patterns = [...patterns];
|
|
746
|
+
|
|
747
|
+
this.overlay = document.createElement('div');
|
|
748
|
+
this.overlay.className = 'tracing-modal-overlay';
|
|
749
|
+
this.render();
|
|
750
|
+
document.body.appendChild(this.overlay);
|
|
751
|
+
this.bindEvents();
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
render() {
|
|
755
|
+
this.overlay.innerHTML = `
|
|
756
|
+
<div class="tracing-modal">
|
|
757
|
+
<div class="tracing-modal-header">
|
|
758
|
+
<h3>Ignored APIs</h3>
|
|
759
|
+
<button class="tracing-modal-close">×</button>
|
|
760
|
+
</div>
|
|
761
|
+
<div class="tracing-modal-body">
|
|
762
|
+
<p style="font-size: 13px; color: #64748b; margin-bottom: 16px;">
|
|
763
|
+
API patterns that will be excluded from tracing.
|
|
764
|
+
</p>
|
|
765
|
+
<div class="ignored-apis-list">
|
|
766
|
+
${this.patterns.map((pattern, i) => `
|
|
767
|
+
<div class="ignored-api-item">
|
|
768
|
+
<span>${this.escapeHtml(pattern)}</span>
|
|
769
|
+
<button data-index="${i}" title="Remove">×</button>
|
|
770
|
+
</div>
|
|
771
|
+
`).join('')}
|
|
772
|
+
</div>
|
|
773
|
+
<div class="add-ignored-api">
|
|
774
|
+
<input type="text" id="new-pattern" placeholder="/api/health/*">
|
|
775
|
+
<button class="btn-secondary btn-add">Add</button>
|
|
776
|
+
</div>
|
|
777
|
+
</div>
|
|
778
|
+
<div class="tracing-modal-footer">
|
|
779
|
+
<button class="btn-secondary btn-cancel">Cancel</button>
|
|
780
|
+
<button class="btn-primary btn-save">Save</button>
|
|
781
|
+
</div>
|
|
782
|
+
</div>
|
|
783
|
+
`;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
bindEvents() {
|
|
787
|
+
this.overlay.querySelector('.tracing-modal-close').addEventListener('click', () => this.close());
|
|
788
|
+
this.overlay.querySelector('.btn-cancel').addEventListener('click', () => this.close());
|
|
789
|
+
this.overlay.querySelector('.btn-save').addEventListener('click', () => this.handleSave());
|
|
790
|
+
this.overlay.querySelector('.btn-add').addEventListener('click', () => this.addPattern());
|
|
791
|
+
|
|
792
|
+
// Remove buttons
|
|
793
|
+
this.overlay.querySelectorAll('.ignored-api-item button').forEach(btn => {
|
|
794
|
+
btn.addEventListener('click', () => {
|
|
795
|
+
const index = parseInt(btn.dataset.index);
|
|
796
|
+
this.removePattern(index);
|
|
797
|
+
});
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
// Enter key to add
|
|
801
|
+
this.overlay.querySelector('#new-pattern').addEventListener('keypress', (e) => {
|
|
802
|
+
if (e.key === 'Enter') this.addPattern();
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
// Click outside to close
|
|
806
|
+
this.overlay.addEventListener('click', (e) => {
|
|
807
|
+
if (e.target === this.overlay) this.close();
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
addPattern() {
|
|
812
|
+
const input = this.overlay.querySelector('#new-pattern');
|
|
813
|
+
const pattern = input.value.trim();
|
|
814
|
+
if (pattern && !this.patterns.includes(pattern)) {
|
|
815
|
+
this.patterns.push(pattern);
|
|
816
|
+
this.render();
|
|
817
|
+
this.bindEvents();
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
removePattern(index) {
|
|
822
|
+
this.patterns.splice(index, 1);
|
|
823
|
+
this.render();
|
|
824
|
+
this.bindEvents();
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
handleSave() {
|
|
828
|
+
this.onSave(this.patterns);
|
|
829
|
+
this.close();
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
close() {
|
|
833
|
+
if (this.overlay) {
|
|
834
|
+
this.overlay.remove();
|
|
835
|
+
this.overlay = null;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
escapeHtml(text) {
|
|
840
|
+
const div = document.createElement('div');
|
|
841
|
+
div.textContent = text;
|
|
842
|
+
return div.innerHTML;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
// =============================================================================
|
|
848
|
+
// Export for module usage
|
|
849
|
+
// =============================================================================
|
|
850
|
+
|
|
851
|
+
if (typeof window !== 'undefined') {
|
|
852
|
+
window.TracingDashboard = TracingDashboard;
|
|
853
|
+
window.TracingConfigModal = TracingConfigModal;
|
|
854
|
+
window.TracingIgnoredModal = TracingIgnoredModal;
|
|
855
|
+
}
|