claude-mpm 3.7.4__py3-none-any.whl → 3.7.8__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.
Files changed (48) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/INSTRUCTIONS.md +18 -0
  3. claude_mpm/agents/schema/agent_schema.json +1 -1
  4. claude_mpm/agents/templates/code_analyzer.json +26 -11
  5. claude_mpm/agents/templates/data_engineer.json +3 -6
  6. claude_mpm/agents/templates/documentation.json +2 -2
  7. claude_mpm/agents/templates/engineer.json +1 -1
  8. claude_mpm/agents/templates/ops.json +3 -8
  9. claude_mpm/agents/templates/qa.json +2 -3
  10. claude_mpm/agents/templates/research.json +1 -2
  11. claude_mpm/agents/templates/security.json +2 -5
  12. claude_mpm/agents/templates/ticketing.json +3 -3
  13. claude_mpm/agents/templates/version_control.json +3 -3
  14. claude_mpm/agents/templates/web_qa.json +3 -3
  15. claude_mpm/agents/templates/web_ui.json +3 -3
  16. claude_mpm/cli/commands/agents.py +118 -1
  17. claude_mpm/cli/parser.py +11 -0
  18. claude_mpm/core/framework_loader.py +8 -7
  19. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +46 -2
  20. claude_mpm/dashboard/templates/index.html +5 -5
  21. claude_mpm/services/agents/deployment/agent_deployment.py +5 -1
  22. claude_mpm/services/agents/deployment/async_agent_deployment.py +5 -1
  23. claude_mpm/services/agents/management/agent_capabilities_generator.py +21 -11
  24. claude_mpm/services/ticket_manager.py +207 -44
  25. claude_mpm/utils/agent_dependency_loader.py +66 -15
  26. claude_mpm/utils/robust_installer.py +587 -0
  27. {claude_mpm-3.7.4.dist-info → claude_mpm-3.7.8.dist-info}/METADATA +17 -21
  28. {claude_mpm-3.7.4.dist-info → claude_mpm-3.7.8.dist-info}/RECORD +32 -47
  29. claude_mpm/.claude-mpm/logs/hooks_20250728.log +0 -10
  30. claude_mpm/agents/agent-template.yaml +0 -83
  31. claude_mpm/cli/README.md +0 -108
  32. claude_mpm/cli_module/refactoring_guide.md +0 -253
  33. claude_mpm/config/async_logging_config.yaml +0 -145
  34. claude_mpm/core/.claude-mpm/logs/hooks_20250730.log +0 -34
  35. claude_mpm/dashboard/.claude-mpm/memories/README.md +0 -36
  36. claude_mpm/dashboard/README.md +0 -121
  37. claude_mpm/dashboard/static/js/dashboard.js.backup +0 -1973
  38. claude_mpm/dashboard/templates/.claude-mpm/memories/README.md +0 -36
  39. claude_mpm/dashboard/templates/.claude-mpm/memories/engineer_agent.md +0 -39
  40. claude_mpm/dashboard/templates/.claude-mpm/memories/version_control_agent.md +0 -38
  41. claude_mpm/hooks/README.md +0 -96
  42. claude_mpm/schemas/agent_schema.json +0 -435
  43. claude_mpm/services/framework_claude_md_generator/README.md +0 -92
  44. claude_mpm/services/version_control/VERSION +0 -1
  45. {claude_mpm-3.7.4.dist-info → claude_mpm-3.7.8.dist-info}/WHEEL +0 -0
  46. {claude_mpm-3.7.4.dist-info → claude_mpm-3.7.8.dist-info}/entry_points.txt +0 -0
  47. {claude_mpm-3.7.4.dist-info → claude_mpm-3.7.8.dist-info}/licenses/LICENSE +0 -0
  48. {claude_mpm-3.7.4.dist-info → claude_mpm-3.7.8.dist-info}/top_level.txt +0 -0
@@ -1,1973 +0,0 @@
1
- /**
2
- * Refactored Dashboard Coordinator
3
- *
4
- * Main coordinator class that orchestrates all dashboard modules while maintaining
5
- * backward compatibility with the original dashboard interface.
6
- *
7
- * WHY: This refactored version breaks down the monolithic 4,133-line dashboard
8
- * into manageable, focused modules while preserving all existing functionality.
9
- * Each module handles a specific concern, improving maintainability and testability.
10
- *
11
- * DESIGN DECISION: Acts as a thin coordinator layer that initializes modules,
12
- * manages inter-module communication through events, and provides backward
13
- * compatibility for existing code that depends on the dashboard interface.
14
- */
15
- class Dashboard {
16
- constructor() {
17
- // Core components (existing)
18
- this.eventViewer = null;
19
- this.moduleViewer = null;
20
- this.sessionManager = null;
21
-
22
- // New modular components
23
- this.socketManager = null;
24
- this.agentInference = null;
25
- this.uiStateManager = null;
26
- this.eventProcessor = null;
27
- this.exportManager = null;
28
- this.workingDirectoryManager = null;
29
- this.fileToolTracker = null;
30
-
31
- // Initialize the dashboard
32
- this.init();
33
- }
34
-
35
- /**
36
- * Initialize the dashboard and all modules
37
- */
38
- init() {
39
- console.log('Initializing refactored Claude MPM Dashboard...');
40
-
41
- // Initialize modules in dependency order
42
- this.initializeSocketManager();
43
- this.initializeCoreComponents();
44
- this.initializeAgentInference();
45
- this.initializeUIStateManager();
46
- this.initializeWorkingDirectoryManager();
47
- this.initializeFileToolTracker();
48
- this.initializeEventProcessor();
49
- this.initializeExportManager();
50
-
51
- // Set up inter-module communication
52
- this.setupModuleInteractions();
53
-
54
- // Initialize from URL parameters
55
- this.initializeFromURL();
56
-
57
- console.log('Claude MPM Dashboard initialized successfully');
58
- }
59
-
60
- /**
61
- * Initialize socket manager
62
- */
63
- initializeSocketManager() {
64
- this.socketManager = new SocketManager();
65
-
66
- // Set up connection controls
67
- this.socketManager.setupConnectionControls();
68
-
69
- // Backward compatibility
70
- this.socketClient = this.socketManager.getSocketClient();
71
- window.socketClient = this.socketClient;
72
- }
73
-
74
- /**
75
- * Initialize core existing components
76
- */
77
- initializeCoreComponents() {
78
- // Initialize existing components with socket client
79
- this.eventViewer = new EventViewer('events-list', this.socketClient);
80
- this.moduleViewer = new ModuleViewer();
81
- this.sessionManager = new SessionManager(this.socketClient);
82
-
83
- // Backward compatibility
84
- window.eventViewer = this.eventViewer;
85
- window.moduleViewer = this.moduleViewer;
86
- window.sessionManager = this.sessionManager;
87
- }
88
-
89
- /**
90
- * Initialize agent inference system
91
- */
92
- initializeAgentInference() {
93
- this.agentInference = new AgentInference(this.eventViewer);
94
- this.agentInference.initialize();
95
- }
96
-
97
- /**
98
- * Initialize UI state manager
99
- */
100
- initializeUIStateManager() {
101
- this.uiStateManager = new UIStateManager();
102
- this.setupTabFilters(); // Set up filters after UI state manager
103
- }
104
-
105
- /**
106
- * Initialize working directory manager
107
- */
108
- initializeWorkingDirectoryManager() {
109
- this.workingDirectoryManager = new WorkingDirectoryManager(this.socketManager);
110
- }
111
-
112
- /**
113
- * Initialize file-tool tracker
114
- */
115
- initializeFileToolTracker() {
116
- this.fileToolTracker = new FileToolTracker(this.agentInference, this.workingDirectoryManager);
117
- }
118
-
119
- /**
120
- * Initialize event processor
121
- */
122
- initializeEventProcessor() {
123
- this.eventProcessor = new EventProcessor(this.eventViewer, this.agentInference);
124
- }
125
-
126
-
127
- /**
128
- * Initialize export manager
129
- */
130
- initializeExportManager() {
131
- this.exportManager = new ExportManager(this.eventViewer);
132
- }
133
-
134
- /**
135
- * Set up interactions between modules
136
- */
137
- setupModuleInteractions() {
138
- // Socket events to update file operations and tool calls
139
- this.socketManager.onEventUpdate((events) => {
140
- this.fileToolTracker.updateFileOperations(events);
141
- this.fileToolTracker.updateToolCalls(events);
142
-
143
- // Process agent inference for new events
144
- this.agentInference.processAgentInference();
145
-
146
- // Auto-scroll events list if on events tab
147
- if (this.uiStateManager.getCurrentTab() === 'events') {
148
- this.exportManager.scrollListToBottom('events-list');
149
- }
150
-
151
- // Re-render current tab
152
- this.renderCurrentTab();
153
- });
154
-
155
- // Connection status changes
156
- this.socketManager.onConnectionStatusChange((status, type) => {
157
- // Set up git branch listener when connected
158
- if (type === 'connected') {
159
- this.workingDirectoryManager.updateGitBranch(
160
- this.workingDirectoryManager.getCurrentWorkingDir()
161
- );
162
- }
163
- });
164
-
165
- // Tab changes
166
- document.addEventListener('tabChanged', (e) => {
167
- this.renderCurrentTab();
168
- this.uiStateManager.updateTabNavigationItems();
169
- });
170
-
171
- // Events clearing
172
- document.addEventListener('eventsClearing', () => {
173
- this.fileToolTracker.clear();
174
- this.agentInference.initialize();
175
- });
176
-
177
- // Card details requests
178
- document.addEventListener('showCardDetails', (e) => {
179
- this.showCardDetails(e.detail.tabName, e.detail.index);
180
- });
181
-
182
- // Session changes
183
- document.addEventListener('sessionFilterChanged', (e) => {
184
- console.log('Session filter changed, re-rendering current tab:', this.uiStateManager.getCurrentTab());
185
- this.renderCurrentTab();
186
- });
187
- }
188
-
189
- /**
190
- * Set up tab filters
191
- */
192
- setupTabFilters() {
193
- // Agents tab filters
194
- const agentsSearchInput = document.getElementById('agents-search-input');
195
- const agentsTypeFilter = document.getElementById('agents-type-filter');
196
-
197
- if (agentsSearchInput) {
198
- agentsSearchInput.addEventListener('input', () => {
199
- if (this.uiStateManager.getCurrentTab() === 'agents') this.renderCurrentTab();
200
- });
201
- }
202
-
203
- if (agentsTypeFilter) {
204
- agentsTypeFilter.addEventListener('change', () => {
205
- if (this.uiStateManager.getCurrentTab() === 'agents') this.renderCurrentTab();
206
- });
207
- }
208
-
209
- // Tools tab filters
210
- const toolsSearchInput = document.getElementById('tools-search-input');
211
- const toolsTypeFilter = document.getElementById('tools-type-filter');
212
-
213
- if (toolsSearchInput) {
214
- toolsSearchInput.addEventListener('input', () => {
215
- if (this.uiStateManager.getCurrentTab() === 'tools') this.renderCurrentTab();
216
- });
217
- }
218
-
219
- if (toolsTypeFilter) {
220
- toolsTypeFilter.addEventListener('change', () => {
221
- if (this.uiStateManager.getCurrentTab() === 'tools') this.renderCurrentTab();
222
- });
223
- }
224
-
225
- // Files tab filters
226
- const filesSearchInput = document.getElementById('files-search-input');
227
- const filesTypeFilter = document.getElementById('files-type-filter');
228
-
229
- if (filesSearchInput) {
230
- filesSearchInput.addEventListener('input', () => {
231
- if (this.uiStateManager.getCurrentTab() === 'files') this.renderCurrentTab();
232
- });
233
- }
234
-
235
- if (filesTypeFilter) {
236
- filesTypeFilter.addEventListener('change', () => {
237
- if (this.uiStateManager.getCurrentTab() === 'files') this.renderCurrentTab();
238
- });
239
- }
240
- }
241
-
242
- /**
243
- * Initialize from URL parameters
244
- */
245
- initializeFromURL() {
246
- const params = new URLSearchParams(window.location.search);
247
- this.socketManager.initializeFromURL(params);
248
- }
249
-
250
- /**
251
- * Render current tab content
252
- */
253
- renderCurrentTab() {
254
- const currentTab = this.uiStateManager.getCurrentTab();
255
-
256
- switch (currentTab) {
257
- case 'events':
258
- // Events tab is handled by EventViewer
259
- break;
260
- case 'agents':
261
- this.renderAgents();
262
- break;
263
- case 'tools':
264
- this.renderTools();
265
- break;
266
- case 'files':
267
- this.renderFiles();
268
- break;
269
- }
270
-
271
- // Update selection UI if we have a selected card
272
- const selectedCard = this.uiStateManager.getSelectedCard();
273
- if (selectedCard.tab === currentTab) {
274
- this.uiStateManager.updateCardSelectionUI();
275
- }
276
-
277
- // Update unified selection UI to maintain consistency
278
- this.uiStateManager.updateUnifiedSelectionUI();
279
- }
280
-
281
- /**
282
- * Render agents tab with unique instance view (one row per PM delegation)
283
- */
284
- renderAgents() {
285
- const agentsList = document.getElementById('agents-list');
286
- if (!agentsList) return;
287
-
288
- // Process agent inference to get PM delegations
289
- this.agentInference.processAgentInference();
290
-
291
- // Generate HTML for unique agent instances
292
- const events = this.eventProcessor.getFilteredEventsForTab('agents');
293
- const agentHTML = this.eventProcessor.generateAgentHTML(events);
294
-
295
- agentsList.innerHTML = agentHTML;
296
- this.exportManager.scrollListToBottom('agents-list');
297
-
298
- // Update filter dropdowns with unique instances
299
- const uniqueInstances = this.agentInference.getUniqueAgentInstances();
300
- this.updateAgentsFilterDropdowns(uniqueInstances);
301
- }
302
-
303
- /**
304
- * Render tools tab with unique instance view (one row per unique tool call)
305
- */
306
- renderTools() {
307
- const toolsList = document.getElementById('tools-list');
308
- if (!toolsList) return;
309
-
310
- const toolCalls = this.fileToolTracker.getToolCalls();
311
- const toolCallsArray = Array.from(toolCalls.entries());
312
- const uniqueToolInstances = this.eventProcessor.getUniqueToolInstances(toolCallsArray);
313
- const toolHTML = this.eventProcessor.generateToolHTML(uniqueToolInstances);
314
-
315
- toolsList.innerHTML = toolHTML;
316
- this.exportManager.scrollListToBottom('tools-list');
317
-
318
- // Update filter dropdowns
319
- this.updateToolsFilterDropdowns(uniqueToolInstances);
320
- }
321
-
322
- /**
323
- * Render files tab with unique instance view (one row per unique file)
324
- */
325
- renderFiles() {
326
- const filesList = document.getElementById('files-list');
327
- if (!filesList) return;
328
-
329
- const fileOperations = this.fileToolTracker.getFileOperations();
330
- const filesArray = Array.from(fileOperations.entries());
331
- const uniqueFileInstances = this.eventProcessor.getUniqueFileInstances(filesArray);
332
- const fileHTML = this.eventProcessor.generateFileHTML(uniqueFileInstances);
333
-
334
- filesList.innerHTML = fileHTML;
335
- this.exportManager.scrollListToBottom('files-list');
336
-
337
- // Update filter dropdowns
338
- this.updateFilesFilterDropdowns(filesArray);
339
- }
340
-
341
- /**
342
- * Update agents filter dropdowns for unique instances
343
- */
344
- updateAgentsFilterDropdowns(uniqueInstances) {
345
- const agentTypes = new Set();
346
-
347
- // uniqueInstances is already an array of unique agent instances
348
- uniqueInstances.forEach(instance => {
349
- if (instance.agentName && instance.agentName !== 'Unknown') {
350
- agentTypes.add(instance.agentName);
351
- }
352
- });
353
-
354
- const sortedTypes = Array.from(agentTypes).filter(type => type && type.trim() !== '');
355
- this.populateFilterDropdown('agents-type-filter', sortedTypes, 'All Agent Types');
356
-
357
- // Debug log
358
- if (sortedTypes.length > 0) {
359
- console.log('Agent types found for filter:', sortedTypes);
360
- } else {
361
- console.log('No agent types found for filter. Events:', events.length);
362
- }
363
- }
364
-
365
- /**
366
- * Update tools filter dropdowns
367
- */
368
- updateToolsFilterDropdowns(toolCallsArray) {
369
- const toolNames = [...new Set(toolCallsArray.map(([key, toolCall]) => toolCall.tool_name))]
370
- .filter(name => name);
371
-
372
- this.populateFilterDropdown('tools-type-filter', toolNames, 'All Tools');
373
- }
374
-
375
- /**
376
- * Update files filter dropdowns
377
- */
378
- updateFilesFilterDropdowns(filesArray) {
379
- const operations = [...new Set(filesArray.flatMap(([path, data]) =>
380
- data.operations.map(op => op.operation)
381
- ))].filter(op => op);
382
-
383
- this.populateFilterDropdown('files-type-filter', operations, 'All Operations');
384
- }
385
-
386
- /**
387
- * Populate filter dropdown with values
388
- */
389
- populateFilterDropdown(selectId, values, allOption = 'All') {
390
- const select = document.getElementById(selectId);
391
- if (!select) return;
392
-
393
- const currentValue = select.value;
394
- const sortedValues = values.sort((a, b) => a.localeCompare(b));
395
-
396
- // Clear existing options except the first "All" option
397
- select.innerHTML = `<option value="">${allOption}</option>`;
398
-
399
- // Add sorted values
400
- sortedValues.forEach(value => {
401
- const option = document.createElement('option');
402
- option.value = value;
403
- option.textContent = value;
404
- select.appendChild(option);
405
- });
406
-
407
- // Restore previous selection if it still exists
408
- if (currentValue && sortedValues.includes(currentValue)) {
409
- select.value = currentValue;
410
- }
411
- }
412
-
413
- /**
414
- * Show card details for specified tab and index
415
- */
416
- showCardDetails(tabName, index) {
417
- switch (tabName) {
418
- case 'events':
419
- if (this.eventViewer) {
420
- this.eventViewer.showEventDetails(index);
421
- }
422
- break;
423
- case 'agents':
424
- this.showAgentDetailsByIndex(index);
425
- break;
426
- case 'tools':
427
- this.showToolDetailsByIndex(index);
428
- break;
429
- case 'files':
430
- this.showFileDetailsByIndex(index);
431
- break;
432
- }
433
- }
434
-
435
- /**
436
- * Show agent details by index
437
- */
438
- showAgentDetailsByIndex(index) {
439
- const events = this.eventProcessor.getFilteredEventsForTab('agents');
440
-
441
- // Defensive checks
442
- if (!events || !Array.isArray(events) || index < 0 || index >= events.length) {
443
- console.warn('Dashboard: Invalid agent index or events array');
444
- return;
445
- }
446
-
447
- const filteredSingleEvent = this.eventProcessor.applyAgentsFilters([events[index]]);
448
-
449
- if (filteredSingleEvent.length > 0 && this.moduleViewer &&
450
- typeof this.moduleViewer.showAgentEvent === 'function') {
451
- const event = filteredSingleEvent[0];
452
- this.moduleViewer.showAgentEvent(event, index);
453
- }
454
- }
455
-
456
- /**
457
- * Show agent instance details for unique instance view
458
- * @param {string} instanceId - Agent instance ID
459
- */
460
- showAgentInstanceDetails(instanceId) {
461
- const pmDelegations = this.agentInference.getPMDelegations();
462
- const instance = pmDelegations.get(instanceId);
463
-
464
- if (!instance) {
465
- // Check if it's an implied delegation
466
- const uniqueInstances = this.agentInference.getUniqueAgentInstances();
467
- const impliedInstance = uniqueInstances.find(inst => inst.id === instanceId);
468
-
469
- if (!impliedInstance) {
470
- console.error('Agent instance not found:', instanceId);
471
- return;
472
- }
473
-
474
- // For implied instances, show basic info
475
- this.showImpliedAgentDetails(impliedInstance);
476
- return;
477
- }
478
-
479
- // Show full PM delegation details
480
- if (this.moduleViewer && typeof this.moduleViewer.showAgentInstance === 'function') {
481
- this.moduleViewer.showAgentInstance(instance);
482
- } else {
483
- // Fallback: show in console or basic modal
484
- console.log('Agent Instance Details:', {
485
- id: instanceId,
486
- agentName: instance.agentName,
487
- type: 'PM Delegation',
488
- eventCount: instance.agentEvents.length,
489
- startTime: instance.timestamp,
490
- pmCall: instance.pmCall
491
- });
492
- }
493
- }
494
-
495
- /**
496
- * Show implied agent details (agents without explicit PM delegation)
497
- * @param {Object} impliedInstance - Implied agent instance
498
- */
499
- showImpliedAgentDetails(impliedInstance) {
500
- if (this.moduleViewer && typeof this.moduleViewer.showImpliedAgent === 'function') {
501
- this.moduleViewer.showImpliedAgent(impliedInstance);
502
- } else {
503
- // Fallback: show in console or basic modal
504
- console.log('Implied Agent Details:', {
505
- id: impliedInstance.id,
506
- agentName: impliedInstance.agentName,
507
- type: 'Implied PM Delegation',
508
- eventCount: impliedInstance.eventCount,
509
- startTime: impliedInstance.timestamp,
510
- note: 'No explicit PM call found - inferred from agent activity'
511
- });
512
- }
513
- }
514
-
515
- /**
516
- * Show tool details by index
517
- */
518
- showToolDetailsByIndex(index) {
519
- const toolCalls = this.fileToolTracker.getToolCalls();
520
- const toolCallsArray = Array.from(toolCalls.entries());
521
- const filteredToolCalls = this.eventProcessor.applyToolCallFilters(toolCallsArray);
522
-
523
- if (index >= 0 && index < filteredToolCalls.length) {
524
- const [toolCallKey] = filteredToolCalls[index];
525
- this.showToolCallDetails(toolCallKey);
526
- }
527
- }
528
-
529
- /**
530
- * Show file details by index
531
- */
532
- showFileDetailsByIndex(index) {
533
- const fileOperations = this.fileToolTracker.getFileOperations();
534
- let filesArray = Array.from(fileOperations.entries());
535
- filesArray = this.eventProcessor.applyFilesFilters(filesArray);
536
-
537
- if (index >= 0 && index < filesArray.length) {
538
- const [filePath] = filesArray[index];
539
- this.showFileDetails(filePath);
540
- }
541
- }
542
-
543
- /**
544
- * Show tool call details
545
- */
546
- showToolCallDetails(toolCallKey) {
547
- const toolCall = this.fileToolTracker.getToolCall(toolCallKey);
548
- if (toolCall && this.moduleViewer) {
549
- this.moduleViewer.showToolCall(toolCall, toolCallKey);
550
- }
551
- }
552
-
553
- /**
554
- * Show file details
555
- */
556
- showFileDetails(filePath) {
557
- const fileData = this.fileToolTracker.getFileOperationsForFile(filePath);
558
- if (fileData && this.moduleViewer) {
559
- this.moduleViewer.showFileOperations(fileData, filePath);
560
- }
561
- }
562
-
563
- // ====================================
564
- // BACKWARD COMPATIBILITY METHODS
565
- // ====================================
566
-
567
- /**
568
- * Switch tab (backward compatibility)
569
- */
570
- switchTab(tabName) {
571
- this.uiStateManager.switchTab(tabName);
572
- }
573
-
574
- /**
575
- * Select card (backward compatibility)
576
- */
577
- selectCard(tabName, index, type, data) {
578
- this.uiStateManager.selectCard(tabName, index, type, data);
579
- }
580
-
581
- /**
582
- * Clear events (backward compatibility)
583
- */
584
- clearEvents() {
585
- this.exportManager.clearEvents();
586
- }
587
-
588
- /**
589
- * Export events (backward compatibility)
590
- */
591
- exportEvents() {
592
- this.exportManager.exportEvents();
593
- }
594
-
595
- /**
596
- * Clear selection (backward compatibility)
597
- */
598
- clearSelection() {
599
- this.uiStateManager.clearSelection();
600
- if (this.eventViewer) {
601
- this.eventViewer.clearSelection();
602
- }
603
- if (this.moduleViewer) {
604
- this.moduleViewer.clear();
605
- }
606
- }
607
-
608
-
609
- /**
610
- * Get current working directory (backward compatibility)
611
- */
612
- get currentWorkingDir() {
613
- return this.workingDirectoryManager.getCurrentWorkingDir();
614
- }
615
-
616
- /**
617
- * Set current working directory (backward compatibility)
618
- */
619
- set currentWorkingDir(dir) {
620
- this.workingDirectoryManager.setWorkingDirectory(dir);
621
- }
622
-
623
- /**
624
- * Get current tab (backward compatibility)
625
- */
626
- get currentTab() {
627
- return this.uiStateManager.getCurrentTab();
628
- }
629
-
630
- /**
631
- * Get selected card (backward compatibility)
632
- */
633
- get selectedCard() {
634
- return this.uiStateManager.getSelectedCard();
635
- }
636
-
637
- /**
638
- * Get file operations (backward compatibility)
639
- */
640
- get fileOperations() {
641
- return this.fileToolTracker.getFileOperations();
642
- }
643
-
644
- /**
645
- * Get tool calls (backward compatibility)
646
- */
647
- get toolCalls() {
648
- return this.fileToolTracker.getToolCalls();
649
- }
650
-
651
-
652
- /**
653
- * Get tab navigation state (backward compatibility)
654
- */
655
- get tabNavigation() {
656
- return this.uiStateManager ? this.uiStateManager.tabNavigation : null;
657
- }
658
- }
659
-
660
- // Global functions for backward compatibility
661
- window.clearEvents = function() {
662
- if (window.dashboard) {
663
- window.dashboard.clearEvents();
664
- }
665
- };
666
-
667
- window.exportEvents = function() {
668
- if (window.dashboard) {
669
- window.dashboard.exportEvents();
670
- }
671
- };
672
-
673
- window.clearSelection = function() {
674
- if (window.dashboard) {
675
- window.dashboard.clearSelection();
676
- }
677
- };
678
-
679
- window.switchTab = function(tabName) {
680
- if (window.dashboard) {
681
- window.dashboard.switchTab(tabName);
682
- }
683
- };
684
-
685
- // File Viewer Modal Functions - REMOVED DUPLICATE (keeping the one at line 1553)
686
- window.showFileViewerModal = function(filePath, workingDir) {
687
- // Use the dashboard's current working directory if not provided
688
- if (!workingDir && window.dashboard && window.dashboard.currentWorkingDir) {
689
- workingDir = window.dashboard.currentWorkingDir;
690
- }
691
-
692
- // Create modal if it doesn't exist
693
- let modal = document.getElementById('file-viewer-modal');
694
- if (!modal) {
695
- modal = createFileViewerModal();
696
- document.body.appendChild(modal);
697
- }
698
-
699
- // Update modal content
700
- updateFileViewerModal(modal, filePath, workingDir);
701
-
702
- // Show the modal as flex container
703
- modal.style.display = 'flex';
704
- document.body.style.overflow = 'hidden'; // Prevent background scrolling
705
- };
706
-
707
- window.hideFileViewerModal = function() {
708
- const modal = document.getElementById('file-viewer-modal');
709
- if (modal) {
710
- modal.style.display = 'none';
711
- document.body.style.overflow = ''; // Restore background scrolling
712
- }
713
- };
714
-
715
- window.copyFileContent = function() {
716
- const modal = document.getElementById('file-viewer-modal');
717
- if (!modal) return;
718
-
719
- const codeElement = modal.querySelector('.file-content-code');
720
- if (!codeElement) return;
721
-
722
- const text = codeElement.textContent;
723
-
724
- if (navigator.clipboard && navigator.clipboard.writeText) {
725
- navigator.clipboard.writeText(text).then(() => {
726
- // Show brief feedback
727
- const button = modal.querySelector('.file-content-copy');
728
- const originalText = button.textContent;
729
- button.textContent = '✅ Copied!';
730
- setTimeout(() => {
731
- button.textContent = originalText;
732
- }, 2000);
733
- }).catch(err => {
734
- console.error('Failed to copy text:', err);
735
- });
736
- } else {
737
- // Fallback for older browsers
738
- const textarea = document.createElement('textarea');
739
- textarea.value = text;
740
- document.body.appendChild(textarea);
741
- textarea.select();
742
- document.execCommand('copy');
743
- document.body.removeChild(textarea);
744
-
745
- const button = modal.querySelector('.file-content-copy');
746
- const originalText = button.textContent;
747
- button.textContent = '✅ Copied!';
748
- setTimeout(() => {
749
- button.textContent = originalText;
750
- }, 2000);
751
- }
752
- };
753
-
754
- function createFileViewerModal() {
755
- const modal = document.createElement('div');
756
- modal.id = 'file-viewer-modal';
757
- modal.className = 'modal file-viewer-modal';
758
-
759
- modal.innerHTML = `
760
- <div class="modal-content file-viewer-content">
761
- <div class="file-viewer-header">
762
- <h2 class="file-viewer-title">
763
- <span class="file-viewer-icon">📄</span>
764
- <span class="file-viewer-title-text">File Viewer</span>
765
- </h2>
766
- <div class="file-viewer-meta">
767
- <span class="file-viewer-file-path"></span>
768
- <span class="file-viewer-file-size"></span>
769
- </div>
770
- <button class="file-viewer-close" onclick="hideFileViewerModal()">
771
- <span>&times;</span>
772
- </button>
773
- </div>
774
- <div class="file-viewer-body">
775
- <div class="file-viewer-loading">
776
- <div class="loading-spinner"></div>
777
- <span>Loading file content...</span>
778
- </div>
779
- <div class="file-viewer-error" style="display: none;">
780
- <div class="error-icon">⚠️</div>
781
- <div class="error-message"></div>
782
- <div class="error-suggestions"></div>
783
- </div>
784
- <div class="file-viewer-content-area" style="display: none;">
785
- <div class="file-viewer-toolbar">
786
- <div class="file-viewer-info">
787
- <span class="file-extension"></span>
788
- <span class="file-encoding"></span>
789
- </div>
790
- <div class="file-viewer-actions">
791
- <button class="file-content-copy" onclick="copyFileContent()">
792
- 📋 Copy
793
- </button>
794
- </div>
795
- </div>
796
- <div class="file-viewer-scroll-wrapper">
797
- <pre class="file-content-display"><code class="file-content-code"></code></pre>
798
- </div>
799
- </div>
800
- </div>
801
- </div>
802
- `;
803
-
804
- // Close modal when clicking outside
805
- modal.addEventListener('click', (e) => {
806
- if (e.target === modal) {
807
- hideFileViewerModal();
808
- }
809
- });
810
-
811
- // Close modal with Escape key
812
- document.addEventListener('keydown', (e) => {
813
- if (e.key === 'Escape' && modal.style.display === 'flex') {
814
- hideFileViewerModal();
815
- }
816
- });
817
-
818
- return modal;
819
- }
820
-
821
- async function updateFileViewerModal(modal, filePath, workingDir) {
822
- // Update header info
823
- const filePathElement = modal.querySelector('.file-viewer-file-path');
824
- const fileSizeElement = modal.querySelector('.file-viewer-file-size');
825
-
826
- filePathElement.textContent = filePath;
827
- fileSizeElement.textContent = '';
828
-
829
- // Show loading state
830
- modal.querySelector('.file-viewer-loading').style.display = 'flex';
831
- modal.querySelector('.file-viewer-error').style.display = 'none';
832
- modal.querySelector('.file-viewer-content-area').style.display = 'none';
833
-
834
- try {
835
- // Get the Socket.IO client
836
- const socket = window.socket || window.dashboard?.socketClient?.socket;
837
- if (!socket) {
838
- throw new Error('No socket connection available');
839
- }
840
-
841
- // Set up one-time listener for file content response
842
- const responsePromise = new Promise((resolve, reject) => {
843
- const responseHandler = (data) => {
844
- if (data.file_path === filePath) {
845
- socket.off('file_content_response', responseHandler);
846
- if (data.success) {
847
- resolve(data);
848
- } else {
849
- reject(new Error(data.error || 'Failed to read file'));
850
- }
851
- }
852
- };
853
-
854
- socket.on('file_content_response', responseHandler);
855
-
856
- // Timeout after 10 seconds
857
- setTimeout(() => {
858
- socket.off('file_content_response', responseHandler);
859
- reject(new Error('Request timeout'));
860
- }, 10000);
861
- });
862
-
863
- // Send file read request
864
- socket.emit('read_file', {
865
- file_path: filePath,
866
- working_dir: workingDir
867
- });
868
-
869
- console.log('📄 File viewer request sent:', {
870
- filePath,
871
- workingDir
872
- });
873
-
874
- // Wait for response
875
- const result = await responsePromise;
876
- console.log('📦 File content received:', result);
877
-
878
- // Hide loading
879
- modal.querySelector('.file-viewer-loading').style.display = 'none';
880
-
881
- // Show successful content
882
- displayFileContent(modal, result);
883
-
884
- } catch (error) {
885
- console.error('❌ Failed to fetch file content:', error);
886
-
887
- modal.querySelector('.file-viewer-loading').style.display = 'none';
888
-
889
- // Create detailed error message
890
- let errorMessage = error.message || 'Unknown error occurred';
891
- let suggestions = [];
892
-
893
- if (error.message.includes('No socket connection')) {
894
- errorMessage = 'Failed to connect to the monitoring server';
895
- suggestions = [
896
- 'Check if the monitoring server is running',
897
- 'Verify the socket connection in the dashboard',
898
- 'Try refreshing the page and reconnecting'
899
- ];
900
- } else if (error.message.includes('timeout')) {
901
- errorMessage = 'Request timed out';
902
- suggestions = [
903
- 'The file may be too large to load quickly',
904
- 'Check your network connection',
905
- 'Try again in a few moments'
906
- ];
907
- } else if (error.message.includes('File does not exist')) {
908
- errorMessage = 'File not found';
909
- suggestions = [
910
- 'The file may have been moved or deleted',
911
- 'Check the file path spelling',
912
- 'Refresh the file list to see current files'
913
- ];
914
- } else if (error.message.includes('Access denied')) {
915
- errorMessage = 'Access denied';
916
- suggestions = [
917
- 'The file is outside the allowed directories',
918
- 'File access is restricted for security reasons'
919
- ];
920
- }
921
-
922
- displayFileError(modal, {
923
- error: errorMessage,
924
- file_path: filePath,
925
- working_dir: workingDir,
926
- suggestions: suggestions
927
- });
928
- }
929
- }
930
-
931
- function displayFileContent(modal, result) {
932
- console.log('📝 displayFileContent called with:', result);
933
- const contentArea = modal.querySelector('.file-viewer-content-area');
934
- const extensionElement = modal.querySelector('.file-extension');
935
- const encodingElement = modal.querySelector('.file-encoding');
936
- const fileSizeElement = modal.querySelector('.file-viewer-file-size');
937
- const codeElement = modal.querySelector('.file-content-code');
938
-
939
- // Update metadata
940
- if (extensionElement) extensionElement.textContent = `Type: ${result.extension || 'unknown'}`;
941
- if (encodingElement) encodingElement.textContent = `Encoding: ${result.encoding || 'unknown'}`;
942
- if (fileSizeElement) fileSizeElement.textContent = `Size: ${formatFileSize(result.file_size)}`;
943
-
944
- // Update content with basic syntax highlighting
945
- if (codeElement && result.content) {
946
- console.log('💡 Setting file content, length:', result.content.length);
947
- codeElement.innerHTML = highlightCode(result.content, result.extension);
948
-
949
- // Force scrolling to work by setting explicit heights
950
- const wrapper = modal.querySelector('.file-viewer-scroll-wrapper');
951
- if (wrapper) {
952
- // Give it a moment for content to render
953
- setTimeout(() => {
954
- const modalContent = modal.querySelector('.modal-content');
955
- const header = modal.querySelector('.file-viewer-header');
956
- const toolbar = modal.querySelector('.file-viewer-toolbar');
957
-
958
- const modalHeight = modalContent?.offsetHeight || 0;
959
- const headerHeight = header?.offsetHeight || 0;
960
- const toolbarHeight = toolbar?.offsetHeight || 0;
961
-
962
- const availableHeight = modalHeight - headerHeight - toolbarHeight - 40; // 40px for padding
963
-
964
- console.log('🎯 Setting file viewer scroll height:', {
965
- modalHeight,
966
- headerHeight,
967
- toolbarHeight,
968
- availableHeight
969
- });
970
-
971
- wrapper.style.maxHeight = `${availableHeight}px`;
972
- wrapper.style.overflowY = 'auto';
973
- }, 50);
974
- }
975
- } else {
976
- console.warn('⚠️ Missing codeElement or file content');
977
- }
978
-
979
- // Show content area
980
- if (contentArea) {
981
- contentArea.style.display = 'block';
982
- console.log('✅ File content area displayed');
983
- }
984
- }
985
-
986
- function displayFileError(modal, result) {
987
- const errorArea = modal.querySelector('.file-viewer-error');
988
- const messageElement = modal.querySelector('.error-message');
989
- const suggestionsElement = modal.querySelector('.error-suggestions');
990
-
991
- let errorMessage = result.error || 'Unknown error occurred';
992
-
993
- messageElement.innerHTML = `
994
- <div class="error-main">${errorMessage}</div>
995
- ${result.file_path ? `<div class="error-file">File: ${result.file_path}</div>` : ''}
996
- ${result.working_dir ? `<div class="error-dir">Working directory: ${result.working_dir}</div>` : ''}
997
- `;
998
-
999
- if (result.suggestions && result.suggestions.length > 0) {
1000
- suggestionsElement.innerHTML = `
1001
- <h4>Suggestions:</h4>
1002
- <ul>
1003
- ${result.suggestions.map(s => `<li>${s}</li>`).join('')}
1004
- </ul>
1005
- `;
1006
- } else {
1007
- suggestionsElement.innerHTML = '';
1008
- }
1009
-
1010
- console.log('📋 Displaying file viewer error:', {
1011
- originalError: result.error,
1012
- processedMessage: errorMessage,
1013
- suggestions: result.suggestions
1014
- });
1015
-
1016
- errorArea.style.display = 'block';
1017
- }
1018
-
1019
- function highlightCode(code, extension) {
1020
- /**
1021
- * Apply basic syntax highlighting to code content
1022
- * WHY: Provides basic highlighting for common file types to improve readability.
1023
- * This is a simple implementation that can be enhanced with full syntax highlighting
1024
- * libraries like highlight.js or Prism.js if needed.
1025
- */
1026
-
1027
- // Escape HTML entities first
1028
- const escaped = code
1029
- .replace(/&/g, '&amp;')
1030
- .replace(/</g, '&lt;')
1031
- .replace(/>/g, '&gt;');
1032
-
1033
- // Basic highlighting based on file extension
1034
- switch (extension) {
1035
- case '.js':
1036
- case '.jsx':
1037
- case '.ts':
1038
- case '.tsx':
1039
- return highlightJavaScript(escaped);
1040
- case '.py':
1041
- return highlightPython(escaped);
1042
- case '.json':
1043
- return highlightJSON(escaped);
1044
- case '.css':
1045
- return highlightCSS(escaped);
1046
- case '.html':
1047
- case '.htm':
1048
- return highlightHTML(escaped);
1049
- case '.md':
1050
- case '.markdown':
1051
- return highlightMarkdown(escaped);
1052
- default:
1053
- // Return with line numbers for plain text
1054
- return addLineNumbers(escaped);
1055
- }
1056
- }
1057
-
1058
- function highlightJavaScript(code) {
1059
- return addLineNumbers(code
1060
- .replace(/\b(function|const|let|var|if|else|for|while|return|import|export|class|extends)\b/g, '<span class="keyword">$1</span>')
1061
- .replace(/(\/\*[\s\S]*?\*\/|\/\/.*)/g, '<span class="comment">$1</span>')
1062
- .replace(/('[^']*'|"[^"]*"|`[^`]*`)/g, '<span class="string">$1</span>')
1063
- .replace(/\b(\d+)\b/g, '<span class="number">$1</span>'));
1064
- }
1065
-
1066
- function highlightPython(code) {
1067
- return addLineNumbers(code
1068
- .replace(/\b(def|class|if|elif|else|for|while|return|import|from|as|try|except|finally|with)\b/g, '<span class="keyword">$1</span>')
1069
- .replace(/(#.*)/g, '<span class="comment">$1</span>')
1070
- .replace(/('[^']*'|"[^"]*"|"""[\s\S]*?""")/g, '<span class="string">$1</span>')
1071
- .replace(/\b(\d+)\b/g, '<span class="number">$1</span>'));
1072
- }
1073
-
1074
- function highlightJSON(code) {
1075
- return addLineNumbers(code
1076
- .replace(/("[\w\s]*")\s*:/g, '<span class="property">$1</span>:')
1077
- .replace(/:\s*(".*?")/g, ': <span class="string">$1</span>')
1078
- .replace(/:\s*(\d+)/g, ': <span class="number">$1</span>')
1079
- .replace(/:\s*(true|false|null)/g, ': <span class="keyword">$1</span>'));
1080
- }
1081
-
1082
- function highlightCSS(code) {
1083
- return addLineNumbers(code
1084
- .replace(/([.#]?[\w-]+)\s*\{/g, '<span class="selector">$1</span> {')
1085
- .replace(/([\w-]+)\s*:/g, '<span class="property">$1</span>:')
1086
- .replace(/:\s*([^;]+);/g, ': <span class="value">$1</span>;')
1087
- .replace(/(\/\*[\s\S]*?\*\/)/g, '<span class="comment">$1</span>'));
1088
- }
1089
-
1090
- function highlightHTML(code) {
1091
- return addLineNumbers(code
1092
- .replace(/(&lt;\/?[\w-]+)/g, '<span class="tag">$1</span>')
1093
- .replace(/([\w-]+)=(['"][^'"]*['"])/g, '<span class="attribute">$1</span>=<span class="string">$2</span>')
1094
- .replace(/(&lt;!--[\s\S]*?--&gt;)/g, '<span class="comment">$1</span>'));
1095
- }
1096
-
1097
- function highlightMarkdown(code) {
1098
- return addLineNumbers(code
1099
- .replace(/^(#{1,6})\s+(.*)$/gm, '<span class="header">$1</span> <span class="header-text">$2</span>')
1100
- .replace(/\*\*(.*?)\*\*/g, '<span class="bold">**$1**</span>')
1101
- .replace(/\*(.*?)\*/g, '<span class="italic">*$1*</span>')
1102
- .replace(/`([^`]+)`/g, '<span class="code">`$1`</span>')
1103
- .replace(/^\s*[-*+]\s+(.*)$/gm, '<span class="list-marker">•</span> $1'));
1104
- }
1105
-
1106
- function addLineNumbers(code) {
1107
- const lines = code.split('\n');
1108
- return lines.map((line, index) =>
1109
- `<span class="line-number">${String(index + 1).padStart(3, ' ')}</span> ${line || ' '}`
1110
- ).join('\n');
1111
- }
1112
-
1113
- function formatFileSize(bytes) {
1114
- if (!bytes) return '0 B';
1115
- const k = 1024;
1116
- const sizes = ['B', 'KB', 'MB', 'GB'];
1117
- const i = Math.floor(Math.log(bytes) / Math.log(k));
1118
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
1119
- }
1120
-
1121
- // Git Diff Modal Functions - restored from original dashboard
1122
- window.showGitDiffModal = function(filePath, timestamp, workingDir) {
1123
- // Use the dashboard's current working directory if not provided
1124
- if (!workingDir && window.dashboard && window.dashboard.currentWorkingDir) {
1125
- workingDir = window.dashboard.currentWorkingDir;
1126
- }
1127
-
1128
- // Create modal if it doesn't exist
1129
- let modal = document.getElementById('git-diff-modal');
1130
- if (!modal) {
1131
- modal = createGitDiffModal();
1132
- document.body.appendChild(modal);
1133
- }
1134
-
1135
- // Update modal content
1136
- updateGitDiffModal(modal, filePath, timestamp, workingDir);
1137
-
1138
- // Show the modal as flex container
1139
- modal.style.display = 'flex';
1140
- document.body.style.overflow = 'hidden'; // Prevent background scrolling
1141
- };
1142
-
1143
- window.hideGitDiffModal = function() {
1144
- const modal = document.getElementById('git-diff-modal');
1145
- if (modal) {
1146
- modal.style.display = 'none';
1147
- document.body.style.overflow = ''; // Restore background scrolling
1148
- }
1149
- };
1150
-
1151
- window.copyGitDiff = function() {
1152
- const modal = document.getElementById('git-diff-modal');
1153
- if (!modal) return;
1154
-
1155
- const codeElement = modal.querySelector('.git-diff-code');
1156
- if (!codeElement) return;
1157
-
1158
- const text = codeElement.textContent;
1159
-
1160
- if (navigator.clipboard && navigator.clipboard.writeText) {
1161
- navigator.clipboard.writeText(text).then(() => {
1162
- // Show brief feedback
1163
- const button = modal.querySelector('.git-diff-copy');
1164
- const originalText = button.textContent;
1165
- button.textContent = '✅ Copied!';
1166
- setTimeout(() => {
1167
- button.textContent = originalText;
1168
- }, 2000);
1169
- }).catch(err => {
1170
- console.error('Failed to copy text:', err);
1171
- });
1172
- } else {
1173
- // Fallback for older browsers
1174
- const textarea = document.createElement('textarea');
1175
- textarea.value = text;
1176
- document.body.appendChild(textarea);
1177
- textarea.select();
1178
- document.execCommand('copy');
1179
- document.body.removeChild(textarea);
1180
-
1181
- const button = modal.querySelector('.git-diff-copy');
1182
- const originalText = button.textContent;
1183
- button.textContent = '✅ Copied!';
1184
- setTimeout(() => {
1185
- button.textContent = originalText;
1186
- }, 2000);
1187
- }
1188
- };
1189
-
1190
- function createGitDiffModal() {
1191
- const modal = document.createElement('div');
1192
- modal.id = 'git-diff-modal';
1193
- modal.className = 'modal git-diff-modal';
1194
-
1195
- modal.innerHTML = `
1196
- <div class="modal-content git-diff-content">
1197
- <div class="git-diff-header">
1198
- <h2 class="git-diff-title">
1199
- <span class="git-diff-icon">📋</span>
1200
- <span class="git-diff-title-text">Git Diff</span>
1201
- </h2>
1202
- <div class="git-diff-meta">
1203
- <span class="git-diff-file-path"></span>
1204
- <span class="git-diff-timestamp"></span>
1205
- </div>
1206
- <button class="git-diff-close" onclick="hideGitDiffModal()">
1207
- <span>&times;</span>
1208
- </button>
1209
- </div>
1210
- <div class="git-diff-body">
1211
- <div class="git-diff-loading">
1212
- <div class="loading-spinner"></div>
1213
- <span>Loading git diff...</span>
1214
- </div>
1215
- <div class="git-diff-error" style="display: none;">
1216
- <div class="error-icon">⚠️</div>
1217
- <div class="error-message"></div>
1218
- <div class="error-suggestions"></div>
1219
- </div>
1220
- <div class="git-diff-content-area" style="display: none;">
1221
- <div class="git-diff-toolbar">
1222
- <div class="git-diff-info">
1223
- <span class="commit-hash"></span>
1224
- <span class="diff-method"></span>
1225
- </div>
1226
- <div class="git-diff-actions">
1227
- <button class="git-diff-copy" onclick="copyGitDiff()">
1228
- 📋 Copy
1229
- </button>
1230
- </div>
1231
- </div>
1232
- <div class="git-diff-scroll-wrapper">
1233
- <pre class="git-diff-display"><code class="git-diff-code"></code></pre>
1234
- </div>
1235
- </div>
1236
- </div>
1237
- </div>
1238
- `;
1239
-
1240
- // Close modal when clicking outside
1241
- modal.addEventListener('click', (e) => {
1242
- if (e.target === modal) {
1243
- hideGitDiffModal();
1244
- }
1245
- });
1246
-
1247
- // Close modal with Escape key
1248
- document.addEventListener('keydown', (e) => {
1249
- if (e.key === 'Escape' && modal.style.display === 'flex') {
1250
- hideGitDiffModal();
1251
- }
1252
- });
1253
-
1254
- return modal;
1255
- }
1256
-
1257
- async function updateGitDiffModal(modal, filePath, timestamp, workingDir) {
1258
- // Update header info
1259
- const filePathElement = modal.querySelector('.git-diff-file-path');
1260
- const timestampElement = modal.querySelector('.git-diff-timestamp');
1261
-
1262
- filePathElement.textContent = filePath;
1263
- timestampElement.textContent = timestamp ? new Date(timestamp).toLocaleString() : 'Latest';
1264
-
1265
- // Show loading state
1266
- modal.querySelector('.git-diff-loading').style.display = 'flex';
1267
- modal.querySelector('.git-diff-error').style.display = 'none';
1268
- modal.querySelector('.git-diff-content-area').style.display = 'none';
1269
-
1270
- try {
1271
- // Get the Socket.IO server port with multiple fallbacks
1272
- let port = 8765; // Default fallback
1273
-
1274
- // Try to get port from socketClient first
1275
- if (window.dashboard && window.dashboard.socketClient && window.dashboard.socketClient.port) {
1276
- port = window.dashboard.socketClient.port;
1277
- }
1278
- // Fallback to port input field if socketClient port is not available
1279
- else {
1280
- const portInput = document.getElementById('port-input');
1281
- if (portInput && portInput.value) {
1282
- port = portInput.value;
1283
- }
1284
- }
1285
-
1286
- // Build URL parameters
1287
- const params = new URLSearchParams({
1288
- file: filePath
1289
- });
1290
-
1291
- if (timestamp) {
1292
- params.append('timestamp', timestamp);
1293
- }
1294
- if (workingDir) {
1295
- params.append('working_dir', workingDir);
1296
- }
1297
-
1298
- const requestUrl = `http://localhost:${port}/api/git-diff?${params}`;
1299
- console.log('🌐 Making git diff request to:', requestUrl);
1300
- console.log('📋 Git diff request parameters:', {
1301
- filePath,
1302
- timestamp,
1303
- workingDir,
1304
- urlParams: params.toString()
1305
- });
1306
-
1307
- // Test server connectivity first
1308
- try {
1309
- const healthResponse = await fetch(`http://localhost:${port}/health`, {
1310
- method: 'GET',
1311
- headers: {
1312
- 'Accept': 'application/json',
1313
- 'Content-Type': 'application/json'
1314
- },
1315
- mode: 'cors'
1316
- });
1317
-
1318
- if (!healthResponse.ok) {
1319
- throw new Error(`Server health check failed: ${healthResponse.status} ${healthResponse.statusText}`);
1320
- }
1321
-
1322
- console.log('✅ Server health check passed');
1323
- } catch (healthError) {
1324
- throw new Error(`Cannot reach server at localhost:${port}. Health check failed: ${healthError.message}`);
1325
- }
1326
-
1327
- // Make the actual git diff request
1328
- const response = await fetch(requestUrl, {
1329
- method: 'GET',
1330
- headers: {
1331
- 'Accept': 'application/json',
1332
- 'Content-Type': 'application/json'
1333
- },
1334
- mode: 'cors'
1335
- });
1336
-
1337
- if (!response.ok) {
1338
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1339
- }
1340
-
1341
- const result = await response.json();
1342
- console.log('📦 Git diff response:', result);
1343
-
1344
- // Hide loading
1345
- modal.querySelector('.git-diff-loading').style.display = 'none';
1346
-
1347
- if (result.success) {
1348
- console.log('📊 Displaying successful git diff');
1349
- // Show successful diff
1350
- displayGitDiff(modal, result);
1351
- } else {
1352
- console.log('⚠️ Displaying git diff error:', result);
1353
- // Show error
1354
- displayGitDiffError(modal, result);
1355
- }
1356
-
1357
- } catch (error) {
1358
- console.error('❌ Failed to fetch git diff:', error);
1359
- console.error('Error details:', {
1360
- name: error.name,
1361
- message: error.message,
1362
- stack: error.stack,
1363
- filePath,
1364
- timestamp,
1365
- workingDir
1366
- });
1367
-
1368
- modal.querySelector('.git-diff-loading').style.display = 'none';
1369
-
1370
- // Create detailed error message based on error type
1371
- let errorMessage = `Network error: ${error.message}`;
1372
- let suggestions = [];
1373
-
1374
- if (error.message.includes('Failed to fetch')) {
1375
- errorMessage = 'Failed to connect to the monitoring server';
1376
- suggestions = [
1377
- 'Check if the monitoring server is running on port 8765',
1378
- 'Verify the port configuration in the dashboard',
1379
- 'Check browser console for CORS or network errors',
1380
- 'Try refreshing the page and reconnecting'
1381
- ];
1382
- } else if (error.message.includes('health check failed')) {
1383
- errorMessage = error.message;
1384
- suggestions = [
1385
- 'The server may be starting up - try again in a few seconds',
1386
- 'Check if another process is using port 8765',
1387
- 'Restart the claude-mpm monitoring server'
1388
- ];
1389
- } else if (error.message.includes('HTTP')) {
1390
- errorMessage = `Server error: ${error.message}`;
1391
- suggestions = [
1392
- 'The server encountered an internal error',
1393
- 'Check the server logs for more details',
1394
- 'Try with a different file or working directory'
1395
- ];
1396
- }
1397
-
1398
- displayGitDiffError(modal, {
1399
- error: errorMessage,
1400
- file_path: filePath,
1401
- working_dir: workingDir,
1402
- suggestions: suggestions,
1403
- debug_info: {
1404
- error_type: error.name,
1405
- original_message: error.message,
1406
- port: window.dashboard?.socketClient?.port || document.getElementById('port-input')?.value || '8765',
1407
- timestamp: new Date().toISOString()
1408
- }
1409
- });
1410
- }
1411
- }
1412
-
1413
- function highlightGitDiff(diffText) {
1414
- /**
1415
- * Apply basic syntax highlighting to git diff output
1416
- * WHY: Git diffs have a standard format that can be highlighted for better readability:
1417
- * - Lines starting with '+' are additions (green)
1418
- * - Lines starting with '-' are deletions (red)
1419
- * - Lines starting with '@@' are context headers (blue)
1420
- * - File headers and metadata get special formatting
1421
- */
1422
- return diffText
1423
- .split('\n')
1424
- .map(line => {
1425
- // Escape HTML entities
1426
- const escaped = line
1427
- .replace(/&/g, '&amp;')
1428
- .replace(/</g, '&lt;')
1429
- .replace(/>/g, '&gt;');
1430
-
1431
- // Apply diff highlighting
1432
- if (line.startsWith('+++') || line.startsWith('---')) {
1433
- return `<span class="diff-header">${escaped}</span>`;
1434
- } else if (line.startsWith('@@')) {
1435
- return `<span class="diff-meta">${escaped}</span>`;
1436
- } else if (line.startsWith('+')) {
1437
- return `<span class="diff-addition">${escaped}</span>`;
1438
- } else if (line.startsWith('-')) {
1439
- return `<span class="diff-deletion">${escaped}</span>`;
1440
- } else if (line.startsWith('commit ') || line.startsWith('Author:') || line.startsWith('Date:')) {
1441
- return `<span class="diff-header">${escaped}</span>`;
1442
- } else {
1443
- return `<span class="diff-context">${escaped}</span>`;
1444
- }
1445
- })
1446
- .join('\n');
1447
- }
1448
-
1449
- function displayGitDiff(modal, result) {
1450
- console.log('📝 displayGitDiff called with:', result);
1451
- const contentArea = modal.querySelector('.git-diff-content-area');
1452
- const commitHashElement = modal.querySelector('.commit-hash');
1453
- const methodElement = modal.querySelector('.diff-method');
1454
- const codeElement = modal.querySelector('.git-diff-code');
1455
-
1456
- console.log('🔍 Elements found:', {
1457
- contentArea: !!contentArea,
1458
- commitHashElement: !!commitHashElement,
1459
- methodElement: !!methodElement,
1460
- codeElement: !!codeElement
1461
- });
1462
-
1463
- // Update metadata
1464
- if (commitHashElement) commitHashElement.textContent = `Commit: ${result.commit_hash}`;
1465
- if (methodElement) methodElement.textContent = `Method: ${result.method}`;
1466
-
1467
- // Update diff content with basic syntax highlighting
1468
- if (codeElement && result.diff) {
1469
- console.log('💡 Setting diff content, length:', result.diff.length);
1470
- codeElement.innerHTML = highlightGitDiff(result.diff);
1471
-
1472
- // Force scrolling to work by setting explicit heights
1473
- const wrapper = modal.querySelector('.git-diff-scroll-wrapper');
1474
- if (wrapper) {
1475
- // Give it a moment for content to render
1476
- setTimeout(() => {
1477
- const modalContent = modal.querySelector('.modal-content');
1478
- const header = modal.querySelector('.git-diff-header');
1479
- const toolbar = modal.querySelector('.git-diff-toolbar');
1480
-
1481
- const modalHeight = modalContent?.offsetHeight || 0;
1482
- const headerHeight = header?.offsetHeight || 0;
1483
- const toolbarHeight = toolbar?.offsetHeight || 0;
1484
-
1485
- const availableHeight = modalHeight - headerHeight - toolbarHeight - 40; // 40px for padding
1486
-
1487
- console.log('🎯 Setting explicit scroll height:', {
1488
- modalHeight,
1489
- headerHeight,
1490
- toolbarHeight,
1491
- availableHeight
1492
- });
1493
-
1494
- wrapper.style.maxHeight = `${availableHeight}px`;
1495
- wrapper.style.overflowY = 'auto';
1496
- }, 50);
1497
- }
1498
- } else {
1499
- console.warn('⚠️ Missing codeElement or diff data');
1500
- }
1501
-
1502
- // Show content area
1503
- if (contentArea) {
1504
- contentArea.style.display = 'block';
1505
- console.log('✅ Content area displayed');
1506
- }
1507
- }
1508
-
1509
- function displayGitDiffError(modal, result) {
1510
- const errorArea = modal.querySelector('.git-diff-error');
1511
- const messageElement = modal.querySelector('.error-message');
1512
- const suggestionsElement = modal.querySelector('.error-suggestions');
1513
-
1514
- // Create more user-friendly error messages
1515
- let errorMessage = result.error || 'Unknown error occurred';
1516
- let isUntracked = false;
1517
-
1518
- if (errorMessage.includes('not tracked by git')) {
1519
- errorMessage = '📝 This file is not tracked by git yet';
1520
- isUntracked = true;
1521
- } else if (errorMessage.includes('No git history found')) {
1522
- errorMessage = '📋 No git history available for this file';
1523
- }
1524
-
1525
- messageElement.innerHTML = `
1526
- <div class="error-main">${errorMessage}</div>
1527
- ${result.file_path ? `<div class="error-file">File: ${result.file_path}</div>` : ''}
1528
- ${result.working_dir ? `<div class="error-dir">Working directory: ${result.working_dir}</div>` : ''}
1529
- `;
1530
-
1531
- if (result.suggestions && result.suggestions.length > 0) {
1532
- const suggestionTitle = isUntracked ? 'How to track this file:' : 'Suggestions:';
1533
- suggestionsElement.innerHTML = `
1534
- <h4>${suggestionTitle}</h4>
1535
- <ul>
1536
- ${result.suggestions.map(s => `<li>${s}</li>`).join('')}
1537
- </ul>
1538
- `;
1539
- } else {
1540
- suggestionsElement.innerHTML = '';
1541
- }
1542
-
1543
- console.log('📋 Displaying git diff error:', {
1544
- originalError: result.error,
1545
- processedMessage: errorMessage,
1546
- isUntracked,
1547
- suggestions: result.suggestions
1548
- });
1549
-
1550
- errorArea.style.display = 'block';
1551
- }
1552
-
1553
- // File Viewer Modal Functions
1554
- window.showFileViewerModal = function(filePath) {
1555
- // Use the dashboard's current working directory
1556
- let workingDir = '';
1557
- if (window.dashboard && window.dashboard.currentWorkingDir) {
1558
- workingDir = window.dashboard.currentWorkingDir;
1559
- }
1560
-
1561
- // Create modal if it doesn't exist
1562
- let modal = document.getElementById('file-viewer-modal');
1563
- if (!modal) {
1564
- modal = createFileViewerModal();
1565
- document.body.appendChild(modal);
1566
- }
1567
-
1568
- // Update modal content
1569
- updateFileViewerModal(modal, filePath, workingDir);
1570
-
1571
- // Show the modal as flex container
1572
- modal.style.display = 'flex';
1573
- document.body.style.overflow = 'hidden'; // Prevent background scrolling
1574
- };
1575
-
1576
- window.hideFileViewerModal = function() {
1577
- const modal = document.getElementById('file-viewer-modal');
1578
- if (modal) {
1579
- modal.style.display = 'none';
1580
- document.body.style.overflow = ''; // Restore background scrolling
1581
- }
1582
- };
1583
-
1584
- window.copyFileContent = function() {
1585
- const modal = document.getElementById('file-viewer-modal');
1586
- if (!modal) return;
1587
-
1588
- const codeElement = modal.querySelector('.file-content-code');
1589
- if (!codeElement) return;
1590
-
1591
- const text = codeElement.textContent;
1592
-
1593
- if (navigator.clipboard && navigator.clipboard.writeText) {
1594
- navigator.clipboard.writeText(text).then(() => {
1595
- // Show brief feedback
1596
- const button = modal.querySelector('.file-content-copy');
1597
- const originalText = button.textContent;
1598
- button.textContent = '✅ Copied!';
1599
- setTimeout(() => {
1600
- button.textContent = originalText;
1601
- }, 2000);
1602
- }).catch(err => {
1603
- console.error('Failed to copy text:', err);
1604
- });
1605
- } else {
1606
- // Fallback for older browsers
1607
- const textarea = document.createElement('textarea');
1608
- textarea.value = text;
1609
- document.body.appendChild(textarea);
1610
- textarea.select();
1611
- document.execCommand('copy');
1612
- document.body.removeChild(textarea);
1613
-
1614
- const button = modal.querySelector('.file-content-copy');
1615
- const originalText = button.textContent;
1616
- button.textContent = '✅ Copied!';
1617
- setTimeout(() => {
1618
- button.textContent = originalText;
1619
- }, 2000);
1620
- }
1621
- };
1622
-
1623
- function createFileViewerModal() {
1624
- const modal = document.createElement('div');
1625
- modal.id = 'file-viewer-modal';
1626
- modal.className = 'modal file-viewer-modal';
1627
-
1628
- modal.innerHTML = `
1629
- <div class="modal-content file-viewer-content">
1630
- <div class="file-viewer-header">
1631
- <h2 class="file-viewer-title">
1632
- <span class="file-viewer-icon">👁️</span>
1633
- <span class="file-viewer-title-text">File Viewer</span>
1634
- </h2>
1635
- <div class="file-viewer-meta">
1636
- <span class="file-viewer-file-path"></span>
1637
- <span class="file-viewer-file-size"></span>
1638
- </div>
1639
- <button class="file-viewer-close" onclick="hideFileViewerModal()">
1640
- <span>&times;</span>
1641
- </button>
1642
- </div>
1643
- <div class="file-viewer-body">
1644
- <div class="file-viewer-loading">
1645
- <div class="loading-spinner"></div>
1646
- <span>Loading file contents...</span>
1647
- </div>
1648
- <div class="file-viewer-error" style="display: none;">
1649
- <div class="error-icon">⚠️</div>
1650
- <div class="error-message"></div>
1651
- <div class="error-suggestions"></div>
1652
- </div>
1653
- <div class="file-viewer-content-area" style="display: none;">
1654
- <div class="file-viewer-toolbar">
1655
- <div class="file-viewer-info">
1656
- <span class="file-extension"></span>
1657
- <span class="file-encoding">UTF-8</span>
1658
- </div>
1659
- <div class="file-viewer-actions">
1660
- <button class="file-content-copy" onclick="copyFileContent()">
1661
- 📋 Copy
1662
- </button>
1663
- </div>
1664
- </div>
1665
- <div class="file-viewer-scroll-wrapper">
1666
- <pre class="file-content-display line-numbers"><code class="file-content-code"></code></pre>
1667
- </div>
1668
- </div>
1669
- </div>
1670
- </div>
1671
- `;
1672
-
1673
- // Close modal when clicking outside
1674
- modal.addEventListener('click', (e) => {
1675
- if (e.target === modal) {
1676
- hideFileViewerModal();
1677
- }
1678
- });
1679
-
1680
- // Close modal with Escape key
1681
- document.addEventListener('keydown', (e) => {
1682
- if (e.key === 'Escape' && modal.style.display === 'flex') {
1683
- hideFileViewerModal();
1684
- }
1685
- });
1686
-
1687
- return modal;
1688
- }
1689
-
1690
- async function updateFileViewerModal(modal, filePath, workingDir) {
1691
- // Update header info
1692
- const filePathElement = modal.querySelector('.file-viewer-file-path');
1693
- const fileSizeElement = modal.querySelector('.file-viewer-file-size');
1694
- const fileExtensionElement = modal.querySelector('.file-extension');
1695
-
1696
- filePathElement.textContent = filePath;
1697
-
1698
- // Extract and display file extension
1699
- const extension = filePath.split('.').pop() || 'txt';
1700
- fileExtensionElement.textContent = `Type: ${extension.toUpperCase()}`;
1701
-
1702
- // Show loading state
1703
- modal.querySelector('.file-viewer-loading').style.display = 'flex';
1704
- modal.querySelector('.file-viewer-error').style.display = 'none';
1705
- modal.querySelector('.file-viewer-content-area').style.display = 'none';
1706
-
1707
- try {
1708
- // Get the Socket.IO server port with multiple fallbacks
1709
- let port = 8765; // Default fallback
1710
-
1711
- // Try to get port from socketClient first
1712
- if (window.dashboard && window.dashboard.socketClient && window.dashboard.socketClient.port) {
1713
- port = window.dashboard.socketClient.port;
1714
- }
1715
- // Fallback to port input field if socketClient port is not available
1716
- else if (document.getElementById('port-input')) {
1717
- const portInput = document.getElementById('port-input');
1718
- if (portInput.value && !isNaN(portInput.value)) {
1719
- port = parseInt(portInput.value);
1720
- }
1721
- }
1722
-
1723
- // Construct API request URL for file content
1724
- const params = new URLSearchParams();
1725
- params.append('file_path', filePath);
1726
- if (workingDir) {
1727
- params.append('working_dir', workingDir);
1728
- }
1729
-
1730
- const requestUrl = `http://localhost:${port}/api/file-content?${params}`;
1731
- console.log('🌐 Making file content request to:', requestUrl);
1732
- console.log('📄 File content request parameters:', {
1733
- filePath,
1734
- workingDir,
1735
- urlParams: params.toString()
1736
- });
1737
-
1738
- // Test server connectivity first
1739
- try {
1740
- const healthResponse = await fetch(`http://localhost:${port}/health`, {
1741
- method: 'GET',
1742
- mode: 'cors',
1743
- timeout: 5000
1744
- });
1745
-
1746
- if (!healthResponse.ok) {
1747
- throw new Error(`Health check failed: ${healthResponse.status}`);
1748
- }
1749
-
1750
- console.log('✅ Server health check passed');
1751
- } catch (healthError) {
1752
- console.warn('⚠️ Server health check failed:', healthError);
1753
- throw new Error(`Unable to connect to monitoring server on port ${port}. Please ensure the server is running.`);
1754
- }
1755
-
1756
- // Make the actual file content request
1757
- const response = await fetch(requestUrl, {
1758
- method: 'GET',
1759
- headers: {
1760
- 'Accept': 'application/json',
1761
- 'Content-Type': 'application/json'
1762
- },
1763
- mode: 'cors'
1764
- });
1765
-
1766
- if (!response.ok) {
1767
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1768
- }
1769
-
1770
- const result = await response.json();
1771
- console.log('📦 File content received:', result);
1772
-
1773
- // Hide loading
1774
- modal.querySelector('.file-viewer-loading').style.display = 'none';
1775
-
1776
- if (result.success) {
1777
- console.log('📊 Displaying file content');
1778
- // Show file content
1779
- displayFileContent(modal, result, extension);
1780
- } else {
1781
- console.log('⚠️ Displaying file content error:', result);
1782
- // Show error
1783
- displayFileContentError(modal, result);
1784
- }
1785
-
1786
- } catch (error) {
1787
- console.error('❌ Failed to fetch file content:', error);
1788
-
1789
- modal.querySelector('.file-viewer-loading').style.display = 'none';
1790
-
1791
- // Create detailed error message based on error type
1792
- let errorMessage = `Network error: ${error.message}`;
1793
- let suggestions = [];
1794
-
1795
- if (error.message.includes('Failed to fetch')) {
1796
- errorMessage = 'Failed to connect to the monitoring server';
1797
- suggestions = [
1798
- 'Check if the monitoring server is running on port 8765',
1799
- 'Verify the port configuration in the dashboard',
1800
- 'Ensure CORS is enabled on the server'
1801
- ];
1802
- } else if (error.message.includes('Health check failed')) {
1803
- errorMessage = 'Unable to connect to monitoring server';
1804
- suggestions = [
1805
- 'The server may be starting up - try again in a few seconds',
1806
- 'Check if another process is using the port',
1807
- 'Restart the claude-mpm monitoring server'
1808
- ];
1809
- } else if (error.message.includes('HTTP 404')) {
1810
- errorMessage = 'File content API endpoint not found';
1811
- suggestions = [
1812
- 'The server may be running an older version',
1813
- 'Try restarting the monitoring server',
1814
- 'Check server logs for errors'
1815
- ];
1816
- } else if (error.message.includes('timeout')) {
1817
- errorMessage = 'Request timed out while fetching file content';
1818
- suggestions = [
1819
- 'File may be too large or server is slow',
1820
- 'Try again in a moment',
1821
- 'Check server logs for performance issues'
1822
- ];
1823
- }
1824
-
1825
- displayFileContentError(modal, {
1826
- success: false,
1827
- error: errorMessage,
1828
- suggestions: suggestions
1829
- });
1830
- }
1831
- }
1832
-
1833
- function displayFileContent(modal, result, extension) {
1834
- console.log('📝 displayFileContent called with:', result);
1835
- const contentArea = modal.querySelector('.file-viewer-content-area');
1836
- const fileSizeElement = modal.querySelector('.file-viewer-file-size');
1837
- const codeElement = modal.querySelector('.file-content-code');
1838
-
1839
- console.log('🔍 File content elements found:', {
1840
- contentArea: !!contentArea,
1841
- fileSizeElement: !!fileSizeElement,
1842
- codeElement: !!codeElement
1843
- });
1844
-
1845
- // Update file size
1846
- if (fileSizeElement && result.file_size) {
1847
- const sizeInBytes = result.file_size;
1848
- const sizeFormatted = sizeInBytes < 1024
1849
- ? `${sizeInBytes} bytes`
1850
- : sizeInBytes < 1024 * 1024
1851
- ? `${(sizeInBytes / 1024).toFixed(1)} KB`
1852
- : `${(sizeInBytes / (1024 * 1024)).toFixed(1)} MB`;
1853
- fileSizeElement.textContent = `Size: ${sizeFormatted}`;
1854
- }
1855
-
1856
- // Set up content with syntax highlighting
1857
- if (codeElement && result.content) {
1858
- // Determine language for Prism.js
1859
- const languageMap = {
1860
- 'js': 'javascript',
1861
- 'jsx': 'javascript',
1862
- 'ts': 'typescript',
1863
- 'tsx': 'typescript',
1864
- 'py': 'python',
1865
- 'rb': 'ruby',
1866
- 'php': 'php',
1867
- 'html': 'html',
1868
- 'htm': 'html',
1869
- 'css': 'css',
1870
- 'scss': 'scss',
1871
- 'sass': 'sass',
1872
- 'json': 'json',
1873
- 'xml': 'xml',
1874
- 'yaml': 'yaml',
1875
- 'yml': 'yaml',
1876
- 'md': 'markdown',
1877
- 'sh': 'bash',
1878
- 'bash': 'bash',
1879
- 'zsh': 'bash',
1880
- 'sql': 'sql',
1881
- 'go': 'go',
1882
- 'java': 'java',
1883
- 'c': 'c',
1884
- 'cpp': 'cpp',
1885
- 'h': 'c',
1886
- 'hpp': 'cpp'
1887
- };
1888
-
1889
- const language = languageMap[extension.toLowerCase()] || 'text';
1890
-
1891
- // Set up code element with Prism.js classes
1892
- codeElement.className = `file-content-code language-${language}`;
1893
- codeElement.textContent = result.content;
1894
-
1895
- // Apply syntax highlighting with Prism.js if available
1896
- if (window.Prism) {
1897
- // Add line numbers
1898
- codeElement.parentElement.className = 'file-content-display line-numbers';
1899
-
1900
- // Apply highlighting
1901
- window.Prism.highlightElement(codeElement);
1902
- }
1903
-
1904
- contentArea.style.display = 'flex';
1905
-
1906
- console.log('✅ File content displayed successfully');
1907
- } else {
1908
- console.error('❌ Missing code element or content');
1909
- }
1910
- }
1911
-
1912
- function displayFileContentError(modal, result) {
1913
- const errorArea = modal.querySelector('.file-viewer-error');
1914
- const messageElement = modal.querySelector('.error-message');
1915
- const suggestionsElement = modal.querySelector('.error-suggestions');
1916
-
1917
- // Create user-friendly error messages
1918
- let errorMessage = result.error || 'Unknown error occurred';
1919
-
1920
- if (errorMessage.includes('not found')) {
1921
- errorMessage = '📁 File not found or not accessible';
1922
- } else if (errorMessage.includes('permission')) {
1923
- errorMessage = '🔒 Permission denied accessing this file';
1924
- } else if (errorMessage.includes('too large')) {
1925
- errorMessage = '📏 File is too large to display';
1926
- } else if (!errorMessage.includes('📁') && !errorMessage.includes('🔒') && !errorMessage.includes('📏')) {
1927
- errorMessage = `⚠️ ${errorMessage}`;
1928
- }
1929
-
1930
- messageElement.textContent = errorMessage;
1931
-
1932
- // Add suggestions if available
1933
- if (result.suggestions && result.suggestions.length > 0) {
1934
- suggestionsElement.innerHTML = `
1935
- <h4>Suggestions:</h4>
1936
- <ul>
1937
- ${result.suggestions.map(suggestion => `<li>${suggestion}</li>`).join('')}
1938
- </ul>
1939
- `;
1940
- } else {
1941
- suggestionsElement.innerHTML = `
1942
- <h4>Try:</h4>
1943
- <ul>
1944
- <li>Check if the file exists and is readable</li>
1945
- <li>Verify file permissions</li>
1946
- <li>Ensure the monitoring server has access to this file</li>
1947
- </ul>
1948
- `;
1949
- }
1950
-
1951
- console.log('📋 Displaying file content error:', {
1952
- originalError: result.error,
1953
- processedMessage: errorMessage,
1954
- suggestions: result.suggestions
1955
- });
1956
-
1957
- errorArea.style.display = 'block';
1958
- }
1959
-
1960
- // Global window functions for backward compatibility
1961
- window.showAgentInstanceDetails = function(instanceId) {
1962
- if (window.dashboard && typeof window.dashboard.showAgentInstanceDetails === 'function') {
1963
- window.dashboard.showAgentInstanceDetails(instanceId);
1964
- } else {
1965
- console.error('Dashboard not available or method not found');
1966
- }
1967
- };
1968
-
1969
- // Initialize dashboard when page loads
1970
- document.addEventListener('DOMContentLoaded', function() {
1971
- window.dashboard = new Dashboard();
1972
- console.log('Dashboard loaded and initialized');
1973
- });