claude-mpm 4.2.39__py3-none-any.whl → 4.2.42__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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/BASE_ENGINEER.md +114 -1
- claude_mpm/agents/BASE_OPS.md +156 -1
- claude_mpm/agents/INSTRUCTIONS.md +120 -11
- claude_mpm/agents/WORKFLOW.md +160 -10
- claude_mpm/agents/templates/agentic-coder-optimizer.json +17 -12
- claude_mpm/agents/templates/react_engineer.json +217 -0
- claude_mpm/agents/templates/web_qa.json +40 -4
- claude_mpm/cli/__init__.py +3 -5
- claude_mpm/commands/mpm-browser-monitor.md +370 -0
- claude_mpm/commands/mpm-monitor.md +177 -0
- claude_mpm/dashboard/static/built/components/code-viewer.js +1076 -2
- claude_mpm/dashboard/static/built/components/ui-state-manager.js +465 -2
- claude_mpm/dashboard/static/css/dashboard.css +2 -0
- claude_mpm/dashboard/static/js/browser-console-monitor.js +495 -0
- claude_mpm/dashboard/static/js/components/browser-log-viewer.js +763 -0
- claude_mpm/dashboard/static/js/components/code-viewer.js +931 -340
- claude_mpm/dashboard/static/js/components/diff-viewer.js +891 -0
- claude_mpm/dashboard/static/js/components/file-change-tracker.js +443 -0
- claude_mpm/dashboard/static/js/components/file-change-viewer.js +690 -0
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +307 -19
- claude_mpm/dashboard/static/js/socket-client.js +2 -2
- claude_mpm/dashboard/static/test-browser-monitor.html +470 -0
- claude_mpm/dashboard/templates/index.html +62 -99
- claude_mpm/services/cli/unified_dashboard_manager.py +1 -1
- claude_mpm/services/monitor/daemon.py +69 -36
- claude_mpm/services/monitor/daemon_manager.py +186 -29
- claude_mpm/services/monitor/handlers/browser.py +451 -0
- claude_mpm/services/monitor/server.py +272 -5
- {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/METADATA +1 -1
- {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/RECORD +35 -29
- claude_mpm/agents/templates/agentic-coder-optimizer.md +0 -44
- claude_mpm/agents/templates/agentic_coder_optimizer.json +0 -238
- claude_mpm/agents/templates/test-non-mpm.json +0 -20
- claude_mpm/dashboard/static/dist/components/code-viewer.js +0 -2
- {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/WHEEL +0 -0
- {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,763 @@
|
|
1
|
+
/**
|
2
|
+
* Browser Log Viewer Component - VERSION 3.0 EXTREME - MAXIMUM HOOK BLOCKING
|
3
|
+
* Displays real-time browser console logs from monitored browser sessions
|
4
|
+
* EXTREME NUCLEAR ISOLATION FROM HOOK EVENTS
|
5
|
+
*/
|
6
|
+
|
7
|
+
class BrowserLogViewer {
|
8
|
+
constructor(container) {
|
9
|
+
console.error('[BROWSER-LOG-VIEWER v3.0 EXTREME] 🚀🚀🚀 CONSTRUCTOR CALLED');
|
10
|
+
console.error('[BROWSER-LOG-VIEWER v3.0 EXTREME] Container ID:', container?.id);
|
11
|
+
console.error('[BROWSER-LOG-VIEWER v3.0 EXTREME] Container parent:', container?.parentElement?.id);
|
12
|
+
console.error('[BROWSER-LOG-VIEWER v3.0 EXTREME] Call stack:', new Error().stack);
|
13
|
+
|
14
|
+
this.container = container;
|
15
|
+
this.logs = [];
|
16
|
+
this.filters = {
|
17
|
+
browserId: 'all',
|
18
|
+
level: 'all'
|
19
|
+
};
|
20
|
+
this.autoRefresh = true;
|
21
|
+
this.autoScroll = true;
|
22
|
+
this.refreshInterval = null;
|
23
|
+
this.maxLogs = 1000;
|
24
|
+
this.browserSessions = new Set();
|
25
|
+
this.VERSION = '3.0-EXTREME-NUCLEAR';
|
26
|
+
|
27
|
+
// EXTREME: Mark territory immediately
|
28
|
+
if (container) {
|
29
|
+
container.setAttribute('data-browser-logs-extreme', 'active');
|
30
|
+
container.setAttribute('data-version', this.VERSION);
|
31
|
+
}
|
32
|
+
|
33
|
+
console.error('[BROWSER-LOG-VIEWER v3.0 EXTREME] STARTING EXTREME INIT');
|
34
|
+
this.init();
|
35
|
+
}
|
36
|
+
|
37
|
+
init() {
|
38
|
+
console.error('[BROWSER-LOG-VIEWER v3.0 EXTREME] 🚨 INIT - EXTREME NUCLEAR CLEARING');
|
39
|
+
console.error('[BROWSER-LOG-VIEWER v3.0 EXTREME] Container before clear:', this.container?.innerHTML?.substring(0, 100));
|
40
|
+
|
41
|
+
// EXTREME NUCLEAR: Multiple clearing passes
|
42
|
+
for (let i = 0; i < 5; i++) {
|
43
|
+
this.container.innerHTML = '';
|
44
|
+
this.container.textContent = '';
|
45
|
+
while (this.container.firstChild) {
|
46
|
+
this.container.removeChild(this.container.firstChild);
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
// Remove ALL classes and attributes that might cause confusion
|
51
|
+
this.container.className = '';
|
52
|
+
const attributesToRemove = ['data-owner', 'data-events', 'data-component'];
|
53
|
+
attributesToRemove.forEach(attr => this.container.removeAttribute(attr));
|
54
|
+
|
55
|
+
// Now mark it as EXCLUSIVELY OURS with EXTREME markers
|
56
|
+
this.container.setAttribute('data-owner', 'BrowserLogViewer-v3-EXTREME-NUCLEAR');
|
57
|
+
this.container.setAttribute('data-browser-logs-only', 'true');
|
58
|
+
this.container.setAttribute('data-no-hooks', 'ABSOLUTELY');
|
59
|
+
this.container.classList.add('browser-log-viewer-container-v3-extreme');
|
60
|
+
this.container.style.background = '#ffffff';
|
61
|
+
this.container.style.border = '3px solid lime'; // Visual indicator
|
62
|
+
|
63
|
+
console.error('[BROWSER-LOG-VIEWER v3.0 EXTREME] RENDERING EXTREME CLEAN INTERFACE');
|
64
|
+
this.render();
|
65
|
+
this.attachEventListeners();
|
66
|
+
this.startAutoRefresh();
|
67
|
+
|
68
|
+
// Set up EXTREME container protection
|
69
|
+
this.protectContainer();
|
70
|
+
|
71
|
+
console.error('[BROWSER-LOG-VIEWER v3.0 EXTREME] ✅ INIT COMPLETE - CONTAINER SECURED');
|
72
|
+
|
73
|
+
// Listen for real-time browser console events from the browser handler
|
74
|
+
if (window.socket) {
|
75
|
+
// Listen ONLY for browser console events with proper validation
|
76
|
+
// These events come from the injected browser monitoring script
|
77
|
+
window.socket.on('browser_log', (logEntry) => {
|
78
|
+
// Extra validation: ensure this is truly a browser log
|
79
|
+
if (logEntry && logEntry.browser_id) {
|
80
|
+
console.log('[BrowserLogViewer] Received valid browser_log event:', logEntry);
|
81
|
+
this.addLog(logEntry);
|
82
|
+
} else {
|
83
|
+
console.warn('[BrowserLogViewer] Rejected invalid browser_log event (no browser_id):', logEntry);
|
84
|
+
}
|
85
|
+
});
|
86
|
+
|
87
|
+
// Also listen for dashboard:browser:console events from the browser handler
|
88
|
+
window.socket.on('dashboard:browser:console', (logEntry) => {
|
89
|
+
// Extra validation: ensure this is truly a browser log
|
90
|
+
if (logEntry && logEntry.browser_id) {
|
91
|
+
console.log('[BrowserLogViewer] Received valid dashboard:browser:console event:', logEntry);
|
92
|
+
this.addLog(logEntry);
|
93
|
+
} else {
|
94
|
+
console.warn('[BrowserLogViewer] Rejected invalid dashboard:browser:console event (no browser_id):', logEntry);
|
95
|
+
}
|
96
|
+
});
|
97
|
+
}
|
98
|
+
}
|
99
|
+
|
100
|
+
render() {
|
101
|
+
console.error('[BROWSER-LOG-VIEWER v2.0] RENDER CALLED - FORCING CLEAN CONTENT');
|
102
|
+
|
103
|
+
// NUCLEAR: Clear container AGAIN before rendering
|
104
|
+
this.container.innerHTML = '';
|
105
|
+
|
106
|
+
// Add version marker at the top
|
107
|
+
this.container.innerHTML = `
|
108
|
+
<!-- BROWSER-LOG-VIEWER VERSION 2.0 NUCLEAR - NO HOOKS ALLOWED -->
|
109
|
+
<div class="browser-log-viewer" data-version="2.0-NUCLEAR">
|
110
|
+
<div class="browser-log-controls">
|
111
|
+
<div class="filter-group">
|
112
|
+
<label for="browser-filter">Browser:</label>
|
113
|
+
<select id="browser-filter" class="filter-select">
|
114
|
+
<option value="all">All Browsers</option>
|
115
|
+
</select>
|
116
|
+
</div>
|
117
|
+
|
118
|
+
<div class="filter-group">
|
119
|
+
<label for="level-filter">Level:</label>
|
120
|
+
<select id="level-filter" class="filter-select">
|
121
|
+
<option value="all">All Levels</option>
|
122
|
+
<option value="ERROR">Error</option>
|
123
|
+
<option value="WARN">Warning</option>
|
124
|
+
<option value="INFO">Info</option>
|
125
|
+
<option value="DEBUG">Debug</option>
|
126
|
+
</select>
|
127
|
+
</div>
|
128
|
+
|
129
|
+
<div class="control-buttons">
|
130
|
+
<button id="refresh-logs" class="btn btn-secondary" title="Refresh logs">
|
131
|
+
<i class="fas fa-sync"></i> Refresh
|
132
|
+
</button>
|
133
|
+
|
134
|
+
<button id="clear-view" class="btn btn-secondary" title="Clear view">
|
135
|
+
<i class="fas fa-eraser"></i> Clear View
|
136
|
+
</button>
|
137
|
+
|
138
|
+
<button id="toggle-auto-refresh" class="btn btn-primary active" title="Toggle auto-refresh">
|
139
|
+
<i class="fas fa-sync-alt"></i> Auto-Refresh
|
140
|
+
</button>
|
141
|
+
|
142
|
+
<button id="toggle-auto-scroll" class="btn btn-primary active" title="Toggle auto-scroll">
|
143
|
+
<i class="fas fa-arrow-down"></i> Auto-Scroll
|
144
|
+
</button>
|
145
|
+
|
146
|
+
<button id="export-logs" class="btn btn-secondary" title="Export logs">
|
147
|
+
<i class="fas fa-download"></i> Export
|
148
|
+
</button>
|
149
|
+
</div>
|
150
|
+
|
151
|
+
<div class="log-stats">
|
152
|
+
<span id="log-count">0 logs</span>
|
153
|
+
<span id="session-count">0 sessions</span>
|
154
|
+
</div>
|
155
|
+
</div>
|
156
|
+
|
157
|
+
<div class="browser-log-container" id="browser-log-container">
|
158
|
+
<div class="log-entries" id="log-entries">
|
159
|
+
<div class="empty-state">
|
160
|
+
<i class="fas fa-globe fa-3x"></i>
|
161
|
+
<h3 style="color: #28a745; font-weight: bold;">BROWSER LOGS ONLY - VERSION 2.0</h3>
|
162
|
+
<p style="color: red; font-weight: bold;">⚠️ HOOK EVENTS ARE BLOCKED HERE ⚠️</p>
|
163
|
+
<p>No browser console logs yet</p>
|
164
|
+
<p class="text-muted">
|
165
|
+
Browser console logs will appear here when the monitoring script is injected.<br>
|
166
|
+
Use <code>/mpm-browser-monitor start</code> to begin monitoring.
|
167
|
+
</p>
|
168
|
+
<p class="text-muted" style="font-size: 11px; margin-top: 10px;">
|
169
|
+
This tab shows console.log, console.error, and console.warn from monitored browsers only.<br>
|
170
|
+
<strong>Hook events ([hook]) are FORCEFULLY BLOCKED and will NEVER appear here.</strong>
|
171
|
+
</p>
|
172
|
+
</div>
|
173
|
+
</div>
|
174
|
+
</div>
|
175
|
+
</div>
|
176
|
+
`;
|
177
|
+
|
178
|
+
this.addStyles();
|
179
|
+
}
|
180
|
+
|
181
|
+
protectContainer() {
|
182
|
+
console.error('[BROWSER-LOG-VIEWER v2.0] ENABLING NUCLEAR PROTECTION');
|
183
|
+
|
184
|
+
// NUCLEAR PROTECTION: Monitor and DESTROY any contamination
|
185
|
+
const observer = new MutationObserver((mutations) => {
|
186
|
+
mutations.forEach((mutation) => {
|
187
|
+
if (mutation.type === 'childList') {
|
188
|
+
mutation.addedNodes.forEach((node) => {
|
189
|
+
if (node.nodeType === 1) { // Element node
|
190
|
+
// Check for ANY contamination
|
191
|
+
const nodeHTML = node.outerHTML || '';
|
192
|
+
const isContaminated =
|
193
|
+
node.classList && (node.classList.contains('event-item') ||
|
194
|
+
node.classList.contains('events-list')) ||
|
195
|
+
nodeHTML.includes('[hook]') ||
|
196
|
+
nodeHTML.includes('hook.pre_tool') ||
|
197
|
+
nodeHTML.includes('hook.post_tool');
|
198
|
+
|
199
|
+
if (isContaminated) {
|
200
|
+
console.error('[BROWSER-LOG-VIEWER v2.0] 🚨 NUCLEAR ALERT: HOOK CONTAMINATION DETECTED! DESTROYING!');
|
201
|
+
console.error('[BROWSER-LOG-VIEWER v2.0] Contaminated node:', node);
|
202
|
+
node.remove();
|
203
|
+
|
204
|
+
// NUCLEAR RESPONSE: Complete re-render
|
205
|
+
this.container.innerHTML = '';
|
206
|
+
this.render();
|
207
|
+
console.error('[BROWSER-LOG-VIEWER v2.0] ✅ CONTAMINATION ELIMINATED - CONTAINER RESTORED');
|
208
|
+
}
|
209
|
+
}
|
210
|
+
});
|
211
|
+
}
|
212
|
+
});
|
213
|
+
});
|
214
|
+
|
215
|
+
// Start observing with AGGRESSIVE settings
|
216
|
+
observer.observe(this.container, {
|
217
|
+
childList: true,
|
218
|
+
subtree: true,
|
219
|
+
characterData: true, // Also watch for text changes
|
220
|
+
attributes: true // Watch for attribute changes
|
221
|
+
});
|
222
|
+
|
223
|
+
console.error('[BROWSER-LOG-VIEWER v2.0] ✅ NUCLEAR PROTECTION ACTIVE');
|
224
|
+
}
|
225
|
+
|
226
|
+
addStyles() {
|
227
|
+
if (!document.getElementById('browser-log-viewer-styles')) {
|
228
|
+
const style = document.createElement('style');
|
229
|
+
style.id = 'browser-log-viewer-styles';
|
230
|
+
style.textContent = `
|
231
|
+
.browser-log-viewer {
|
232
|
+
height: 100%;
|
233
|
+
display: flex;
|
234
|
+
flex-direction: column;
|
235
|
+
}
|
236
|
+
|
237
|
+
.browser-log-controls {
|
238
|
+
padding: 15px;
|
239
|
+
background: #f8f9fa;
|
240
|
+
border-bottom: 1px solid #dee2e6;
|
241
|
+
display: flex;
|
242
|
+
align-items: center;
|
243
|
+
gap: 15px;
|
244
|
+
flex-wrap: wrap;
|
245
|
+
}
|
246
|
+
|
247
|
+
.filter-group {
|
248
|
+
display: flex;
|
249
|
+
align-items: center;
|
250
|
+
gap: 8px;
|
251
|
+
}
|
252
|
+
|
253
|
+
.filter-group label {
|
254
|
+
font-weight: 500;
|
255
|
+
margin: 0;
|
256
|
+
}
|
257
|
+
|
258
|
+
.filter-select {
|
259
|
+
padding: 5px 10px;
|
260
|
+
border: 1px solid #ced4da;
|
261
|
+
border-radius: 4px;
|
262
|
+
background: white;
|
263
|
+
min-width: 120px;
|
264
|
+
}
|
265
|
+
|
266
|
+
.control-buttons {
|
267
|
+
display: flex;
|
268
|
+
gap: 8px;
|
269
|
+
margin-left: auto;
|
270
|
+
}
|
271
|
+
|
272
|
+
.btn {
|
273
|
+
padding: 6px 12px;
|
274
|
+
border: 1px solid transparent;
|
275
|
+
border-radius: 4px;
|
276
|
+
cursor: pointer;
|
277
|
+
font-size: 14px;
|
278
|
+
display: inline-flex;
|
279
|
+
align-items: center;
|
280
|
+
gap: 5px;
|
281
|
+
transition: all 0.2s;
|
282
|
+
}
|
283
|
+
|
284
|
+
.btn-primary {
|
285
|
+
background: #007bff;
|
286
|
+
color: white;
|
287
|
+
border-color: #007bff;
|
288
|
+
}
|
289
|
+
|
290
|
+
.btn-primary:hover {
|
291
|
+
background: #0056b3;
|
292
|
+
border-color: #0056b3;
|
293
|
+
}
|
294
|
+
|
295
|
+
.btn-primary.active {
|
296
|
+
background: #28a745;
|
297
|
+
border-color: #28a745;
|
298
|
+
}
|
299
|
+
|
300
|
+
.btn-secondary {
|
301
|
+
background: #6c757d;
|
302
|
+
color: white;
|
303
|
+
border-color: #6c757d;
|
304
|
+
}
|
305
|
+
|
306
|
+
.btn-secondary:hover {
|
307
|
+
background: #545b62;
|
308
|
+
border-color: #545b62;
|
309
|
+
}
|
310
|
+
|
311
|
+
.log-stats {
|
312
|
+
display: flex;
|
313
|
+
gap: 15px;
|
314
|
+
font-size: 14px;
|
315
|
+
color: #6c757d;
|
316
|
+
}
|
317
|
+
|
318
|
+
.browser-log-container {
|
319
|
+
flex: 1;
|
320
|
+
overflow-y: auto;
|
321
|
+
background: white;
|
322
|
+
padding: 10px;
|
323
|
+
}
|
324
|
+
|
325
|
+
.log-entries {
|
326
|
+
font-family: 'Monaco', 'Consolas', 'Courier New', monospace;
|
327
|
+
font-size: 12px;
|
328
|
+
line-height: 1.5;
|
329
|
+
}
|
330
|
+
|
331
|
+
.log-entry {
|
332
|
+
padding: 8px 12px;
|
333
|
+
margin-bottom: 4px;
|
334
|
+
border-left: 3px solid #dee2e6;
|
335
|
+
background: #f8f9fa;
|
336
|
+
border-radius: 0 4px 4px 0;
|
337
|
+
display: flex;
|
338
|
+
align-items: flex-start;
|
339
|
+
gap: 10px;
|
340
|
+
word-break: break-word;
|
341
|
+
}
|
342
|
+
|
343
|
+
.log-entry.error {
|
344
|
+
border-left-color: #dc3545;
|
345
|
+
background: #f8d7da;
|
346
|
+
}
|
347
|
+
|
348
|
+
.log-entry.warn {
|
349
|
+
border-left-color: #ffc107;
|
350
|
+
background: #fff3cd;
|
351
|
+
}
|
352
|
+
|
353
|
+
.log-entry.info {
|
354
|
+
border-left-color: #17a2b8;
|
355
|
+
background: #d1ecf1;
|
356
|
+
}
|
357
|
+
|
358
|
+
.log-entry.debug {
|
359
|
+
border-left-color: #6c757d;
|
360
|
+
background: #e2e3e5;
|
361
|
+
}
|
362
|
+
|
363
|
+
.log-timestamp {
|
364
|
+
color: #6c757d;
|
365
|
+
white-space: nowrap;
|
366
|
+
flex-shrink: 0;
|
367
|
+
}
|
368
|
+
|
369
|
+
.log-level {
|
370
|
+
font-weight: bold;
|
371
|
+
text-transform: uppercase;
|
372
|
+
padding: 2px 6px;
|
373
|
+
border-radius: 3px;
|
374
|
+
font-size: 10px;
|
375
|
+
flex-shrink: 0;
|
376
|
+
}
|
377
|
+
|
378
|
+
.log-level.error {
|
379
|
+
background: #dc3545;
|
380
|
+
color: white;
|
381
|
+
}
|
382
|
+
|
383
|
+
.log-level.warn {
|
384
|
+
background: #ffc107;
|
385
|
+
color: #212529;
|
386
|
+
}
|
387
|
+
|
388
|
+
.log-level.info {
|
389
|
+
background: #17a2b8;
|
390
|
+
color: white;
|
391
|
+
}
|
392
|
+
|
393
|
+
.log-level.debug {
|
394
|
+
background: #6c757d;
|
395
|
+
color: white;
|
396
|
+
}
|
397
|
+
|
398
|
+
.log-browser {
|
399
|
+
color: #007bff;
|
400
|
+
font-size: 10px;
|
401
|
+
flex-shrink: 0;
|
402
|
+
}
|
403
|
+
|
404
|
+
.log-message {
|
405
|
+
flex: 1;
|
406
|
+
color: #212529;
|
407
|
+
}
|
408
|
+
|
409
|
+
.log-url {
|
410
|
+
color: #6c757d;
|
411
|
+
font-size: 10px;
|
412
|
+
margin-top: 4px;
|
413
|
+
}
|
414
|
+
|
415
|
+
.empty-state {
|
416
|
+
text-align: center;
|
417
|
+
padding: 60px 20px;
|
418
|
+
color: #6c757d;
|
419
|
+
}
|
420
|
+
|
421
|
+
.empty-state i {
|
422
|
+
color: #dee2e6;
|
423
|
+
margin-bottom: 20px;
|
424
|
+
}
|
425
|
+
|
426
|
+
.empty-state p {
|
427
|
+
margin: 10px 0;
|
428
|
+
}
|
429
|
+
|
430
|
+
.text-muted {
|
431
|
+
color: #adb5bd !important;
|
432
|
+
}
|
433
|
+
`;
|
434
|
+
document.head.appendChild(style);
|
435
|
+
}
|
436
|
+
}
|
437
|
+
|
438
|
+
attachEventListeners() {
|
439
|
+
// Browser filter
|
440
|
+
document.getElementById('browser-filter').addEventListener('change', (e) => {
|
441
|
+
this.filters.browserId = e.target.value;
|
442
|
+
this.renderLogs();
|
443
|
+
});
|
444
|
+
|
445
|
+
// Level filter
|
446
|
+
document.getElementById('level-filter').addEventListener('change', (e) => {
|
447
|
+
this.filters.level = e.target.value;
|
448
|
+
this.renderLogs();
|
449
|
+
});
|
450
|
+
|
451
|
+
// Refresh button - just clears and re-renders in real-time mode
|
452
|
+
document.getElementById('refresh-logs').addEventListener('click', () => {
|
453
|
+
console.log('[BrowserLogViewer] Refresh clicked - re-rendering current logs');
|
454
|
+
this.renderLogs();
|
455
|
+
});
|
456
|
+
|
457
|
+
// Clear view button
|
458
|
+
document.getElementById('clear-view').addEventListener('click', () => {
|
459
|
+
this.logs = [];
|
460
|
+
this.renderLogs();
|
461
|
+
});
|
462
|
+
|
463
|
+
// Auto-refresh toggle
|
464
|
+
document.getElementById('toggle-auto-refresh').addEventListener('click', (e) => {
|
465
|
+
this.autoRefresh = !this.autoRefresh;
|
466
|
+
e.currentTarget.classList.toggle('active', this.autoRefresh);
|
467
|
+
|
468
|
+
if (this.autoRefresh) {
|
469
|
+
this.startAutoRefresh();
|
470
|
+
} else {
|
471
|
+
this.stopAutoRefresh();
|
472
|
+
}
|
473
|
+
});
|
474
|
+
|
475
|
+
// Auto-scroll toggle
|
476
|
+
document.getElementById('toggle-auto-scroll').addEventListener('click', (e) => {
|
477
|
+
this.autoScroll = !this.autoScroll;
|
478
|
+
e.currentTarget.classList.toggle('active', this.autoScroll);
|
479
|
+
});
|
480
|
+
|
481
|
+
// Export logs
|
482
|
+
document.getElementById('export-logs').addEventListener('click', () => {
|
483
|
+
this.exportLogs();
|
484
|
+
});
|
485
|
+
}
|
486
|
+
|
487
|
+
async loadInitialLogs() {
|
488
|
+
// Deprecated - we don't load logs from files anymore
|
489
|
+
// Browser logs are only shown in real-time from WebSocket events
|
490
|
+
console.log('[BrowserLogViewer] Skipping file-based log loading - using real-time events only');
|
491
|
+
}
|
492
|
+
|
493
|
+
async loadLogs() {
|
494
|
+
// Deprecated - we don't load logs from files anymore
|
495
|
+
// Browser logs are only shown in real-time from WebSocket events
|
496
|
+
console.log('[BrowserLogViewer] Real-time mode only - not loading from files');
|
497
|
+
this.updateStats();
|
498
|
+
}
|
499
|
+
|
500
|
+
async loadLogFile(filename) {
|
501
|
+
// Deprecated - we don't load logs from files anymore
|
502
|
+
// Browser logs are only shown in real-time from WebSocket events
|
503
|
+
console.log('[BrowserLogViewer] File loading disabled - real-time mode only');
|
504
|
+
}
|
505
|
+
|
506
|
+
addLog(logEntry, render = true) {
|
507
|
+
// NUCLEAR VALIDATION: Multiple layers of protection
|
508
|
+
|
509
|
+
// Layer 1: Must have browser_id
|
510
|
+
if (!logEntry.browser_id) {
|
511
|
+
console.error('[BROWSER-LOG-VIEWER v2.0] ❌ REJECTED: No browser_id:', logEntry);
|
512
|
+
return;
|
513
|
+
}
|
514
|
+
|
515
|
+
// Layer 2: Check for hook contamination in ANY field
|
516
|
+
const entryString = JSON.stringify(logEntry).toLowerCase();
|
517
|
+
if (entryString.includes('hook') ||
|
518
|
+
entryString.includes('pre_tool') ||
|
519
|
+
entryString.includes('post_tool')) {
|
520
|
+
console.error('[BROWSER-LOG-VIEWER v2.0] 🚨 NUCLEAR REJECTION: Hook contamination detected!', logEntry);
|
521
|
+
return;
|
522
|
+
}
|
523
|
+
|
524
|
+
// Layer 3: Explicit hook type check
|
525
|
+
if (logEntry.type === 'hook' ||
|
526
|
+
logEntry.event_type === 'hook' ||
|
527
|
+
logEntry.event === 'hook' ||
|
528
|
+
(logEntry.message && typeof logEntry.message === 'string' &&
|
529
|
+
(logEntry.message.includes('[hook]') ||
|
530
|
+
logEntry.message.includes('hook.') ||
|
531
|
+
logEntry.message.includes('pre_tool') ||
|
532
|
+
logEntry.message.includes('post_tool')))) {
|
533
|
+
console.error('[BROWSER-LOG-VIEWER v2.0] 🚨🚨 CRITICAL REJECTION: Hook event blocked!', logEntry);
|
534
|
+
return;
|
535
|
+
}
|
536
|
+
|
537
|
+
console.log('[BROWSER-LOG-VIEWER v2.0] ✅ ACCEPTED: Valid browser log:', logEntry);
|
538
|
+
|
539
|
+
// Additionally validate that we have proper browser log structure
|
540
|
+
if (!logEntry.message && !logEntry.level) {
|
541
|
+
console.warn('[BrowserLogViewer] Rejecting malformed browser log entry:', logEntry);
|
542
|
+
return;
|
543
|
+
}
|
544
|
+
|
545
|
+
// Ensure proper structure for browser logs
|
546
|
+
const normalizedEntry = {
|
547
|
+
browser_id: logEntry.browser_id || 'unknown',
|
548
|
+
level: logEntry.level || 'INFO',
|
549
|
+
message: logEntry.message || '',
|
550
|
+
timestamp: logEntry.timestamp || new Date().toISOString(),
|
551
|
+
url: logEntry.url || '',
|
552
|
+
line_info: logEntry.line_info || null
|
553
|
+
};
|
554
|
+
|
555
|
+
// Add to logs array
|
556
|
+
this.logs.push(normalizedEntry);
|
557
|
+
|
558
|
+
// Track browser session
|
559
|
+
if (normalizedEntry.browser_id) {
|
560
|
+
this.browserSessions.add(normalizedEntry.browser_id);
|
561
|
+
this.updateBrowserFilter();
|
562
|
+
}
|
563
|
+
|
564
|
+
// Limit logs to prevent memory issues
|
565
|
+
if (this.logs.length > this.maxLogs) {
|
566
|
+
this.logs = this.logs.slice(-this.maxLogs);
|
567
|
+
}
|
568
|
+
|
569
|
+
if (render) {
|
570
|
+
this.renderLogs();
|
571
|
+
}
|
572
|
+
}
|
573
|
+
|
574
|
+
updateBrowserFilter() {
|
575
|
+
const select = document.getElementById('browser-filter');
|
576
|
+
const currentValue = select.value;
|
577
|
+
|
578
|
+
// Clear existing options except "All"
|
579
|
+
select.innerHTML = '<option value="all">All Browsers</option>';
|
580
|
+
|
581
|
+
// Add browser sessions
|
582
|
+
Array.from(this.browserSessions).sort().forEach(browserId => {
|
583
|
+
const option = document.createElement('option');
|
584
|
+
option.value = browserId;
|
585
|
+
option.textContent = browserId.substring(0, 20) + '...';
|
586
|
+
select.appendChild(option);
|
587
|
+
});
|
588
|
+
|
589
|
+
// Restore selection
|
590
|
+
select.value = currentValue;
|
591
|
+
}
|
592
|
+
|
593
|
+
renderLogs() {
|
594
|
+
console.error('[BROWSER-LOG-VIEWER v2.0] RENDER-LOGS: Starting render');
|
595
|
+
const container = document.getElementById('log-entries');
|
596
|
+
|
597
|
+
// NUCLEAR SAFETY: Verify we're in the right place
|
598
|
+
const parentContainer = document.getElementById('browser-logs-container');
|
599
|
+
if (!parentContainer || !parentContainer.contains(container)) {
|
600
|
+
console.error('[BROWSER-LOG-VIEWER v2.0] 🚨 WRONG CONTAINER - ABORTING!');
|
601
|
+
return;
|
602
|
+
}
|
603
|
+
|
604
|
+
// NUCLEAR CLEAN: Clear any potential contamination
|
605
|
+
const existingContent = container.innerHTML;
|
606
|
+
if (existingContent.includes('[hook]') ||
|
607
|
+
existingContent.includes('hook.pre_tool') ||
|
608
|
+
existingContent.includes('hook.post_tool')) {
|
609
|
+
console.error('[BROWSER-LOG-VIEWER v2.0] 🚨 CONTAMINATION FOUND IN RENDER - NUKING!');
|
610
|
+
container.innerHTML = '';
|
611
|
+
}
|
612
|
+
|
613
|
+
// Filter logs
|
614
|
+
const filteredLogs = this.logs.filter(log => {
|
615
|
+
if (this.filters.browserId !== 'all' && log.browser_id !== this.filters.browserId) {
|
616
|
+
return false;
|
617
|
+
}
|
618
|
+
if (this.filters.level !== 'all' && log.level !== this.filters.level) {
|
619
|
+
return false;
|
620
|
+
}
|
621
|
+
return true;
|
622
|
+
});
|
623
|
+
|
624
|
+
if (filteredLogs.length === 0) {
|
625
|
+
// Show proper empty state based on whether we have any logs at all
|
626
|
+
if (this.logs.length === 0) {
|
627
|
+
container.innerHTML = `
|
628
|
+
<div class="empty-state">
|
629
|
+
<i class="fas fa-globe fa-3x"></i>
|
630
|
+
<p>No browser console logs yet</p>
|
631
|
+
<p class="text-muted">
|
632
|
+
Browser console logs will appear here when the monitoring script is injected.<br>
|
633
|
+
Use <code>/mpm-browser-monitor start</code> to begin monitoring.
|
634
|
+
</p>
|
635
|
+
<p class="text-muted" style="font-size: 11px; margin-top: 10px;">
|
636
|
+
This tab shows console.log, console.error, and console.warn from monitored browsers only.
|
637
|
+
</p>
|
638
|
+
</div>
|
639
|
+
`;
|
640
|
+
} else {
|
641
|
+
container.innerHTML = `
|
642
|
+
<div class="empty-state">
|
643
|
+
<i class="fas fa-filter fa-3x"></i>
|
644
|
+
<p>No logs match the current filters</p>
|
645
|
+
</div>
|
646
|
+
`;
|
647
|
+
}
|
648
|
+
} else {
|
649
|
+
container.innerHTML = filteredLogs.map(log => this.renderLogEntry(log)).join('');
|
650
|
+
|
651
|
+
// Auto-scroll to bottom if enabled
|
652
|
+
if (this.autoScroll) {
|
653
|
+
const logContainer = document.getElementById('browser-log-container');
|
654
|
+
logContainer.scrollTop = logContainer.scrollHeight;
|
655
|
+
}
|
656
|
+
}
|
657
|
+
|
658
|
+
this.updateStats();
|
659
|
+
}
|
660
|
+
|
661
|
+
renderLogEntry(log) {
|
662
|
+
const levelClass = log.level ? log.level.toLowerCase() : 'info';
|
663
|
+
const timestamp = log.timestamp ? new Date(log.timestamp).toLocaleTimeString() : '';
|
664
|
+
const browserId = log.browser_id ? log.browser_id.substring(0, 12) : 'unknown';
|
665
|
+
|
666
|
+
let urlInfo = '';
|
667
|
+
if (log.url) {
|
668
|
+
try {
|
669
|
+
const url = new URL(log.url);
|
670
|
+
urlInfo = `<div class="log-url">${url.pathname}</div>`;
|
671
|
+
} catch {
|
672
|
+
urlInfo = `<div class="log-url">${log.url}</div>`;
|
673
|
+
}
|
674
|
+
}
|
675
|
+
|
676
|
+
return `
|
677
|
+
<div class="log-entry ${levelClass}">
|
678
|
+
<span class="log-timestamp">${timestamp}</span>
|
679
|
+
<span class="log-level ${levelClass}">${log.level || 'INFO'}</span>
|
680
|
+
<span class="log-browser">[${browserId}]</span>
|
681
|
+
<div class="log-message">
|
682
|
+
${this.escapeHtml(log.message || '')}
|
683
|
+
${urlInfo}
|
684
|
+
</div>
|
685
|
+
</div>
|
686
|
+
`;
|
687
|
+
}
|
688
|
+
|
689
|
+
escapeHtml(text) {
|
690
|
+
const div = document.createElement('div');
|
691
|
+
div.textContent = text;
|
692
|
+
return div.innerHTML;
|
693
|
+
}
|
694
|
+
|
695
|
+
updateStats() {
|
696
|
+
document.getElementById('log-count').textContent = `${this.logs.length} logs`;
|
697
|
+
document.getElementById('session-count').textContent = `${this.browserSessions.size} sessions`;
|
698
|
+
}
|
699
|
+
|
700
|
+
startAutoRefresh() {
|
701
|
+
if (this.refreshInterval) {
|
702
|
+
clearInterval(this.refreshInterval);
|
703
|
+
}
|
704
|
+
|
705
|
+
if (this.autoRefresh) {
|
706
|
+
// Refresh every 5 seconds
|
707
|
+
this.refreshInterval = setInterval(() => {
|
708
|
+
this.loadLogs();
|
709
|
+
}, 5000);
|
710
|
+
}
|
711
|
+
}
|
712
|
+
|
713
|
+
stopAutoRefresh() {
|
714
|
+
if (this.refreshInterval) {
|
715
|
+
clearInterval(this.refreshInterval);
|
716
|
+
this.refreshInterval = null;
|
717
|
+
}
|
718
|
+
}
|
719
|
+
|
720
|
+
exportLogs() {
|
721
|
+
const filteredLogs = this.logs.filter(log => {
|
722
|
+
if (this.filters.browserId !== 'all' && log.browser_id !== this.filters.browserId) {
|
723
|
+
return false;
|
724
|
+
}
|
725
|
+
if (this.filters.level !== 'all' && log.level !== this.filters.level) {
|
726
|
+
return false;
|
727
|
+
}
|
728
|
+
return true;
|
729
|
+
});
|
730
|
+
|
731
|
+
const jsonData = JSON.stringify(filteredLogs, null, 2);
|
732
|
+
const blob = new Blob([jsonData], { type: 'application/json' });
|
733
|
+
const url = URL.createObjectURL(blob);
|
734
|
+
|
735
|
+
const a = document.createElement('a');
|
736
|
+
a.href = url;
|
737
|
+
a.download = `browser-logs-${new Date().toISOString()}.json`;
|
738
|
+
document.body.appendChild(a);
|
739
|
+
a.click();
|
740
|
+
document.body.removeChild(a);
|
741
|
+
URL.revokeObjectURL(url);
|
742
|
+
}
|
743
|
+
|
744
|
+
destroy() {
|
745
|
+
this.stopAutoRefresh();
|
746
|
+
|
747
|
+
if (window.socket) {
|
748
|
+
window.socket.off('browser_log');
|
749
|
+
window.socket.off('dashboard:browser:console');
|
750
|
+
}
|
751
|
+
|
752
|
+
// Remove container ownership marker
|
753
|
+
if (this.container) {
|
754
|
+
this.container.removeAttribute('data-owner');
|
755
|
+
this.container.classList.remove('browser-log-viewer-container');
|
756
|
+
}
|
757
|
+
}
|
758
|
+
}
|
759
|
+
|
760
|
+
// Export for use in dashboard
|
761
|
+
if (typeof module !== 'undefined' && module.exports) {
|
762
|
+
module.exports = BrowserLogViewer;
|
763
|
+
}
|