shell-mirror 1.5.57 โ 1.5.59
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.
- package/package.json +1 -1
- package/public/app/dashboard.css +139 -0
- package/public/app/dashboard.js +290 -31
package/package.json
CHANGED
package/public/app/dashboard.css
CHANGED
|
@@ -152,6 +152,59 @@ body {
|
|
|
152
152
|
animation: spin 1s linear infinite;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
/* Inline Refresh Button */
|
|
156
|
+
.refresh-btn-inline {
|
|
157
|
+
background: #f8f9fa;
|
|
158
|
+
border: 1px solid #e9ecef;
|
|
159
|
+
border-radius: 50%;
|
|
160
|
+
color: #666;
|
|
161
|
+
width: 32px;
|
|
162
|
+
height: 32px;
|
|
163
|
+
cursor: pointer;
|
|
164
|
+
transition: all 0.2s ease;
|
|
165
|
+
display: flex;
|
|
166
|
+
align-items: center;
|
|
167
|
+
justify-content: center;
|
|
168
|
+
font-size: 0.9rem;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.refresh-btn-inline:hover:not(:disabled) {
|
|
172
|
+
background: #e9ecef;
|
|
173
|
+
color: #333;
|
|
174
|
+
transform: scale(1.05);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.refresh-btn-inline.loading {
|
|
178
|
+
opacity: 0.7;
|
|
179
|
+
cursor: not-allowed;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.refresh-btn-inline.loading .refresh-icon {
|
|
183
|
+
animation: spin 1s linear infinite;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/* Cleanup Button */
|
|
187
|
+
.cleanup-btn-inline {
|
|
188
|
+
background: #ffebee;
|
|
189
|
+
border: 1px solid #f48fb1;
|
|
190
|
+
border-radius: 50%;
|
|
191
|
+
color: #d32f2f;
|
|
192
|
+
width: 32px;
|
|
193
|
+
height: 32px;
|
|
194
|
+
cursor: pointer;
|
|
195
|
+
transition: all 0.2s ease;
|
|
196
|
+
display: flex;
|
|
197
|
+
align-items: center;
|
|
198
|
+
justify-content: center;
|
|
199
|
+
font-size: 0.9rem;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.cleanup-btn-inline:hover:not(:disabled) {
|
|
203
|
+
background: #f8bbd9;
|
|
204
|
+
color: #b71c1c;
|
|
205
|
+
transform: scale(1.05);
|
|
206
|
+
}
|
|
207
|
+
|
|
155
208
|
.connection-status {
|
|
156
209
|
font-size: 0.8rem;
|
|
157
210
|
font-weight: 500;
|
|
@@ -242,6 +295,18 @@ body {
|
|
|
242
295
|
margin-bottom: 20px;
|
|
243
296
|
}
|
|
244
297
|
|
|
298
|
+
.card-title-section {
|
|
299
|
+
display: flex;
|
|
300
|
+
align-items: center;
|
|
301
|
+
gap: 12px;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.agent-actions-header {
|
|
305
|
+
display: flex;
|
|
306
|
+
align-items: center;
|
|
307
|
+
gap: 8px;
|
|
308
|
+
}
|
|
309
|
+
|
|
245
310
|
.card-header h2 {
|
|
246
311
|
font-size: 1.3rem;
|
|
247
312
|
font-weight: 600;
|
|
@@ -298,6 +363,11 @@ body {
|
|
|
298
363
|
color: #2e7d32;
|
|
299
364
|
}
|
|
300
365
|
|
|
366
|
+
.agent-status.recent {
|
|
367
|
+
background: #fff3e0;
|
|
368
|
+
color: #f57c00;
|
|
369
|
+
}
|
|
370
|
+
|
|
301
371
|
.agent-status.offline {
|
|
302
372
|
background: #ffebee;
|
|
303
373
|
color: #c62828;
|
|
@@ -356,6 +426,24 @@ body {
|
|
|
356
426
|
transform: translateY(-1px);
|
|
357
427
|
}
|
|
358
428
|
|
|
429
|
+
/* Disabled/Offline Agent Styles */
|
|
430
|
+
.agent-item.agent-offline {
|
|
431
|
+
opacity: 0.7;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
.btn-disabled {
|
|
435
|
+
background: #f5f5f5 !important;
|
|
436
|
+
color: #999 !important;
|
|
437
|
+
cursor: not-allowed !important;
|
|
438
|
+
border: 1px solid #e0e0e0 !important;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
.btn-disabled:hover {
|
|
442
|
+
background: #f5f5f5 !important;
|
|
443
|
+
transform: none !important;
|
|
444
|
+
color: #999 !important;
|
|
445
|
+
}
|
|
446
|
+
|
|
359
447
|
/* Quick Actions */
|
|
360
448
|
.action-buttons {
|
|
361
449
|
display: flex;
|
|
@@ -852,4 +940,55 @@ body {
|
|
|
852
940
|
|
|
853
941
|
.api-error button {
|
|
854
942
|
margin-top: 10px;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
/* Connection Testing States */
|
|
946
|
+
.btn-connect.testing {
|
|
947
|
+
background: #ffc107 !important;
|
|
948
|
+
color: #333 !important;
|
|
949
|
+
cursor: wait !important;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/* Connection Error Notifications */
|
|
953
|
+
.connection-error-notification {
|
|
954
|
+
position: fixed;
|
|
955
|
+
top: 120px;
|
|
956
|
+
right: 20px;
|
|
957
|
+
background: #fff;
|
|
958
|
+
border-left: 4px solid #f44336;
|
|
959
|
+
border-radius: 8px;
|
|
960
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
961
|
+
padding: 16px 20px;
|
|
962
|
+
max-width: 350px;
|
|
963
|
+
z-index: 3000;
|
|
964
|
+
animation: slideInRight 0.3s ease-out;
|
|
965
|
+
display: flex;
|
|
966
|
+
align-items: flex-start;
|
|
967
|
+
gap: 12px;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
.connection-error-notification .error-content {
|
|
971
|
+
flex: 1;
|
|
972
|
+
font-size: 0.9rem;
|
|
973
|
+
line-height: 1.4;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
.connection-error-notification button {
|
|
977
|
+
background: none;
|
|
978
|
+
border: none;
|
|
979
|
+
font-size: 1.2rem;
|
|
980
|
+
cursor: pointer;
|
|
981
|
+
color: #999;
|
|
982
|
+
padding: 0;
|
|
983
|
+
width: 20px;
|
|
984
|
+
height: 20px;
|
|
985
|
+
display: flex;
|
|
986
|
+
align-items: center;
|
|
987
|
+
justify-content: center;
|
|
988
|
+
border-radius: 50%;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
.connection-error-notification button:hover {
|
|
992
|
+
background: #f5f5f5;
|
|
993
|
+
color: #333;
|
|
855
994
|
}
|
package/public/app/dashboard.js
CHANGED
|
@@ -12,6 +12,8 @@ class ShellMirrorDashboard {
|
|
|
12
12
|
this.refreshInterval = null;
|
|
13
13
|
this.lastRefresh = null;
|
|
14
14
|
this.isRefreshing = false;
|
|
15
|
+
this.connectionStatusDebounce = null;
|
|
16
|
+
this.currentConnectionStatus = null;
|
|
15
17
|
this.init();
|
|
16
18
|
}
|
|
17
19
|
|
|
@@ -297,16 +299,51 @@ class ShellMirrorDashboard {
|
|
|
297
299
|
}
|
|
298
300
|
|
|
299
301
|
updateConnectionStatus(status) {
|
|
302
|
+
// Debounce rapid status changes to prevent UI flickering
|
|
303
|
+
if (this.connectionStatusDebounce) {
|
|
304
|
+
clearTimeout(this.connectionStatusDebounce);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Don't update if status is the same
|
|
308
|
+
if (this.currentConnectionStatus === status) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// For rapid disconnection/reconnection, only show status after a delay
|
|
313
|
+
if (status === 'disconnected' && this.currentConnectionStatus === 'connected') {
|
|
314
|
+
this.connectionStatusDebounce = setTimeout(() => {
|
|
315
|
+
this.setConnectionStatusUI(status);
|
|
316
|
+
}, 2000); // Wait 2 seconds before showing disconnected
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// For reconnection, show immediately but don't flicker
|
|
321
|
+
if (status === 'connected' && this.connectionStatusDebounce) {
|
|
322
|
+
clearTimeout(this.connectionStatusDebounce);
|
|
323
|
+
this.connectionStatusDebounce = null;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
this.setConnectionStatusUI(status);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
setConnectionStatusUI(status) {
|
|
330
|
+
this.currentConnectionStatus = status;
|
|
300
331
|
const connectionStatus = document.getElementById('connection-status');
|
|
301
332
|
if (connectionStatus) {
|
|
302
333
|
connectionStatus.className = `connection-status ${status}`;
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
334
|
+
|
|
335
|
+
// Only show status when there are issues or special modes
|
|
336
|
+
const statusConfig = {
|
|
337
|
+
connected: { text: '', show: false }, // Hide when everything is working
|
|
338
|
+
disconnected: { text: '๐ก Reconnecting...', show: true },
|
|
339
|
+
error: { text: '๐ด Connection Error', show: true },
|
|
340
|
+
failed: { text: '๐ด Offline', show: true },
|
|
341
|
+
offline: { text: '๐ก HTTP Only', show: true }
|
|
308
342
|
};
|
|
309
|
-
|
|
343
|
+
|
|
344
|
+
const config = statusConfig[status] || { text: 'โ Unknown', show: true };
|
|
345
|
+
connectionStatus.textContent = config.text;
|
|
346
|
+
connectionStatus.style.display = config.show ? 'inline-block' : 'none';
|
|
310
347
|
}
|
|
311
348
|
}
|
|
312
349
|
|
|
@@ -465,14 +502,11 @@ class ShellMirrorDashboard {
|
|
|
465
502
|
}
|
|
466
503
|
|
|
467
504
|
renderAuthenticatedDashboard() {
|
|
468
|
-
// Update user section
|
|
505
|
+
// Update user section - simplified without refresh button
|
|
469
506
|
document.getElementById('user-section').innerHTML = `
|
|
470
507
|
<div class="dashboard-controls">
|
|
471
|
-
<span id="connection-status" class="connection-status"
|
|
508
|
+
<span id="connection-status" class="connection-status" style="display: none;"></span>
|
|
472
509
|
<span id="refresh-status" class="refresh-status">Initializing...</span>
|
|
473
|
-
<button id="refresh-btn" class="refresh-btn" onclick="dashboard.manualRefresh()" title="Refresh agents">
|
|
474
|
-
<span class="refresh-icon">๐</span>
|
|
475
|
-
</button>
|
|
476
510
|
</div>
|
|
477
511
|
<div class="user-info">
|
|
478
512
|
<span class="user-name">${this.user.name || this.user.email}</span>
|
|
@@ -503,30 +537,62 @@ class ShellMirrorDashboard {
|
|
|
503
537
|
}
|
|
504
538
|
|
|
505
539
|
renderActiveAgents() {
|
|
506
|
-
// Filter for
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
|
|
540
|
+
// Filter for agents that should be displayed (online, recent, or offline with sessions)
|
|
541
|
+
const displayAgents = this.agents.filter(agent => {
|
|
542
|
+
// Always show online agents
|
|
543
|
+
if (agent.status === 'online') return true;
|
|
544
|
+
|
|
545
|
+
// Show recent agents (last 2 minutes)
|
|
546
|
+
if (agent.status === 'recent') return true;
|
|
547
|
+
|
|
548
|
+
// Show offline agents only if they have active sessions
|
|
549
|
+
const sessions = this.agentSessions[agent.agentId] || [];
|
|
550
|
+
const activeSessions = sessions.filter(s => s.status === 'active');
|
|
551
|
+
return activeSessions.length > 0;
|
|
510
552
|
});
|
|
553
|
+
|
|
554
|
+
// Separate truly active vs inactive agents
|
|
555
|
+
const activeAgents = displayAgents.filter(agent => agent.status === 'online' || agent.status === 'recent');
|
|
511
556
|
|
|
512
|
-
const agentCount =
|
|
513
|
-
const agentsHtml =
|
|
557
|
+
const agentCount = displayAgents.length;
|
|
558
|
+
const agentsHtml = displayAgents.map(agent => {
|
|
514
559
|
const sessions = this.agentSessions[agent.agentId] || [];
|
|
515
560
|
const sessionCount = sessions.length;
|
|
516
561
|
|
|
562
|
+
const isConnectable = agent.status === 'online' || agent.status === 'recent';
|
|
563
|
+
const statusIcon = {
|
|
564
|
+
'online': '๐ข',
|
|
565
|
+
'recent': '๐ก',
|
|
566
|
+
'offline': '๐ด'
|
|
567
|
+
}[agent.status] || 'โ';
|
|
568
|
+
|
|
569
|
+
const statusText = {
|
|
570
|
+
'online': 'Live',
|
|
571
|
+
'recent': 'Recent',
|
|
572
|
+
'offline': 'Offline'
|
|
573
|
+
}[agent.status] || agent.status;
|
|
574
|
+
|
|
575
|
+
const lastSeenText = agent.timeSinceLastSeen !== undefined
|
|
576
|
+
? this.formatPreciseLastSeen(agent.timeSinceLastSeen)
|
|
577
|
+
: this.formatLastSeen(agent.lastSeen);
|
|
578
|
+
|
|
517
579
|
return `
|
|
518
|
-
<div class="agent-item">
|
|
580
|
+
<div class="agent-item ${!isConnectable ? 'agent-offline' : ''}">
|
|
519
581
|
<div class="agent-info">
|
|
520
582
|
<div class="agent-name">${agent.machineName || agent.agentId}</div>
|
|
521
|
-
<div class="agent-status ${agent.
|
|
522
|
-
|
|
583
|
+
<div class="agent-status ${agent.status}">
|
|
584
|
+
${statusIcon} ${statusText}
|
|
585
|
+
</div>
|
|
586
|
+
<div class="agent-last-seen">Last seen: ${lastSeenText}</div>
|
|
523
587
|
${sessionCount > 0 ? `<div class="agent-sessions">${sessionCount} active session${sessionCount !== 1 ? 's' : ''}</div>` : ''}
|
|
524
588
|
</div>
|
|
525
589
|
<div class="agent-actions">
|
|
526
|
-
<button class="btn-connect
|
|
527
|
-
|
|
590
|
+
<button class="btn-connect ${!isConnectable ? 'btn-disabled' : ''}"
|
|
591
|
+
onclick="dashboard.connectToAgent('${agent.agentId}')"
|
|
592
|
+
${!isConnectable ? 'disabled' : ''}>
|
|
593
|
+
${!isConnectable ? 'Offline' : sessionCount > 0 ? 'Resume Session' : 'New Session'}
|
|
528
594
|
</button>
|
|
529
|
-
${sessionCount > 0 ? `<button class="btn-sessions" onclick="dashboard.showAgentSessions('${agent.agentId}')">
|
|
595
|
+
${sessionCount > 0 && isConnectable ? `<button class="btn-sessions" onclick="dashboard.showAgentSessions('${agent.agentId}')">
|
|
530
596
|
All Sessions
|
|
531
597
|
</button>` : ''}
|
|
532
598
|
</div>
|
|
@@ -534,11 +600,25 @@ class ShellMirrorDashboard {
|
|
|
534
600
|
`;
|
|
535
601
|
}).join('');
|
|
536
602
|
|
|
603
|
+
// Check if there are any offline agents to show cleanup option
|
|
604
|
+
const offlineAgents = this.agents.filter(agent => agent.status === 'offline');
|
|
605
|
+
const showCleanup = offlineAgents.length > 0;
|
|
606
|
+
|
|
537
607
|
return `
|
|
538
608
|
<div class="dashboard-card">
|
|
539
609
|
<div class="card-header">
|
|
540
|
-
<
|
|
541
|
-
|
|
610
|
+
<div class="card-title-section">
|
|
611
|
+
<h2>๐ฅ๏ธ Active Agents</h2>
|
|
612
|
+
<span class="agent-count">${agentCount} agent${agentCount !== 1 ? 's' : ''}</span>
|
|
613
|
+
</div>
|
|
614
|
+
<div class="agent-actions-header">
|
|
615
|
+
<button id="refresh-btn" class="refresh-btn-inline" onclick="dashboard.manualRefresh()" title="Refresh agents">
|
|
616
|
+
<span class="refresh-icon">๐</span>
|
|
617
|
+
</button>
|
|
618
|
+
${showCleanup ? `<button class="cleanup-btn-inline" onclick="dashboard.cleanupOfflineAgents()" title="Remove offline agents">
|
|
619
|
+
<span>๐งน</span>
|
|
620
|
+
</button>` : ''}
|
|
621
|
+
</div>
|
|
542
622
|
</div>
|
|
543
623
|
<div class="card-content">
|
|
544
624
|
${agentCount > 0 ? agentsHtml : '<p class="no-data">No active agents. <a href="#" onclick="dashboard.showAgentInstructions()">Set up an agent</a></p>'}
|
|
@@ -710,6 +790,16 @@ class ShellMirrorDashboard {
|
|
|
710
790
|
return `${Math.floor(diff / 86400)} days ago`;
|
|
711
791
|
}
|
|
712
792
|
|
|
793
|
+
formatPreciseLastSeen(timeSinceLastSeen) {
|
|
794
|
+
if (!timeSinceLastSeen && timeSinceLastSeen !== 0) return 'Unknown';
|
|
795
|
+
|
|
796
|
+
if (timeSinceLastSeen < 10) return 'Just now';
|
|
797
|
+
if (timeSinceLastSeen < 60) return `${Math.floor(timeSinceLastSeen)} seconds ago`;
|
|
798
|
+
if (timeSinceLastSeen < 3600) return `${Math.floor(timeSinceLastSeen / 60)} minutes ago`;
|
|
799
|
+
if (timeSinceLastSeen < 86400) return `${Math.floor(timeSinceLastSeen / 3600)} hours ago`;
|
|
800
|
+
return `${Math.floor(timeSinceLastSeen / 86400)} days ago`;
|
|
801
|
+
}
|
|
802
|
+
|
|
713
803
|
formatDate(date) {
|
|
714
804
|
return new Intl.DateTimeFormat('en-US', {
|
|
715
805
|
month: 'short',
|
|
@@ -743,9 +833,109 @@ class ShellMirrorDashboard {
|
|
|
743
833
|
}
|
|
744
834
|
}
|
|
745
835
|
|
|
836
|
+
showAgentConnectionTest(agentId, status) {
|
|
837
|
+
// Find the agent item in the DOM and show loading state
|
|
838
|
+
const agentItems = document.querySelectorAll('.agent-item');
|
|
839
|
+
agentItems.forEach(item => {
|
|
840
|
+
const connectBtn = item.querySelector('.btn-connect');
|
|
841
|
+
if (connectBtn && connectBtn.onclick.toString().includes(agentId)) {
|
|
842
|
+
if (status === 'testing') {
|
|
843
|
+
connectBtn.textContent = 'Testing...';
|
|
844
|
+
connectBtn.disabled = true;
|
|
845
|
+
connectBtn.classList.add('testing');
|
|
846
|
+
} else if (status === 'done') {
|
|
847
|
+
connectBtn.disabled = false;
|
|
848
|
+
connectBtn.classList.remove('testing');
|
|
849
|
+
// Original text will be restored on next refresh
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
showConnectionError(agent, message) {
|
|
856
|
+
// Show a more user-friendly error message
|
|
857
|
+
const notification = document.createElement('div');
|
|
858
|
+
notification.className = 'connection-error-notification';
|
|
859
|
+
notification.innerHTML = `
|
|
860
|
+
<div class="error-content">
|
|
861
|
+
<strong>Connection Failed</strong><br>
|
|
862
|
+
Agent: ${agent.machineName || agent.agentId}<br>
|
|
863
|
+
${message}
|
|
864
|
+
</div>
|
|
865
|
+
<button onclick="this.parentElement.remove()">ร</button>
|
|
866
|
+
`;
|
|
867
|
+
document.body.appendChild(notification);
|
|
868
|
+
|
|
869
|
+
// Auto-remove after 10 seconds
|
|
870
|
+
setTimeout(() => {
|
|
871
|
+
if (document.body.contains(notification)) {
|
|
872
|
+
document.body.removeChild(notification);
|
|
873
|
+
}
|
|
874
|
+
}, 10000);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
async testAgentConnectivity(agentId) {
|
|
878
|
+
try {
|
|
879
|
+
console.log('[DASHBOARD] ๐ Testing connectivity for agent:', agentId);
|
|
880
|
+
|
|
881
|
+
// Try to ping the agent through the API
|
|
882
|
+
const response = await fetch(`/php-backend/api/ping-agent.php?agentId=${encodeURIComponent(agentId)}`, {
|
|
883
|
+
method: 'GET',
|
|
884
|
+
credentials: 'include',
|
|
885
|
+
timeout: 5000 // 5 second timeout
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
if (response.ok) {
|
|
889
|
+
const data = await response.json();
|
|
890
|
+
console.log('[DASHBOARD] ๐ Ping response:', data);
|
|
891
|
+
return data.success && data.reachable;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
console.log('[DASHBOARD] โ ๏ธ Ping request failed:', response.status);
|
|
895
|
+
return false;
|
|
896
|
+
} catch (error) {
|
|
897
|
+
console.log('[DASHBOARD] โ ๏ธ Agent connectivity test failed:', error);
|
|
898
|
+
return false;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
746
902
|
// Action handlers
|
|
747
903
|
async connectToAgent(agentId) {
|
|
748
904
|
console.log('[DASHBOARD] ๐ DEBUG: connectToAgent called with agentId:', agentId);
|
|
905
|
+
|
|
906
|
+
// First, test if agent is actually reachable
|
|
907
|
+
const agent = this.agents.find(a => a.agentId === agentId);
|
|
908
|
+
if (!agent) {
|
|
909
|
+
alert('Agent not found. Please refresh and try again.');
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
// Check agent status
|
|
914
|
+
if (agent.status === 'offline') {
|
|
915
|
+
alert(`Agent "${agent.machineName || agentId}" is offline. Please ensure the agent is running.`);
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// Test agent connectivity for recent agents with loading indicator
|
|
920
|
+
if (agent.status === 'recent') {
|
|
921
|
+
console.log('[DASHBOARD] ๐ Testing agent connectivity...');
|
|
922
|
+
|
|
923
|
+
// Show loading state on the specific agent
|
|
924
|
+
this.showAgentConnectionTest(agentId, 'testing');
|
|
925
|
+
|
|
926
|
+
const isReachable = await this.testAgentConnectivity(agentId);
|
|
927
|
+
|
|
928
|
+
// Hide loading state
|
|
929
|
+
this.showAgentConnectionTest(agentId, 'done');
|
|
930
|
+
|
|
931
|
+
if (!isReachable) {
|
|
932
|
+
this.showConnectionError(agent, 'Connection test failed - agent may be offline');
|
|
933
|
+
// Refresh agent list to get updated status
|
|
934
|
+
await this.refreshDashboardData();
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
749
939
|
console.log('[DASHBOARD] ๐ DEBUG: Current agentSessions:', this.agentSessions);
|
|
750
940
|
|
|
751
941
|
// Check if there are existing sessions for this agent
|
|
@@ -821,16 +1011,15 @@ class ShellMirrorDashboard {
|
|
|
821
1011
|
}
|
|
822
1012
|
|
|
823
1013
|
startNewSession() {
|
|
824
|
-
// Get first available agent for new session
|
|
825
|
-
const activeAgents = this.agents.filter(agent =>
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
});
|
|
1014
|
+
// Get first available agent for new session (only truly active ones)
|
|
1015
|
+
const activeAgents = this.agents.filter(agent =>
|
|
1016
|
+
agent.status === 'online' || agent.status === 'recent'
|
|
1017
|
+
);
|
|
829
1018
|
|
|
830
1019
|
if (activeAgents.length > 0) {
|
|
831
1020
|
this.connectToAgent(activeAgents[0].agentId);
|
|
832
1021
|
} else {
|
|
833
|
-
alert('No active agents available. Please ensure an agent is running on your Mac.');
|
|
1022
|
+
alert('No active agents available. Please ensure an agent is running on your Mac and try refreshing.');
|
|
834
1023
|
}
|
|
835
1024
|
}
|
|
836
1025
|
|
|
@@ -951,6 +1140,76 @@ class ShellMirrorDashboard {
|
|
|
951
1140
|
}
|
|
952
1141
|
}
|
|
953
1142
|
|
|
1143
|
+
async cleanupOfflineAgents() {
|
|
1144
|
+
try {
|
|
1145
|
+
console.log('[DASHBOARD] ๐งน Cleaning up offline agents...');
|
|
1146
|
+
|
|
1147
|
+
const response = await fetch('/php-backend/api/cleanup-agents.php', {
|
|
1148
|
+
method: 'POST',
|
|
1149
|
+
credentials: 'include'
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1152
|
+
if (response.ok) {
|
|
1153
|
+
const data = await response.json();
|
|
1154
|
+
console.log('[DASHBOARD] โ
Cleanup response:', data);
|
|
1155
|
+
|
|
1156
|
+
if (data.success) {
|
|
1157
|
+
// Show success message
|
|
1158
|
+
this.showCleanupResult(data.data.message);
|
|
1159
|
+
|
|
1160
|
+
// Refresh the agent list
|
|
1161
|
+
await this.refreshDashboardData();
|
|
1162
|
+
} else {
|
|
1163
|
+
this.showCleanupError('Cleanup failed: ' + (data.message || 'Unknown error'));
|
|
1164
|
+
}
|
|
1165
|
+
} else {
|
|
1166
|
+
this.showCleanupError('Cleanup request failed: ' + response.status);
|
|
1167
|
+
}
|
|
1168
|
+
} catch (error) {
|
|
1169
|
+
console.error('[DASHBOARD] โ Cleanup failed:', error);
|
|
1170
|
+
this.showCleanupError('Cleanup failed: ' + error.message);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
showCleanupResult(message) {
|
|
1175
|
+
const notification = document.createElement('div');
|
|
1176
|
+
notification.className = 'cleanup-result-notification';
|
|
1177
|
+
notification.innerHTML = `
|
|
1178
|
+
<div class="result-content">
|
|
1179
|
+
<strong>โ
Cleanup Complete</strong><br>
|
|
1180
|
+
${message}
|
|
1181
|
+
</div>
|
|
1182
|
+
<button onclick="this.parentElement.remove()">ร</button>
|
|
1183
|
+
`;
|
|
1184
|
+
document.body.appendChild(notification);
|
|
1185
|
+
|
|
1186
|
+
// Auto-remove after 5 seconds
|
|
1187
|
+
setTimeout(() => {
|
|
1188
|
+
if (document.body.contains(notification)) {
|
|
1189
|
+
document.body.removeChild(notification);
|
|
1190
|
+
}
|
|
1191
|
+
}, 5000);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
showCleanupError(message) {
|
|
1195
|
+
const notification = document.createElement('div');
|
|
1196
|
+
notification.className = 'connection-error-notification';
|
|
1197
|
+
notification.innerHTML = `
|
|
1198
|
+
<div class="error-content">
|
|
1199
|
+
<strong>Cleanup Failed</strong><br>
|
|
1200
|
+
${message}
|
|
1201
|
+
</div>
|
|
1202
|
+
<button onclick="this.parentElement.remove()">ร</button>
|
|
1203
|
+
`;
|
|
1204
|
+
document.body.appendChild(notification);
|
|
1205
|
+
|
|
1206
|
+
// Auto-remove after 8 seconds
|
|
1207
|
+
setTimeout(() => {
|
|
1208
|
+
if (document.body.contains(notification)) {
|
|
1209
|
+
document.body.removeChild(notification);
|
|
1210
|
+
}
|
|
1211
|
+
}, 8000);
|
|
1212
|
+
}
|
|
954
1213
|
|
|
955
1214
|
showAgentInstructions() {
|
|
956
1215
|
// TODO: Show modal with agent setup instructions
|