shell-mirror 1.5.39 → 1.5.41

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.
@@ -235,6 +235,19 @@ body {
235
235
  margin-top: 2px;
236
236
  }
237
237
 
238
+ .agent-sessions {
239
+ font-size: 0.8rem;
240
+ color: #4285f4;
241
+ margin-top: 2px;
242
+ font-weight: 500;
243
+ }
244
+
245
+ .agent-actions {
246
+ display: flex;
247
+ gap: 8px;
248
+ align-items: center;
249
+ }
250
+
238
251
  .btn-connect {
239
252
  background: #4caf50;
240
253
  color: white;
@@ -244,6 +257,7 @@ body {
244
257
  font-weight: 500;
245
258
  cursor: pointer;
246
259
  transition: all 0.2s ease;
260
+ font-size: 0.9rem;
247
261
  }
248
262
 
249
263
  .btn-connect:hover {
@@ -251,6 +265,23 @@ body {
251
265
  transform: translateY(-1px);
252
266
  }
253
267
 
268
+ .btn-sessions {
269
+ background: #4285f4;
270
+ color: white;
271
+ border: none;
272
+ padding: 8px 16px;
273
+ border-radius: 6px;
274
+ font-weight: 500;
275
+ cursor: pointer;
276
+ transition: all 0.2s ease;
277
+ font-size: 0.9rem;
278
+ }
279
+
280
+ .btn-sessions:hover {
281
+ background: #3367d6;
282
+ transform: translateY(-1px);
283
+ }
284
+
254
285
  /* Quick Actions */
255
286
  .action-buttons {
256
287
  display: flex;
@@ -522,4 +553,145 @@ body {
522
553
  flex-direction: row;
523
554
  gap: 12px;
524
555
  }
556
+ }
557
+
558
+ /* Modal Styles */
559
+ .modal-overlay {
560
+ position: fixed;
561
+ top: 0;
562
+ left: 0;
563
+ right: 0;
564
+ bottom: 0;
565
+ background: rgba(0, 0, 0, 0.5);
566
+ display: flex;
567
+ align-items: center;
568
+ justify-content: center;
569
+ z-index: 2000;
570
+ }
571
+
572
+ .modal {
573
+ background: white;
574
+ border-radius: 12px;
575
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
576
+ max-width: 500px;
577
+ width: 90%;
578
+ max-height: 80vh;
579
+ overflow: hidden;
580
+ }
581
+
582
+ .modal-header {
583
+ padding: 20px 24px;
584
+ border-bottom: 1px solid #eee;
585
+ display: flex;
586
+ justify-content: space-between;
587
+ align-items: center;
588
+ }
589
+
590
+ .modal-header h3 {
591
+ margin: 0;
592
+ font-size: 1.2rem;
593
+ font-weight: 600;
594
+ color: #333;
595
+ }
596
+
597
+ .modal-close {
598
+ background: none;
599
+ border: none;
600
+ font-size: 1.5rem;
601
+ cursor: pointer;
602
+ padding: 0;
603
+ width: 30px;
604
+ height: 30px;
605
+ display: flex;
606
+ align-items: center;
607
+ justify-content: center;
608
+ border-radius: 50%;
609
+ color: #666;
610
+ }
611
+
612
+ .modal-close:hover {
613
+ background: #f5f5f5;
614
+ color: #333;
615
+ }
616
+
617
+ .modal-body {
618
+ padding: 24px;
619
+ max-height: 60vh;
620
+ overflow-y: auto;
621
+ }
622
+
623
+ /* Session List Styles */
624
+ .sessions-modal-content p {
625
+ margin-top: 0;
626
+ margin-bottom: 16px;
627
+ color: #666;
628
+ }
629
+
630
+ .session-list {
631
+ margin-bottom: 20px;
632
+ }
633
+
634
+ .session-list-item {
635
+ display: flex;
636
+ justify-content: space-between;
637
+ align-items: center;
638
+ padding: 12px;
639
+ border: 1px solid #eee;
640
+ border-radius: 8px;
641
+ margin-bottom: 8px;
642
+ cursor: pointer;
643
+ transition: all 0.2s ease;
644
+ }
645
+
646
+ .session-list-item:hover {
647
+ background: #f8f9fa;
648
+ border-color: #4285f4;
649
+ }
650
+
651
+ .session-list-info {
652
+ flex: 1;
653
+ }
654
+
655
+ .session-list-name {
656
+ font-weight: 600;
657
+ color: #333;
658
+ margin-bottom: 4px;
659
+ }
660
+
661
+ .session-list-details {
662
+ display: flex;
663
+ gap: 12px;
664
+ font-size: 0.8rem;
665
+ color: #666;
666
+ }
667
+
668
+ .session-list-id {
669
+ font-family: monospace;
670
+ background: #f5f5f5;
671
+ padding: 2px 6px;
672
+ border-radius: 4px;
673
+ }
674
+
675
+ .session-list-status {
676
+ padding: 4px 8px;
677
+ border-radius: 12px;
678
+ font-size: 0.75rem;
679
+ font-weight: 500;
680
+ text-transform: uppercase;
681
+ }
682
+
683
+ .session-list-status.active {
684
+ background: #e8f5e8;
685
+ color: #2e7d32;
686
+ }
687
+
688
+ .session-list-status.crashed {
689
+ background: #ffebee;
690
+ color: #c62828;
691
+ }
692
+
693
+ .sessions-modal-actions {
694
+ border-top: 1px solid #eee;
695
+ padding-top: 16px;
696
+ text-align: center;
525
697
  }
@@ -5,6 +5,7 @@ class ShellMirrorDashboard {
5
5
  this.user = null;
6
6
  this.agents = [];
7
7
  this.sessions = [];
8
+ this.agentSessions = {}; // Maps agentId to sessions array
8
9
  this.init();
9
10
  }
10
11
 
@@ -20,6 +21,7 @@ class ShellMirrorDashboard {
20
21
  this.user = authStatus.user;
21
22
  await this.loadDashboardData();
22
23
  this.renderAuthenticatedDashboard();
24
+ this.startAutoRefresh(); // Start auto-refresh for authenticated users
23
25
  } else {
24
26
  this.renderUnauthenticatedDashboard();
25
27
  }
@@ -31,6 +33,24 @@ class ShellMirrorDashboard {
31
33
  }
32
34
  }
33
35
 
36
+ startAutoRefresh() {
37
+ // Refresh agent data every 30 seconds to detect disconnected agents
38
+ this.refreshInterval = setInterval(async () => {
39
+ if (this.isAuthenticated) {
40
+ await this.loadDashboardData();
41
+ // Only re-render the agents section to avoid full page flash
42
+ this.updateAgentsDisplay();
43
+ }
44
+ }, 30000);
45
+ }
46
+
47
+ updateAgentsDisplay() {
48
+ const agentsCard = document.querySelector('.dashboard-card');
49
+ if (agentsCard) {
50
+ agentsCard.innerHTML = this.renderActiveAgents();
51
+ }
52
+ }
53
+
34
54
  showLoading() {
35
55
  document.getElementById('loading-overlay').style.display = 'flex';
36
56
  }
@@ -61,6 +81,31 @@ class ShellMirrorDashboard {
61
81
 
62
82
  if (agentsData.success && agentsData.data && agentsData.data.agents) {
63
83
  this.agents = agentsData.data.agents;
84
+
85
+ // Simulate session data for each agent
86
+ this.agents.forEach(agent => {
87
+ if (agent.onlineStatus === 'online') {
88
+ // Mock sessions for online agents
89
+ this.agentSessions[agent.agentId] = [
90
+ {
91
+ id: `ses_${Date.now()}_main`,
92
+ name: 'Main Terminal',
93
+ lastActivity: Date.now() - 5 * 60 * 1000, // 5 minutes ago
94
+ createdAt: Date.now() - 2 * 60 * 60 * 1000, // 2 hours ago
95
+ status: 'active',
96
+ connectedClients: 0
97
+ },
98
+ {
99
+ id: `ses_${Date.now()}_debug`,
100
+ name: 'Debug Server',
101
+ lastActivity: Date.now() - 15 * 60 * 1000, // 15 minutes ago
102
+ createdAt: Date.now() - 1 * 60 * 60 * 1000, // 1 hour ago
103
+ status: 'active',
104
+ connectedClients: 0
105
+ }
106
+ ];
107
+ }
108
+ });
64
109
  }
65
110
 
66
111
  // TODO: Load session history when API is available
@@ -155,18 +200,29 @@ class ShellMirrorDashboard {
155
200
  });
156
201
 
157
202
  const agentCount = activeAgents.length;
158
- const agentsHtml = activeAgents.map(agent => `
203
+ const agentsHtml = activeAgents.map(agent => {
204
+ const sessions = this.agentSessions[agent.agentId] || [];
205
+ const sessionCount = sessions.length;
206
+
207
+ return `
159
208
  <div class="agent-item">
160
209
  <div class="agent-info">
161
210
  <div class="agent-name">${agent.machineName || agent.agentId}</div>
162
211
  <div class="agent-status ${agent.onlineStatus}">${agent.onlineStatus}</div>
163
212
  <div class="agent-last-seen">Last seen: ${this.formatLastSeen(agent.lastSeen)}</div>
213
+ ${sessionCount > 0 ? `<div class="agent-sessions">${sessionCount} active session${sessionCount !== 1 ? 's' : ''}</div>` : ''}
214
+ </div>
215
+ <div class="agent-actions">
216
+ <button class="btn-connect" onclick="dashboard.connectToAgent('${agent.agentId}')">
217
+ New Session
218
+ </button>
219
+ ${sessionCount > 0 ? `<button class="btn-sessions" onclick="dashboard.showAgentSessions('${agent.agentId}')">
220
+ View Sessions
221
+ </button>` : ''}
164
222
  </div>
165
- <button class="btn-connect" onclick="dashboard.connectToAgent('${agent.agentId}')">
166
- Connect
167
- </button>
168
223
  </div>
169
- `).join('');
224
+ `;
225
+ }).join('');
170
226
 
171
227
  return `
172
228
  <div class="dashboard-card">
@@ -382,8 +438,96 @@ class ShellMirrorDashboard {
382
438
  window.location.href = `/app/terminal.html?agent=${agentId}`;
383
439
  }
384
440
 
441
+ async connectToSession(agentId, sessionId) {
442
+ window.location.href = `/app/terminal.html?agent=${agentId}&session=${sessionId}`;
443
+ }
444
+
385
445
  startNewSession() {
386
- window.location.href = '/app/terminal.html';
446
+ // Get first available agent for new session
447
+ const activeAgents = this.agents.filter(agent => {
448
+ const timeSinceLastSeen = Date.now() / 1000 - agent.lastSeen;
449
+ return agent.onlineStatus === 'online' || timeSinceLastSeen < 300;
450
+ });
451
+
452
+ if (activeAgents.length > 0) {
453
+ this.connectToAgent(activeAgents[0].agentId);
454
+ } else {
455
+ alert('No active agents available. Please ensure an agent is running on your Mac.');
456
+ }
457
+ }
458
+
459
+ showAgentSessions(agentId) {
460
+ const sessions = this.agentSessions[agentId] || [];
461
+ const agent = this.agents.find(a => a.agentId === agentId);
462
+ const agentName = agent ? (agent.machineName || agent.agentId) : agentId;
463
+
464
+ if (sessions.length === 0) {
465
+ alert(`No active sessions found for ${agentName}`);
466
+ return;
467
+ }
468
+
469
+ // Create session list modal
470
+ const sessionsList = sessions.map(session => `
471
+ <div class="session-list-item" onclick="dashboard.connectToSession('${agentId}', '${session.id}')">
472
+ <div class="session-list-info">
473
+ <div class="session-list-name">${session.name}</div>
474
+ <div class="session-list-details">
475
+ <span class="session-list-id">${session.id.substring(0, 8)}...</span>
476
+ <span class="session-list-activity">Last activity: ${this.formatLastActivity(session.lastActivity)}</span>
477
+ </div>
478
+ </div>
479
+ <div class="session-list-status ${session.status}">${session.status}</div>
480
+ </div>
481
+ `).join('');
482
+
483
+ // Show modal with sessions
484
+ this.showModal(`Sessions on ${agentName}`, `
485
+ <div class="sessions-modal-content">
486
+ <p>Active terminal sessions (${sessions.length}):</p>
487
+ <div class="session-list">
488
+ ${sessionsList}
489
+ </div>
490
+ <div class="sessions-modal-actions">
491
+ <button class="btn-primary" onclick="dashboard.connectToAgent('${agentId}')">
492
+ + Create New Session
493
+ </button>
494
+ </div>
495
+ </div>
496
+ `);
497
+ }
498
+
499
+ showModal(title, content) {
500
+ // Create modal overlay
501
+ const modalOverlay = document.createElement('div');
502
+ modalOverlay.className = 'modal-overlay';
503
+ modalOverlay.onclick = () => document.body.removeChild(modalOverlay);
504
+
505
+ modalOverlay.innerHTML = `
506
+ <div class="modal" onclick="event.stopPropagation()">
507
+ <div class="modal-header">
508
+ <h3>${title}</h3>
509
+ <button class="modal-close" onclick="document.body.removeChild(this.closest('.modal-overlay'))">×</button>
510
+ </div>
511
+ <div class="modal-body">
512
+ ${content}
513
+ </div>
514
+ </div>
515
+ `;
516
+
517
+ document.body.appendChild(modalOverlay);
518
+ }
519
+
520
+ formatLastActivity(timestamp) {
521
+ const now = Date.now();
522
+ const diff = now - timestamp;
523
+ const minutes = Math.floor(diff / 60000);
524
+ const hours = Math.floor(diff / 3600000);
525
+ const days = Math.floor(diff / 86400000);
526
+
527
+ if (minutes < 1) return 'now';
528
+ if (minutes < 60) return `${minutes}m ago`;
529
+ if (hours < 24) return `${hours}h ago`;
530
+ return `${days}d ago`;
387
531
  }
388
532
 
389
533
  showAgentInstructions() {
@@ -21,11 +21,109 @@
21
21
  width: 100%;
22
22
  background-color: #000000;
23
23
  }
24
+
25
+ #terminal-container.show {
26
+ display: flex;
27
+ flex-direction: column;
28
+ }
29
+
30
+ /* Session Header */
31
+ .session-header {
32
+ background: #2a2a2a;
33
+ color: #ccc;
34
+ padding: 8px 16px;
35
+ border-bottom: 1px solid #444;
36
+ display: flex;
37
+ align-items: center;
38
+ justify-content: space-between;
39
+ font-size: 0.9em;
40
+ z-index: 100;
41
+ }
42
+
43
+ .session-info {
44
+ display: flex;
45
+ align-items: center;
46
+ gap: 12px;
47
+ }
48
+
49
+ .session-name {
50
+ font-weight: bold;
51
+ color: #fff;
52
+ }
53
+
54
+ .session-controls {
55
+ display: flex;
56
+ align-items: center;
57
+ gap: 8px;
58
+ }
59
+
60
+ .session-dropdown {
61
+ position: relative;
62
+ display: inline-block;
63
+ }
64
+
65
+ .session-dropdown-btn {
66
+ background: #3a3a3a;
67
+ color: #ccc;
68
+ border: 1px solid #555;
69
+ padding: 4px 8px;
70
+ border-radius: 4px;
71
+ cursor: pointer;
72
+ font-size: 0.8em;
73
+ display: flex;
74
+ align-items: center;
75
+ gap: 4px;
76
+ }
77
+
78
+ .session-dropdown-btn:hover {
79
+ background: #4a4a4a;
80
+ }
81
+
82
+ .session-dropdown-content {
83
+ display: none;
84
+ position: absolute;
85
+ background: #2a2a2a;
86
+ border: 1px solid #555;
87
+ border-radius: 4px;
88
+ right: 0;
89
+ top: 100%;
90
+ min-width: 200px;
91
+ z-index: 1000;
92
+ box-shadow: 0 4px 8px rgba(0,0,0,0.3);
93
+ }
94
+
95
+ .session-dropdown-content.show {
96
+ display: block;
97
+ }
98
+
99
+ .session-dropdown-item {
100
+ padding: 8px 12px;
101
+ cursor: pointer;
102
+ border-bottom: 1px solid #444;
103
+ display: flex;
104
+ justify-content: space-between;
105
+ align-items: center;
106
+ }
107
+
108
+ .session-dropdown-item:last-child {
109
+ border-bottom: none;
110
+ }
111
+
112
+ .session-dropdown-item:hover {
113
+ background: #3a3a3a;
114
+ }
115
+
116
+ .session-dropdown-item.current {
117
+ background: #4285f4;
118
+ color: white;
119
+ }
120
+
24
121
  #terminal {
25
122
  padding: 8px; /* Mac Terminal.app padding */
26
123
  background-color: #000000;
27
- height: calc(100% - 16px);
124
+ height: calc(100% - 16px - 40px); /* Subtract session header height */
28
125
  width: calc(100% - 16px);
126
+ flex: 1;
29
127
  }
30
128
  #connect-container { padding: 2em; text-align: center; }
31
129
  #agent-id-input { font-size: 1.2em; padding: 8px; width: 400px; margin-bottom: 1em; }
@@ -35,7 +133,7 @@
35
133
  .back-to-dashboard {
36
134
  position: fixed;
37
135
  top: 20px;
38
- left: 20px;
136
+ right: 20px;
39
137
  background: rgba(66, 133, 244, 0.9);
40
138
  color: white;
41
139
  border: none;
@@ -55,11 +153,37 @@
55
153
  background: rgba(51, 103, 214, 0.9);
56
154
  transform: translateY(-1px);
57
155
  }
156
+
157
+ /* Connection Status Indicator */
158
+ .connection-status {
159
+ width: 8px;
160
+ height: 8px;
161
+ border-radius: 50%;
162
+ background: #ff4444;
163
+ margin-right: 4px;
164
+ transition: all 0.3s ease;
165
+ }
166
+
167
+ .connection-status.connected {
168
+ background: #44ff44;
169
+ box-shadow: 0 0 8px rgba(68, 255, 68, 0.5);
170
+ }
171
+
172
+ .connection-status.connecting {
173
+ background: #ffaa44;
174
+ animation: pulse 1.5s ease-in-out infinite alternate;
175
+ }
176
+
177
+ @keyframes pulse {
178
+ from { opacity: 1; }
179
+ to { opacity: 0.4; }
180
+ }
58
181
  </style>
59
182
  </head>
60
183
  <body>
61
184
  <!-- Back to Dashboard Button -->
62
- <a href="/app/dashboard.html" class="back-to-dashboard">
185
+ <a href="/app/dashboard.html" class="back-to-dashboard" id="dashboard-btn">
186
+ <div class="connection-status" id="connection-status"></div>
63
187
  <span>←</span>
64
188
  <span>Dashboard</span>
65
189
  </a>
@@ -69,6 +193,25 @@
69
193
  <p>Connecting to terminal...</p>
70
194
  </div>
71
195
  <div id="terminal-container">
196
+ <div class="session-header" id="session-header" style="display: none;">
197
+ <div class="session-info">
198
+ <span class="session-name" id="session-name">Terminal Session</span>
199
+ <span class="session-id" id="session-id"></span>
200
+ </div>
201
+ <div class="session-controls">
202
+ <div class="session-dropdown">
203
+ <button class="session-dropdown-btn" id="session-dropdown-btn">
204
+ <span>Sessions</span>
205
+ <span>▼</span>
206
+ </button>
207
+ <div class="session-dropdown-content" id="session-dropdown-content">
208
+ <div class="session-dropdown-item" onclick="createNewSession()">
209
+ <span>+ New Session</span>
210
+ </div>
211
+ </div>
212
+ </div>
213
+ </div>
214
+ </div>
72
215
  <div id="terminal"></div>
73
216
  </div>
74
217