claude-mpm 4.2.9__py3-none-any.whl → 4.2.11__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/cli/commands/dashboard.py +59 -126
- claude_mpm/cli/commands/monitor.py +71 -212
- claude_mpm/cli/commands/run.py +33 -33
- claude_mpm/dashboard/static/css/code-tree.css +8 -16
- claude_mpm/dashboard/static/dist/components/code-tree.js +1 -1
- claude_mpm/dashboard/static/dist/components/file-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/components/unified-data-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/dashboard.js +1 -1
- claude_mpm/dashboard/static/dist/socket-client.js +1 -1
- claude_mpm/dashboard/static/js/components/code-tree.js +692 -114
- claude_mpm/dashboard/static/js/components/file-viewer.js +538 -0
- claude_mpm/dashboard/static/js/components/module-viewer.js +26 -0
- claude_mpm/dashboard/static/js/components/unified-data-viewer.js +166 -14
- claude_mpm/dashboard/static/js/dashboard.js +108 -91
- claude_mpm/dashboard/static/js/socket-client.js +9 -7
- claude_mpm/dashboard/templates/index.html +2 -7
- claude_mpm/hooks/claude_hooks/hook_handler.py +1 -11
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +54 -59
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +112 -72
- claude_mpm/services/agents/deployment/agent_template_builder.py +0 -1
- claude_mpm/services/cli/unified_dashboard_manager.py +354 -0
- claude_mpm/services/monitor/__init__.py +20 -0
- claude_mpm/services/monitor/daemon.py +256 -0
- claude_mpm/services/monitor/event_emitter.py +279 -0
- claude_mpm/services/monitor/handlers/__init__.py +20 -0
- claude_mpm/services/monitor/handlers/code_analysis.py +334 -0
- claude_mpm/services/monitor/handlers/dashboard.py +298 -0
- claude_mpm/services/monitor/handlers/hooks.py +491 -0
- claude_mpm/services/monitor/management/__init__.py +18 -0
- claude_mpm/services/monitor/management/health.py +124 -0
- claude_mpm/services/monitor/management/lifecycle.py +298 -0
- claude_mpm/services/monitor/server.py +442 -0
- claude_mpm/tools/code_tree_analyzer.py +33 -17
- {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/METADATA +1 -1
- {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/RECORD +41 -36
- claude_mpm/cli/commands/socketio_monitor.py +0 -233
- claude_mpm/scripts/socketio_daemon.py +0 -571
- claude_mpm/scripts/socketio_daemon_hardened.py +0 -937
- claude_mpm/scripts/socketio_daemon_wrapper.py +0 -78
- claude_mpm/scripts/socketio_server_manager.py +0 -349
- claude_mpm/services/cli/dashboard_launcher.py +0 -423
- claude_mpm/services/cli/socketio_manager.py +0 -595
- claude_mpm/services/dashboard/stable_server.py +0 -1020
- claude_mpm/services/socketio/monitor_server.py +0 -505
- {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/WHEEL +0 -0
- {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Viewer Component
|
|
3
|
+
*
|
|
4
|
+
* A simple file content viewer that displays file contents in a modal window.
|
|
5
|
+
* This component handles file loading via HTTP requests and displays the content
|
|
6
|
+
* with basic syntax highlighting support.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
class FileViewer {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.modal = null;
|
|
12
|
+
this.currentFile = null;
|
|
13
|
+
this.initialized = false;
|
|
14
|
+
this.contentCache = new Map();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Initialize the file viewer
|
|
19
|
+
*/
|
|
20
|
+
initialize() {
|
|
21
|
+
if (this.initialized) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
this.createModal();
|
|
26
|
+
this.setupEventHandlers();
|
|
27
|
+
|
|
28
|
+
this.initialized = true;
|
|
29
|
+
console.log('File viewer initialized');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Create modal DOM structure
|
|
34
|
+
*/
|
|
35
|
+
createModal() {
|
|
36
|
+
const modalHtml = `
|
|
37
|
+
<div class="file-viewer-modal" id="file-viewer-modal">
|
|
38
|
+
<div class="file-viewer-content">
|
|
39
|
+
<div class="file-viewer-header">
|
|
40
|
+
<h2>📄 File Viewer</h2>
|
|
41
|
+
<button class="file-viewer-close" id="file-viewer-close">×</button>
|
|
42
|
+
</div>
|
|
43
|
+
<div class="file-viewer-path" id="file-viewer-path">
|
|
44
|
+
Loading...
|
|
45
|
+
</div>
|
|
46
|
+
<div class="file-viewer-body">
|
|
47
|
+
<pre class="file-viewer-code" id="file-viewer-code">
|
|
48
|
+
<code id="file-viewer-code-content">Loading file content...</code>
|
|
49
|
+
</pre>
|
|
50
|
+
</div>
|
|
51
|
+
<div class="file-viewer-footer">
|
|
52
|
+
<div class="file-viewer-info">
|
|
53
|
+
<span id="file-viewer-type">Type: --</span>
|
|
54
|
+
<span id="file-viewer-lines">Lines: --</span>
|
|
55
|
+
<span id="file-viewer-size">Size: --</span>
|
|
56
|
+
</div>
|
|
57
|
+
<button class="file-viewer-copy" id="file-viewer-copy">📋 Copy</button>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
// Add modal to body
|
|
64
|
+
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
|
65
|
+
this.modal = document.getElementById('file-viewer-modal');
|
|
66
|
+
|
|
67
|
+
// Add styles if not already present
|
|
68
|
+
if (!document.getElementById('file-viewer-styles')) {
|
|
69
|
+
const styles = `
|
|
70
|
+
<style id="file-viewer-styles">
|
|
71
|
+
.file-viewer-modal {
|
|
72
|
+
display: none;
|
|
73
|
+
position: fixed;
|
|
74
|
+
top: 0;
|
|
75
|
+
left: 0;
|
|
76
|
+
width: 100%;
|
|
77
|
+
height: 100%;
|
|
78
|
+
background: rgba(0, 0, 0, 0.7);
|
|
79
|
+
z-index: 10000;
|
|
80
|
+
animation: fadeIn 0.2s;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.file-viewer-modal.show {
|
|
84
|
+
display: flex;
|
|
85
|
+
align-items: center;
|
|
86
|
+
justify-content: center;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.file-viewer-content {
|
|
90
|
+
background: #1e1e1e;
|
|
91
|
+
border-radius: 8px;
|
|
92
|
+
width: 90%;
|
|
93
|
+
max-width: 1200px;
|
|
94
|
+
height: 80%;
|
|
95
|
+
display: flex;
|
|
96
|
+
flex-direction: column;
|
|
97
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.file-viewer-header {
|
|
101
|
+
display: flex;
|
|
102
|
+
justify-content: space-between;
|
|
103
|
+
align-items: center;
|
|
104
|
+
padding: 15px 20px;
|
|
105
|
+
background: #2d2d30;
|
|
106
|
+
border-radius: 8px 8px 0 0;
|
|
107
|
+
border-bottom: 1px solid #3e3e42;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.file-viewer-header h2 {
|
|
111
|
+
margin: 0;
|
|
112
|
+
color: #cccccc;
|
|
113
|
+
font-size: 18px;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.file-viewer-close {
|
|
117
|
+
background: none;
|
|
118
|
+
border: none;
|
|
119
|
+
color: #999;
|
|
120
|
+
font-size: 24px;
|
|
121
|
+
cursor: pointer;
|
|
122
|
+
padding: 0;
|
|
123
|
+
width: 30px;
|
|
124
|
+
height: 30px;
|
|
125
|
+
display: flex;
|
|
126
|
+
align-items: center;
|
|
127
|
+
justify-content: center;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.file-viewer-close:hover {
|
|
131
|
+
color: #fff;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.file-viewer-path {
|
|
135
|
+
padding: 10px 20px;
|
|
136
|
+
background: #252526;
|
|
137
|
+
color: #8b8b8b;
|
|
138
|
+
font-family: 'Consolas', 'Monaco', monospace;
|
|
139
|
+
font-size: 12px;
|
|
140
|
+
border-bottom: 1px solid #3e3e42;
|
|
141
|
+
word-break: break-all;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.file-viewer-body {
|
|
145
|
+
flex: 1;
|
|
146
|
+
overflow: auto;
|
|
147
|
+
padding: 20px;
|
|
148
|
+
background: #1e1e1e;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.file-viewer-code {
|
|
152
|
+
margin: 0;
|
|
153
|
+
padding: 0;
|
|
154
|
+
background: transparent;
|
|
155
|
+
overflow: visible;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.file-viewer-code code {
|
|
159
|
+
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
|
160
|
+
font-size: 13px;
|
|
161
|
+
line-height: 1.5;
|
|
162
|
+
color: #d4d4d4;
|
|
163
|
+
white-space: pre;
|
|
164
|
+
display: block;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.file-viewer-footer {
|
|
168
|
+
padding: 15px 20px;
|
|
169
|
+
background: #2d2d30;
|
|
170
|
+
border-top: 1px solid #3e3e42;
|
|
171
|
+
display: flex;
|
|
172
|
+
justify-content: space-between;
|
|
173
|
+
align-items: center;
|
|
174
|
+
border-radius: 0 0 8px 8px;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.file-viewer-info {
|
|
178
|
+
display: flex;
|
|
179
|
+
gap: 20px;
|
|
180
|
+
color: #8b8b8b;
|
|
181
|
+
font-size: 12px;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.file-viewer-copy {
|
|
185
|
+
background: #0e639c;
|
|
186
|
+
color: white;
|
|
187
|
+
border: none;
|
|
188
|
+
padding: 6px 12px;
|
|
189
|
+
border-radius: 4px;
|
|
190
|
+
cursor: pointer;
|
|
191
|
+
font-size: 12px;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.file-viewer-copy:hover {
|
|
195
|
+
background: #1177bb;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.file-viewer-copy.copied {
|
|
199
|
+
background: #4ec9b0;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.file-viewer-error {
|
|
203
|
+
color: #f48771;
|
|
204
|
+
padding: 20px;
|
|
205
|
+
text-align: center;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
@keyframes fadeIn {
|
|
209
|
+
from { opacity: 0; }
|
|
210
|
+
to { opacity: 1; }
|
|
211
|
+
}
|
|
212
|
+
</style>
|
|
213
|
+
`;
|
|
214
|
+
document.head.insertAdjacentHTML('beforeend', styles);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Setup event handlers
|
|
220
|
+
*/
|
|
221
|
+
setupEventHandlers() {
|
|
222
|
+
// Close button
|
|
223
|
+
document.getElementById('file-viewer-close').addEventListener('click', () => {
|
|
224
|
+
this.hide();
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Close on backdrop click
|
|
228
|
+
this.modal.addEventListener('click', (e) => {
|
|
229
|
+
if (e.target === this.modal) {
|
|
230
|
+
this.hide();
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Close on ESC key
|
|
235
|
+
document.addEventListener('keydown', (e) => {
|
|
236
|
+
if (e.key === 'Escape' && this.modal.classList.contains('show')) {
|
|
237
|
+
this.hide();
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Copy button
|
|
242
|
+
document.getElementById('file-viewer-copy').addEventListener('click', () => {
|
|
243
|
+
this.copyContent();
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Show the file viewer with file content
|
|
249
|
+
*/
|
|
250
|
+
async show(filePath) {
|
|
251
|
+
if (!this.initialized) {
|
|
252
|
+
this.initialize();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
this.currentFile = filePath;
|
|
256
|
+
this.modal.classList.add('show');
|
|
257
|
+
|
|
258
|
+
// Update path
|
|
259
|
+
document.getElementById('file-viewer-path').textContent = filePath;
|
|
260
|
+
|
|
261
|
+
// Load file content
|
|
262
|
+
await this.loadFileContent(filePath);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Hide the file viewer
|
|
267
|
+
*/
|
|
268
|
+
hide() {
|
|
269
|
+
this.modal.classList.remove('show');
|
|
270
|
+
this.currentFile = null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Load file content
|
|
275
|
+
*/
|
|
276
|
+
async loadFileContent(filePath) {
|
|
277
|
+
const codeContent = document.getElementById('file-viewer-code-content');
|
|
278
|
+
|
|
279
|
+
// Check cache first
|
|
280
|
+
if (this.contentCache.has(filePath)) {
|
|
281
|
+
this.displayContent(this.contentCache.get(filePath));
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Show loading state
|
|
286
|
+
codeContent.textContent = 'Loading file content...';
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
// Try to fetch file content via API
|
|
290
|
+
const response = await fetch('/api/file', {
|
|
291
|
+
method: 'POST',
|
|
292
|
+
headers: {
|
|
293
|
+
'Content-Type': 'application/json',
|
|
294
|
+
},
|
|
295
|
+
body: JSON.stringify({ path: filePath })
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
if (!response.ok) {
|
|
299
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const data = await response.json();
|
|
303
|
+
|
|
304
|
+
if (data.success && data.content !== undefined) {
|
|
305
|
+
// Cache the content
|
|
306
|
+
this.contentCache.set(filePath, data.content);
|
|
307
|
+
|
|
308
|
+
// Display the content
|
|
309
|
+
this.displayContent(data.content);
|
|
310
|
+
|
|
311
|
+
// Update file info
|
|
312
|
+
this.updateFileInfo(data);
|
|
313
|
+
} else {
|
|
314
|
+
throw new Error(data.error || 'Failed to load file content');
|
|
315
|
+
}
|
|
316
|
+
} catch (error) {
|
|
317
|
+
console.error('Error loading file:', error);
|
|
318
|
+
|
|
319
|
+
// If API fails, show error message with helpful information
|
|
320
|
+
this.displayError(filePath, error.message);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Display file content
|
|
326
|
+
*/
|
|
327
|
+
displayContent(content) {
|
|
328
|
+
const codeContent = document.getElementById('file-viewer-code-content');
|
|
329
|
+
|
|
330
|
+
// Set the content
|
|
331
|
+
codeContent.textContent = content || '(Empty file)';
|
|
332
|
+
|
|
333
|
+
// Update line count
|
|
334
|
+
const lines = content ? content.split('\n').length : 0;
|
|
335
|
+
document.getElementById('file-viewer-lines').textContent = `Lines: ${lines}`;
|
|
336
|
+
|
|
337
|
+
// Update file size
|
|
338
|
+
const size = content ? new Blob([content]).size : 0;
|
|
339
|
+
document.getElementById('file-viewer-size').textContent = `Size: ${this.formatFileSize(size)}`;
|
|
340
|
+
|
|
341
|
+
// Detect and set file type
|
|
342
|
+
const fileType = this.detectFileType(this.currentFile);
|
|
343
|
+
document.getElementById('file-viewer-type').textContent = `Type: ${fileType}`;
|
|
344
|
+
|
|
345
|
+
// Apply syntax highlighting if Prism is available
|
|
346
|
+
if (window.Prism) {
|
|
347
|
+
const language = this.detectLanguage(this.currentFile);
|
|
348
|
+
codeContent.className = `language-${language}`;
|
|
349
|
+
Prism.highlightElement(codeContent);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Display error message
|
|
355
|
+
*/
|
|
356
|
+
displayError(filePath, errorMessage) {
|
|
357
|
+
const codeContent = document.getElementById('file-viewer-code-content');
|
|
358
|
+
|
|
359
|
+
// For now, show a helpful message since the API endpoint doesn't exist yet
|
|
360
|
+
const errorHtml = `
|
|
361
|
+
<div class="file-viewer-error">
|
|
362
|
+
⚠️ File content loading is not yet implemented
|
|
363
|
+
|
|
364
|
+
File path: ${filePath}
|
|
365
|
+
|
|
366
|
+
The file viewing functionality requires:
|
|
367
|
+
1. A server-side /api/file endpoint
|
|
368
|
+
2. Proper file reading permissions
|
|
369
|
+
3. Security validation for file access
|
|
370
|
+
|
|
371
|
+
Error: ${errorMessage}
|
|
372
|
+
|
|
373
|
+
This feature will be available once the backend API is implemented.
|
|
374
|
+
</div>
|
|
375
|
+
`;
|
|
376
|
+
|
|
377
|
+
codeContent.innerHTML = errorHtml;
|
|
378
|
+
|
|
379
|
+
// Update info
|
|
380
|
+
document.getElementById('file-viewer-lines').textContent = 'Lines: --';
|
|
381
|
+
document.getElementById('file-viewer-size').textContent = 'Size: --';
|
|
382
|
+
document.getElementById('file-viewer-type').textContent = 'Type: --';
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Update file info
|
|
387
|
+
*/
|
|
388
|
+
updateFileInfo(data) {
|
|
389
|
+
if (data.lines !== undefined) {
|
|
390
|
+
document.getElementById('file-viewer-lines').textContent = `Lines: ${data.lines}`;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (data.size !== undefined) {
|
|
394
|
+
document.getElementById('file-viewer-size').textContent = `Size: ${this.formatFileSize(data.size)}`;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (data.type) {
|
|
398
|
+
document.getElementById('file-viewer-type').textContent = `Type: ${data.type}`;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Format file size for display
|
|
404
|
+
*/
|
|
405
|
+
formatFileSize(bytes) {
|
|
406
|
+
if (bytes === 0) return '0 Bytes';
|
|
407
|
+
|
|
408
|
+
const k = 1024;
|
|
409
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
410
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
411
|
+
|
|
412
|
+
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Detect file type from path
|
|
417
|
+
*/
|
|
418
|
+
detectFileType(path) {
|
|
419
|
+
if (!path) return 'Unknown';
|
|
420
|
+
|
|
421
|
+
const ext = path.split('.').pop()?.toLowerCase();
|
|
422
|
+
const typeMap = {
|
|
423
|
+
'py': 'Python',
|
|
424
|
+
'js': 'JavaScript',
|
|
425
|
+
'ts': 'TypeScript',
|
|
426
|
+
'jsx': 'React JSX',
|
|
427
|
+
'tsx': 'React TSX',
|
|
428
|
+
'html': 'HTML',
|
|
429
|
+
'css': 'CSS',
|
|
430
|
+
'json': 'JSON',
|
|
431
|
+
'xml': 'XML',
|
|
432
|
+
'yaml': 'YAML',
|
|
433
|
+
'yml': 'YAML',
|
|
434
|
+
'md': 'Markdown',
|
|
435
|
+
'txt': 'Text',
|
|
436
|
+
'sh': 'Shell Script',
|
|
437
|
+
'bash': 'Bash Script',
|
|
438
|
+
'sql': 'SQL',
|
|
439
|
+
'go': 'Go',
|
|
440
|
+
'rs': 'Rust',
|
|
441
|
+
'java': 'Java',
|
|
442
|
+
'cpp': 'C++',
|
|
443
|
+
'c': 'C',
|
|
444
|
+
'cs': 'C#',
|
|
445
|
+
'rb': 'Ruby',
|
|
446
|
+
'php': 'PHP'
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
return typeMap[ext] || 'Text';
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Detect language for syntax highlighting
|
|
454
|
+
*/
|
|
455
|
+
detectLanguage(path) {
|
|
456
|
+
if (!path) return 'plaintext';
|
|
457
|
+
|
|
458
|
+
const ext = path.split('.').pop()?.toLowerCase();
|
|
459
|
+
const languageMap = {
|
|
460
|
+
'py': 'python',
|
|
461
|
+
'js': 'javascript',
|
|
462
|
+
'ts': 'typescript',
|
|
463
|
+
'jsx': 'jsx',
|
|
464
|
+
'tsx': 'tsx',
|
|
465
|
+
'html': 'html',
|
|
466
|
+
'css': 'css',
|
|
467
|
+
'json': 'json',
|
|
468
|
+
'xml': 'xml',
|
|
469
|
+
'yaml': 'yaml',
|
|
470
|
+
'yml': 'yaml',
|
|
471
|
+
'md': 'markdown',
|
|
472
|
+
'sh': 'bash',
|
|
473
|
+
'bash': 'bash',
|
|
474
|
+
'sql': 'sql',
|
|
475
|
+
'go': 'go',
|
|
476
|
+
'rs': 'rust',
|
|
477
|
+
'java': 'java',
|
|
478
|
+
'cpp': 'cpp',
|
|
479
|
+
'c': 'c',
|
|
480
|
+
'cs': 'csharp',
|
|
481
|
+
'rb': 'ruby',
|
|
482
|
+
'php': 'php'
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
return languageMap[ext] || 'plaintext';
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Copy file content to clipboard
|
|
490
|
+
*/
|
|
491
|
+
async copyContent() {
|
|
492
|
+
const codeContent = document.getElementById('file-viewer-code-content');
|
|
493
|
+
const button = document.getElementById('file-viewer-copy');
|
|
494
|
+
const content = codeContent.textContent;
|
|
495
|
+
|
|
496
|
+
try {
|
|
497
|
+
await navigator.clipboard.writeText(content);
|
|
498
|
+
|
|
499
|
+
// Show feedback
|
|
500
|
+
const originalText = button.textContent;
|
|
501
|
+
button.textContent = '✅ Copied!';
|
|
502
|
+
button.classList.add('copied');
|
|
503
|
+
|
|
504
|
+
setTimeout(() => {
|
|
505
|
+
button.textContent = originalText;
|
|
506
|
+
button.classList.remove('copied');
|
|
507
|
+
}, 2000);
|
|
508
|
+
} catch (err) {
|
|
509
|
+
console.error('Failed to copy:', err);
|
|
510
|
+
alert('Failed to copy content to clipboard');
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Create singleton instance
|
|
516
|
+
const fileViewer = new FileViewer();
|
|
517
|
+
|
|
518
|
+
// Create global function for easy access
|
|
519
|
+
window.showFileViewerModal = (filePath) => {
|
|
520
|
+
fileViewer.show(filePath);
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
// Export for use in other modules
|
|
524
|
+
if (typeof window !== 'undefined') {
|
|
525
|
+
window.FileViewer = fileViewer;
|
|
526
|
+
|
|
527
|
+
// Initialize when DOM is ready
|
|
528
|
+
if (document.readyState === 'loading') {
|
|
529
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
530
|
+
fileViewer.initialize();
|
|
531
|
+
});
|
|
532
|
+
} else {
|
|
533
|
+
// DOM is already loaded
|
|
534
|
+
fileViewer.initialize();
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
export default fileViewer;
|
|
@@ -18,11 +18,37 @@ class ModuleViewer {
|
|
|
18
18
|
// When true, all events show JSON expanded; when false, all collapsed
|
|
19
19
|
this.globalJsonExpanded = localStorage.getItem('dashboard-json-expanded') === 'true';
|
|
20
20
|
|
|
21
|
+
// Separate state for Full Event Data sections
|
|
22
|
+
this.fullEventDataExpanded = localStorage.getItem('dashboard-full-event-expanded') === 'true';
|
|
23
|
+
|
|
21
24
|
// Track if keyboard listener has been added to avoid duplicates
|
|
22
25
|
this.keyboardListenerAdded = false;
|
|
23
26
|
|
|
24
27
|
// Initialize unified data viewer
|
|
25
28
|
this.unifiedViewer = new UnifiedDataViewer('module-data-content');
|
|
29
|
+
|
|
30
|
+
// Sync unified viewer's JSON state with module viewer's state
|
|
31
|
+
// This ensures both viewers maintain consistent JSON visibility
|
|
32
|
+
this.unifiedViewer.globalJsonExpanded = this.globalJsonExpanded;
|
|
33
|
+
this.unifiedViewer.fullEventDataExpanded = this.fullEventDataExpanded;
|
|
34
|
+
|
|
35
|
+
// Listen for JSON state changes from unified viewer
|
|
36
|
+
document.addEventListener('jsonToggleChanged', (e) => {
|
|
37
|
+
this.globalJsonExpanded = e.detail.expanded;
|
|
38
|
+
// Also update the unified viewer's state if it's different
|
|
39
|
+
if (this.unifiedViewer.globalJsonExpanded !== e.detail.expanded) {
|
|
40
|
+
this.unifiedViewer.globalJsonExpanded = e.detail.expanded;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Listen for Full Event Data state changes
|
|
45
|
+
document.addEventListener('fullEventToggleChanged', (e) => {
|
|
46
|
+
this.fullEventDataExpanded = e.detail.expanded;
|
|
47
|
+
// Also update the unified viewer's state if it's different
|
|
48
|
+
if (this.unifiedViewer.fullEventDataExpanded !== e.detail.expanded) {
|
|
49
|
+
this.unifiedViewer.fullEventDataExpanded = e.detail.expanded;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
26
52
|
|
|
27
53
|
this.init();
|
|
28
54
|
}
|