shell-mirror 1.5.33 → 1.5.34

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/README.md CHANGED
@@ -146,12 +146,14 @@ Before you can run the application, you need to get a **Client ID** and **Client
146
146
  - Open your web browser and go to `https://shellmirror.app`
147
147
  - You will be prompted to log in with your Google account
148
148
  - Once authenticated, you will see your shell mirrored in the browser
149
+ - **macOS users:** You may see a firewall dialog asking if Node.js can accept incoming connections - click "Allow" for full terminal functionality
149
150
 
150
151
  **For Local Development:**
151
152
  - Open your web browser and go to `http://localhost:3000`
152
153
  - Or access from another device on the same network: `http://<your-macs-ip-address>:3000`
153
154
  - You will be prompted to log in with your Google account
154
155
  - Once authenticated, you will see your shell mirrored in the browser
156
+ - **macOS users:** You may see a firewall dialog asking if Node.js can accept incoming connections - click "Allow" for full terminal functionality
155
157
 
156
158
  ### 6. Deployment Notes
157
159
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shell-mirror",
3
- "version": "1.5.33",
3
+ "version": "1.5.34",
4
4
  "description": "Access your Mac shell from any device securely. Perfect for mobile coding with Claude Code CLI, Gemini CLI, and any shell tool.",
5
5
  "main": "server.js",
6
6
  "bin": {
@@ -0,0 +1,558 @@
1
+ /* Shell Mirror Dashboard Styles */
2
+
3
+ * {
4
+ margin: 0;
5
+ padding: 0;
6
+ box-sizing: border-box;
7
+ }
8
+
9
+ body {
10
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
11
+ line-height: 1.6;
12
+ color: #333;
13
+ background: #f5f7fa;
14
+ min-height: 100vh;
15
+ }
16
+
17
+ /* Header */
18
+ .dashboard-header {
19
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
20
+ color: white;
21
+ padding: 20px 0;
22
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
23
+ }
24
+
25
+ .header-content {
26
+ max-width: 1200px;
27
+ margin: 0 auto;
28
+ padding: 0 20px;
29
+ display: flex;
30
+ justify-content: space-between;
31
+ align-items: center;
32
+ }
33
+
34
+ .logo h1 {
35
+ font-size: 1.8rem;
36
+ font-weight: 700;
37
+ margin-bottom: 2px;
38
+ }
39
+
40
+ .logo .subtitle {
41
+ font-size: 0.9rem;
42
+ opacity: 0.8;
43
+ }
44
+
45
+ .user-section {
46
+ display: flex;
47
+ align-items: center;
48
+ gap: 15px;
49
+ }
50
+
51
+ .user-info {
52
+ display: flex;
53
+ align-items: center;
54
+ gap: 10px;
55
+ position: relative;
56
+ }
57
+
58
+ .user-name {
59
+ font-weight: 500;
60
+ }
61
+
62
+ .user-dropdown {
63
+ position: relative;
64
+ }
65
+
66
+ .dropdown-btn {
67
+ background: rgba(255, 255, 255, 0.2);
68
+ border: 1px solid rgba(255, 255, 255, 0.3);
69
+ border-radius: 8px;
70
+ color: white;
71
+ padding: 8px 12px;
72
+ cursor: pointer;
73
+ transition: all 0.2s ease;
74
+ }
75
+
76
+ .dropdown-btn:hover {
77
+ background: rgba(255, 255, 255, 0.3);
78
+ }
79
+
80
+ .dropdown-content {
81
+ display: none;
82
+ position: absolute;
83
+ right: 0;
84
+ top: 100%;
85
+ background: white;
86
+ min-width: 150px;
87
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
88
+ border-radius: 8px;
89
+ overflow: hidden;
90
+ z-index: 1000;
91
+ }
92
+
93
+ .dropdown-content a {
94
+ color: #333;
95
+ padding: 12px 16px;
96
+ text-decoration: none;
97
+ display: block;
98
+ transition: background 0.2s ease;
99
+ }
100
+
101
+ .dropdown-content a:hover {
102
+ background: #f8f9fa;
103
+ }
104
+
105
+ .user-dropdown:hover .dropdown-content {
106
+ display: block;
107
+ }
108
+
109
+ /* Buttons */
110
+ .btn-primary {
111
+ background: #4285F4;
112
+ color: white;
113
+ padding: 12px 24px;
114
+ border: none;
115
+ border-radius: 8px;
116
+ font-weight: 600;
117
+ cursor: pointer;
118
+ transition: all 0.2s ease;
119
+ display: inline-flex;
120
+ align-items: center;
121
+ text-decoration: none;
122
+ }
123
+
124
+ .btn-primary:hover {
125
+ background: #3367d6;
126
+ transform: translateY(-1px);
127
+ }
128
+
129
+ .btn-primary.small {
130
+ padding: 8px 16px;
131
+ font-size: 0.9rem;
132
+ }
133
+
134
+ /* Main Dashboard */
135
+ .dashboard-main {
136
+ max-width: 1200px;
137
+ margin: 0 auto;
138
+ padding: 40px 20px;
139
+ }
140
+
141
+ .dashboard-grid {
142
+ display: grid;
143
+ grid-template-columns: 1fr 300px;
144
+ gap: 30px;
145
+ margin-bottom: 30px;
146
+ }
147
+
148
+ .dashboard-card {
149
+ background: white;
150
+ border-radius: 12px;
151
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
152
+ padding: 24px;
153
+ transition: all 0.2s ease;
154
+ }
155
+
156
+ .dashboard-card:hover {
157
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
158
+ }
159
+
160
+ .dashboard-card.full-width {
161
+ grid-column: 1 / -1;
162
+ }
163
+
164
+ .card-header {
165
+ display: flex;
166
+ justify-content: space-between;
167
+ align-items: center;
168
+ margin-bottom: 20px;
169
+ }
170
+
171
+ .card-header h2 {
172
+ font-size: 1.3rem;
173
+ font-weight: 600;
174
+ color: #333;
175
+ }
176
+
177
+ .agent-count {
178
+ background: #e3f2fd;
179
+ color: #1976d2;
180
+ padding: 4px 8px;
181
+ border-radius: 12px;
182
+ font-size: 0.8rem;
183
+ font-weight: 500;
184
+ }
185
+
186
+ .card-content {
187
+ color: #666;
188
+ }
189
+
190
+ /* Agent Items */
191
+ .agent-item {
192
+ display: flex;
193
+ justify-content: space-between;
194
+ align-items: center;
195
+ padding: 16px 0;
196
+ border-bottom: 1px solid #f0f0f0;
197
+ }
198
+
199
+ .agent-item:last-child {
200
+ border-bottom: none;
201
+ }
202
+
203
+ .agent-info {
204
+ flex: 1;
205
+ }
206
+
207
+ .agent-name {
208
+ font-weight: 600;
209
+ color: #333;
210
+ margin-bottom: 4px;
211
+ }
212
+
213
+ .agent-status {
214
+ display: inline-block;
215
+ padding: 2px 8px;
216
+ border-radius: 10px;
217
+ font-size: 0.75rem;
218
+ font-weight: 500;
219
+ text-transform: uppercase;
220
+ }
221
+
222
+ .agent-status.online {
223
+ background: #e8f5e8;
224
+ color: #2e7d32;
225
+ }
226
+
227
+ .agent-status.offline {
228
+ background: #ffebee;
229
+ color: #c62828;
230
+ }
231
+
232
+ .agent-last-seen {
233
+ font-size: 0.8rem;
234
+ color: #999;
235
+ margin-top: 2px;
236
+ }
237
+
238
+ .btn-connect {
239
+ background: #4caf50;
240
+ color: white;
241
+ border: none;
242
+ padding: 8px 16px;
243
+ border-radius: 6px;
244
+ font-weight: 500;
245
+ cursor: pointer;
246
+ transition: all 0.2s ease;
247
+ }
248
+
249
+ .btn-connect:hover {
250
+ background: #45a049;
251
+ transform: translateY(-1px);
252
+ }
253
+
254
+ /* Quick Actions */
255
+ .action-buttons {
256
+ display: flex;
257
+ flex-direction: column;
258
+ gap: 12px;
259
+ }
260
+
261
+ .action-btn {
262
+ display: flex;
263
+ align-items: center;
264
+ gap: 12px;
265
+ padding: 16px;
266
+ background: #f8f9fa;
267
+ border: 1px solid #e9ecef;
268
+ border-radius: 8px;
269
+ cursor: pointer;
270
+ transition: all 0.2s ease;
271
+ text-align: left;
272
+ }
273
+
274
+ .action-btn:hover {
275
+ background: #e9ecef;
276
+ border-color: #dee2e6;
277
+ }
278
+
279
+ .action-icon {
280
+ font-size: 1.2rem;
281
+ }
282
+
283
+ .action-text {
284
+ font-weight: 500;
285
+ color: #333;
286
+ }
287
+
288
+ /* Session Items */
289
+ .session-item {
290
+ display: flex;
291
+ justify-content: space-between;
292
+ align-items: center;
293
+ padding: 16px 0;
294
+ border-bottom: 1px solid #f0f0f0;
295
+ }
296
+
297
+ .session-item:last-child {
298
+ border-bottom: none;
299
+ }
300
+
301
+ .session-info {
302
+ flex: 1;
303
+ }
304
+
305
+ .session-agent {
306
+ font-weight: 500;
307
+ color: #333;
308
+ font-family: monospace;
309
+ font-size: 0.9rem;
310
+ }
311
+
312
+ .session-time {
313
+ font-size: 0.8rem;
314
+ color: #999;
315
+ margin-top: 2px;
316
+ }
317
+
318
+ .session-details {
319
+ display: flex;
320
+ flex-direction: column;
321
+ align-items: flex-end;
322
+ gap: 4px;
323
+ }
324
+
325
+ .session-duration {
326
+ font-size: 0.8rem;
327
+ color: #666;
328
+ }
329
+
330
+ .session-status {
331
+ padding: 2px 8px;
332
+ border-radius: 10px;
333
+ font-size: 0.7rem;
334
+ font-weight: 500;
335
+ text-transform: uppercase;
336
+ }
337
+
338
+ .session-status.completed {
339
+ background: #e8f5e8;
340
+ color: #2e7d32;
341
+ }
342
+
343
+ .session-status.active {
344
+ background: #fff3e0;
345
+ color: #f57c00;
346
+ }
347
+
348
+ /* Empty States */
349
+ .no-data {
350
+ text-align: center;
351
+ color: #999;
352
+ font-style: italic;
353
+ }
354
+
355
+ .no-data a {
356
+ color: #4285F4;
357
+ text-decoration: none;
358
+ }
359
+
360
+ .no-data a:hover {
361
+ text-decoration: underline;
362
+ }
363
+
364
+ /* Login Overlay */
365
+ .login-overlay {
366
+ position: fixed;
367
+ top: 0;
368
+ left: 0;
369
+ width: 100%;
370
+ height: 100%;
371
+ background: rgba(0, 0, 0, 0.8);
372
+ display: flex;
373
+ align-items: center;
374
+ justify-content: center;
375
+ z-index: 1000;
376
+ }
377
+
378
+ .login-modal {
379
+ background: white;
380
+ border-radius: 16px;
381
+ padding: 40px;
382
+ max-width: 500px;
383
+ width: 90%;
384
+ text-align: center;
385
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
386
+ }
387
+
388
+ .login-content h2 {
389
+ margin-bottom: 12px;
390
+ color: #333;
391
+ }
392
+
393
+ .login-content p {
394
+ color: #666;
395
+ margin-bottom: 30px;
396
+ }
397
+
398
+ .dashboard-preview {
399
+ display: grid;
400
+ grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
401
+ gap: 20px;
402
+ margin: 30px 0;
403
+ }
404
+
405
+ .preview-section {
406
+ padding: 20px;
407
+ background: #f8f9fa;
408
+ border-radius: 8px;
409
+ text-align: center;
410
+ }
411
+
412
+ .preview-section h3 {
413
+ font-size: 1rem;
414
+ margin-bottom: 8px;
415
+ color: #333;
416
+ }
417
+
418
+ .preview-section p {
419
+ font-size: 0.8rem;
420
+ color: #666;
421
+ margin: 0;
422
+ }
423
+
424
+ .login-note {
425
+ margin-top: 20px;
426
+ font-size: 0.85rem;
427
+ color: #999;
428
+ }
429
+
430
+ /* Dashboard Skeleton (for unauthenticated preview) */
431
+ .dashboard-skeleton .dashboard-card.blurred {
432
+ filter: blur(2px);
433
+ opacity: 0.6;
434
+ pointer-events: none;
435
+ }
436
+
437
+ .skeleton-content {
438
+ display: flex;
439
+ flex-direction: column;
440
+ gap: 12px;
441
+ }
442
+
443
+ .skeleton-agent,
444
+ .skeleton-action,
445
+ .skeleton-session {
446
+ height: 60px;
447
+ background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
448
+ background-size: 200% 100%;
449
+ animation: loading 1.5s infinite;
450
+ border-radius: 8px;
451
+ }
452
+
453
+ .skeleton-action {
454
+ height: 50px;
455
+ }
456
+
457
+ .skeleton-session {
458
+ height: 40px;
459
+ }
460
+
461
+ @keyframes loading {
462
+ 0% {
463
+ background-position: 200% 0;
464
+ }
465
+ 100% {
466
+ background-position: -200% 0;
467
+ }
468
+ }
469
+
470
+ /* Loading Overlay */
471
+ .loading-overlay {
472
+ position: fixed;
473
+ top: 0;
474
+ left: 0;
475
+ width: 100%;
476
+ height: 100%;
477
+ background: rgba(255, 255, 255, 0.9);
478
+ display: flex;
479
+ flex-direction: column;
480
+ align-items: center;
481
+ justify-content: center;
482
+ z-index: 2000;
483
+ }
484
+
485
+ .loading-spinner {
486
+ width: 40px;
487
+ height: 40px;
488
+ border: 4px solid #f3f3f3;
489
+ border-top: 4px solid #4285F4;
490
+ border-radius: 50%;
491
+ animation: spin 1s linear infinite;
492
+ margin-bottom: 20px;
493
+ }
494
+
495
+ @keyframes spin {
496
+ 0% { transform: rotate(0deg); }
497
+ 100% { transform: rotate(360deg); }
498
+ }
499
+
500
+ /* Error State */
501
+ .error-state {
502
+ text-align: center;
503
+ padding: 60px 20px;
504
+ color: #666;
505
+ }
506
+
507
+ .error-state h2 {
508
+ margin-bottom: 16px;
509
+ color: #333;
510
+ }
511
+
512
+ .error-state p {
513
+ margin-bottom: 24px;
514
+ }
515
+
516
+ /* Responsive Design */
517
+ @media (max-width: 768px) {
518
+ .dashboard-grid {
519
+ grid-template-columns: 1fr;
520
+ gap: 20px;
521
+ }
522
+
523
+ .header-content {
524
+ flex-direction: column;
525
+ gap: 15px;
526
+ text-align: center;
527
+ }
528
+
529
+ .dashboard-main {
530
+ padding: 20px 15px;
531
+ }
532
+
533
+ .dashboard-card {
534
+ padding: 20px;
535
+ }
536
+
537
+ .login-modal {
538
+ padding: 30px 20px;
539
+ }
540
+
541
+ .dashboard-preview {
542
+ grid-template-columns: 1fr;
543
+ gap: 15px;
544
+ }
545
+
546
+ .agent-item,
547
+ .session-item {
548
+ flex-direction: column;
549
+ align-items: flex-start;
550
+ gap: 12px;
551
+ }
552
+
553
+ .session-details {
554
+ align-items: flex-start;
555
+ flex-direction: row;
556
+ gap: 12px;
557
+ }
558
+ }
@@ -0,0 +1,76 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Shell Mirror Dashboard</title>
7
+ <link rel="stylesheet" href="dashboard.css">
8
+ </head>
9
+ <body>
10
+ <!-- Header -->
11
+ <header class="dashboard-header">
12
+ <div class="header-content">
13
+ <div class="logo">
14
+ <h1>Shell Mirror</h1>
15
+ <span class="subtitle">Dashboard</span>
16
+ </div>
17
+ <div class="user-section" id="user-section">
18
+ <!-- Dynamic content based on auth status -->
19
+ </div>
20
+ </div>
21
+ </header>
22
+
23
+ <!-- Main Dashboard Content -->
24
+ <main class="dashboard-main" id="dashboard-main">
25
+ <!-- Will be populated by JavaScript based on auth status -->
26
+ </main>
27
+
28
+ <!-- Login Overlay (for unauthenticated users) -->
29
+ <div class="login-overlay" id="login-overlay" style="display: none;">
30
+ <div class="login-modal">
31
+ <div class="login-content">
32
+ <h2>Welcome to Shell Mirror</h2>
33
+ <p>Sign in to access your dashboard and manage your terminal sessions</p>
34
+
35
+ <!-- Dashboard Preview -->
36
+ <div class="dashboard-preview">
37
+ <div class="preview-section">
38
+ <h3>🖥️ Active Agents</h3>
39
+ <p>See and connect to your running Mac agents</p>
40
+ </div>
41
+ <div class="preview-section">
42
+ <h3>📊 Session History</h3>
43
+ <p>Track your past terminal sessions</p>
44
+ </div>
45
+ <div class="preview-section">
46
+ <h3>⚙️ Quick Actions</h3>
47
+ <p>Manage settings and download agents</p>
48
+ </div>
49
+ </div>
50
+
51
+ <button class="btn-primary" onclick="handleLogin()">
52
+ <svg width="20" height="20" viewBox="0 0 24 24" style="margin-right: 8px;">
53
+ <path fill="white" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
54
+ <path fill="white" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
55
+ <path fill="white" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
56
+ <path fill="white" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
57
+ </svg>
58
+ Sign in with Google
59
+ </button>
60
+
61
+ <p class="login-note">
62
+ Your credentials are secure and encrypted. We use Google OAuth 2.0 for authentication.
63
+ </p>
64
+ </div>
65
+ </div>
66
+ </div>
67
+
68
+ <!-- Loading indicator -->
69
+ <div class="loading-overlay" id="loading-overlay">
70
+ <div class="loading-spinner"></div>
71
+ <p>Loading dashboard...</p>
72
+ </div>
73
+
74
+ <script src="dashboard.js"></script>
75
+ </body>
76
+ </html>
@@ -0,0 +1,307 @@
1
+ // Dashboard functionality for Shell Mirror
2
+ class ShellMirrorDashboard {
3
+ constructor() {
4
+ this.isAuthenticated = false;
5
+ this.user = null;
6
+ this.agents = [];
7
+ this.sessions = [];
8
+ this.init();
9
+ }
10
+
11
+ async init() {
12
+ this.showLoading();
13
+
14
+ try {
15
+ const authStatus = await this.checkAuthStatus();
16
+
17
+ if (authStatus.isAuthenticated) {
18
+ this.isAuthenticated = true;
19
+ this.user = authStatus.user;
20
+ await this.loadDashboardData();
21
+ this.renderAuthenticatedDashboard();
22
+ } else {
23
+ this.renderUnauthenticatedDashboard();
24
+ }
25
+ } catch (error) {
26
+ console.error('Dashboard initialization failed:', error);
27
+ this.renderErrorState();
28
+ } finally {
29
+ this.hideLoading();
30
+ }
31
+ }
32
+
33
+ showLoading() {
34
+ document.getElementById('loading-overlay').style.display = 'flex';
35
+ }
36
+
37
+ hideLoading() {
38
+ document.getElementById('loading-overlay').style.display = 'none';
39
+ }
40
+
41
+ async checkAuthStatus() {
42
+ try {
43
+ const response = await fetch('/php-backend/api/auth-status.php');
44
+ const data = await response.json();
45
+
46
+ if (data.success && data.data && data.data.authenticated) {
47
+ return { isAuthenticated: true, user: data.data };
48
+ }
49
+ } catch (error) {
50
+ console.log('Auth check failed:', error);
51
+ }
52
+ return { isAuthenticated: false, user: null };
53
+ }
54
+
55
+ async loadDashboardData() {
56
+ try {
57
+ // Load active agents
58
+ const agentsResponse = await fetch('/php-backend/api/agents-list.php');
59
+ const agentsData = await agentsResponse.json();
60
+
61
+ if (agentsData.success && agentsData.data) {
62
+ this.agents = agentsData.data;
63
+ }
64
+
65
+ // TODO: Load session history when API is available
66
+ this.sessions = [
67
+ {
68
+ id: 1,
69
+ agentId: 'local-MacBookPro-1755126622411',
70
+ startTime: new Date('2025-08-13T22:10:00Z'),
71
+ duration: '15 minutes',
72
+ status: 'completed'
73
+ },
74
+ {
75
+ id: 2,
76
+ agentId: 'local-MacBookPro-1755123565749',
77
+ startTime: new Date('2025-08-13T21:30:00Z'),
78
+ duration: '45 minutes',
79
+ status: 'completed'
80
+ }
81
+ ];
82
+
83
+ } catch (error) {
84
+ console.error('Failed to load dashboard data:', error);
85
+ }
86
+ }
87
+
88
+ renderUnauthenticatedDashboard() {
89
+ // Update user section
90
+ document.getElementById('user-section').innerHTML = `
91
+ <button class="btn-primary small" onclick="handleLogin()">Sign In</button>
92
+ `;
93
+
94
+ // Show blurred dashboard skeleton
95
+ document.getElementById('dashboard-main').innerHTML = `
96
+ <div class="dashboard-skeleton">
97
+ <div class="dashboard-grid">
98
+ <div class="dashboard-card blurred">
99
+ <h2>🖥️ Active Agents</h2>
100
+ <div class="skeleton-content">
101
+ <div class="skeleton-agent"></div>
102
+ <div class="skeleton-agent"></div>
103
+ </div>
104
+ </div>
105
+
106
+ <div class="dashboard-card blurred">
107
+ <h2>⚡ Quick Actions</h2>
108
+ <div class="skeleton-content">
109
+ <div class="skeleton-action"></div>
110
+ <div class="skeleton-action"></div>
111
+ <div class="skeleton-action"></div>
112
+ </div>
113
+ </div>
114
+
115
+ <div class="dashboard-card blurred full-width">
116
+ <h2>📊 Recent Sessions</h2>
117
+ <div class="skeleton-content">
118
+ <div class="skeleton-session"></div>
119
+ <div class="skeleton-session"></div>
120
+ </div>
121
+ </div>
122
+ </div>
123
+ </div>
124
+ `;
125
+
126
+ // Show login overlay
127
+ document.getElementById('login-overlay').style.display = 'flex';
128
+ }
129
+
130
+ renderAuthenticatedDashboard() {
131
+ // Update user section
132
+ document.getElementById('user-section').innerHTML = `
133
+ <div class="user-info">
134
+ <span class="user-name">${this.user.name || this.user.email}</span>
135
+ <div class="user-dropdown">
136
+ <button class="dropdown-btn">⚙️</button>
137
+ <div class="dropdown-content">
138
+ <a href="#" onclick="dashboard.logout()">Logout</a>
139
+ <a href="/">Home</a>
140
+ </div>
141
+ </div>
142
+ </div>
143
+ `;
144
+
145
+ // Render main dashboard content
146
+ document.getElementById('dashboard-main').innerHTML = `
147
+ <div class="dashboard-grid">
148
+ ${this.renderActiveAgents()}
149
+ ${this.renderQuickActions()}
150
+ ${this.renderRecentSessions()}
151
+ </div>
152
+ `;
153
+
154
+ // Hide login overlay
155
+ document.getElementById('login-overlay').style.display = 'none';
156
+ }
157
+
158
+ renderActiveAgents() {
159
+ const agentCount = this.agents.length;
160
+ const agentsHtml = this.agents.map(agent => `
161
+ <div class="agent-item">
162
+ <div class="agent-info">
163
+ <div class="agent-name">${agent.hostname || agent.id}</div>
164
+ <div class="agent-status ${agent.status}">${agent.status}</div>
165
+ <div class="agent-last-seen">Last seen: ${this.formatLastSeen(agent.lastSeen)}</div>
166
+ </div>
167
+ <button class="btn-connect" onclick="dashboard.connectToAgent('${agent.id}')">
168
+ Connect
169
+ </button>
170
+ </div>
171
+ `).join('');
172
+
173
+ return `
174
+ <div class="dashboard-card">
175
+ <div class="card-header">
176
+ <h2>🖥️ Active Agents</h2>
177
+ <span class="agent-count">${agentCount} agent${agentCount !== 1 ? 's' : ''}</span>
178
+ </div>
179
+ <div class="card-content">
180
+ ${agentCount > 0 ? agentsHtml : '<p class="no-data">No active agents. <a href="#" onclick="dashboard.showAgentInstructions()">Set up an agent</a></p>'}
181
+ </div>
182
+ </div>
183
+ `;
184
+ }
185
+
186
+ renderQuickActions() {
187
+ return `
188
+ <div class="dashboard-card">
189
+ <h2>⚡ Quick Actions</h2>
190
+ <div class="card-content">
191
+ <div class="action-buttons">
192
+ <button class="action-btn" onclick="dashboard.startNewSession()">
193
+ <span class="action-icon">🚀</span>
194
+ <span class="action-text">New Terminal Session</span>
195
+ </button>
196
+ <button class="action-btn" onclick="dashboard.showAgentInstructions()">
197
+ <span class="action-icon">📥</span>
198
+ <span class="action-text">Download Agent</span>
199
+ </button>
200
+ <button class="action-btn" onclick="dashboard.showSettings()">
201
+ <span class="action-icon">⚙️</span>
202
+ <span class="action-text">Settings</span>
203
+ </button>
204
+ </div>
205
+ </div>
206
+ </div>
207
+ `;
208
+ }
209
+
210
+ renderRecentSessions() {
211
+ const sessionsHtml = this.sessions.map(session => `
212
+ <div class="session-item">
213
+ <div class="session-info">
214
+ <div class="session-agent">${session.agentId}</div>
215
+ <div class="session-time">${this.formatDate(session.startTime)}</div>
216
+ </div>
217
+ <div class="session-details">
218
+ <span class="session-duration">${session.duration}</span>
219
+ <span class="session-status ${session.status}">${session.status}</span>
220
+ </div>
221
+ </div>
222
+ `).join('');
223
+
224
+ return `
225
+ <div class="dashboard-card full-width">
226
+ <h2>📊 Recent Sessions</h2>
227
+ <div class="card-content">
228
+ ${this.sessions.length > 0 ? sessionsHtml : '<p class="no-data">No recent sessions</p>'}
229
+ </div>
230
+ </div>
231
+ `;
232
+ }
233
+
234
+ renderErrorState() {
235
+ document.getElementById('dashboard-main').innerHTML = `
236
+ <div class="error-state">
237
+ <h2>⚠️ Something went wrong</h2>
238
+ <p>Unable to load dashboard. Please try refreshing the page.</p>
239
+ <button class="btn-primary" onclick="location.reload()">Refresh</button>
240
+ </div>
241
+ `;
242
+ }
243
+
244
+ // Utility methods
245
+ formatLastSeen(lastSeen) {
246
+ if (!lastSeen) return 'Unknown';
247
+
248
+ const now = Date.now() / 1000;
249
+ const diff = now - lastSeen;
250
+
251
+ if (diff < 60) return 'Just now';
252
+ if (diff < 3600) return `${Math.floor(diff / 60)} minutes ago`;
253
+ if (diff < 86400) return `${Math.floor(diff / 3600)} hours ago`;
254
+ return `${Math.floor(diff / 86400)} days ago`;
255
+ }
256
+
257
+ formatDate(date) {
258
+ return new Intl.DateTimeFormat('en-US', {
259
+ month: 'short',
260
+ day: 'numeric',
261
+ hour: '2-digit',
262
+ minute: '2-digit'
263
+ }).format(date);
264
+ }
265
+
266
+ // Action handlers
267
+ async connectToAgent(agentId) {
268
+ window.location.href = `/app/terminal.html?agent=${agentId}`;
269
+ }
270
+
271
+ startNewSession() {
272
+ window.location.href = '/app/terminal.html';
273
+ }
274
+
275
+ showAgentInstructions() {
276
+ // TODO: Show modal with agent setup instructions
277
+ alert('Agent setup instructions coming soon!');
278
+ }
279
+
280
+ showSettings() {
281
+ // TODO: Implement settings modal
282
+ alert('Settings coming soon!');
283
+ }
284
+
285
+ async logout() {
286
+ try {
287
+ await fetch('/php-backend/api/auth-logout.php', { method: 'POST' });
288
+ window.location.href = '/';
289
+ } catch (error) {
290
+ console.error('Logout failed:', error);
291
+ // Fallback - redirect anyway
292
+ window.location.href = '/';
293
+ }
294
+ }
295
+ }
296
+
297
+ // Global functions
298
+ function handleLogin() {
299
+ const returnUrl = encodeURIComponent(window.location.pathname);
300
+ window.location.href = `/php-backend/api/auth-login.php?return=${returnUrl}`;
301
+ }
302
+
303
+ // Initialize dashboard
304
+ let dashboard;
305
+ document.addEventListener('DOMContentLoaded', () => {
306
+ dashboard = new ShellMirrorDashboard();
307
+ });
@@ -30,9 +30,40 @@
30
30
  #connect-container { padding: 2em; text-align: center; }
31
31
  #agent-id-input { font-size: 1.2em; padding: 8px; width: 400px; margin-bottom: 1em; }
32
32
  #connect-btn { font-size: 1.2em; padding: 10px 20px; }
33
+
34
+ /* Back to Dashboard Button */
35
+ .back-to-dashboard {
36
+ position: fixed;
37
+ top: 20px;
38
+ left: 20px;
39
+ background: rgba(66, 133, 244, 0.9);
40
+ color: white;
41
+ border: none;
42
+ padding: 12px 20px;
43
+ border-radius: 8px;
44
+ font-weight: 500;
45
+ cursor: pointer;
46
+ z-index: 1000;
47
+ transition: all 0.2s ease;
48
+ display: flex;
49
+ align-items: center;
50
+ gap: 8px;
51
+ text-decoration: none;
52
+ }
53
+
54
+ .back-to-dashboard:hover {
55
+ background: rgba(51, 103, 214, 0.9);
56
+ transform: translateY(-1px);
57
+ }
33
58
  </style>
34
59
  </head>
35
60
  <body>
61
+ <!-- Back to Dashboard Button -->
62
+ <a href="/app/dashboard.html" class="back-to-dashboard">
63
+ <span>←</span>
64
+ <span>Dashboard</span>
65
+ </a>
66
+
36
67
  <div id="connect-container">
37
68
  <h2>Terminal Mirror</h2>
38
69
  <div id="agent-discovery">
package/public/index.html CHANGED
@@ -662,31 +662,30 @@
662
662
 
663
663
 
664
664
  <script>
665
- // Check if user is already authenticated and redirect to terminal
666
- async function checkAuthAndRedirect() {
665
+ // Check authentication status without auto-redirect
666
+ async function checkAuthStatus() {
667
667
  try {
668
668
  const response = await fetch('/php-backend/api/auth-status.php');
669
669
  const data = await response.json();
670
670
 
671
671
  if (data.success && data.data && data.data.authenticated) {
672
- // User is logged in, redirect to terminal
673
- window.location.href = '/app/terminal.html';
674
- return true;
672
+ return { isAuthenticated: true, user: data.data };
675
673
  }
676
674
  } catch (error) {
677
675
  console.log('Auth check failed:', error);
678
676
  }
679
- return false;
677
+ return { isAuthenticated: false, user: null };
680
678
  }
681
679
 
682
680
  // Handle Google login - direct web OAuth
683
681
  async function handleGoogleLogin() {
684
- // Check if already logged in first
685
- const isLoggedIn = await checkAuthAndRedirect();
686
- if (isLoggedIn) return;
687
-
688
682
  // Direct OAuth flow using the web backend
689
- window.location.href = '/php-backend/api/auth-login.php';
683
+ window.location.href = '/php-backend/api/auth-login.php?return=' + encodeURIComponent('/app/dashboard');
684
+ }
685
+
686
+ // Handle dashboard navigation
687
+ async function openDashboard() {
688
+ window.location.href = '/app/dashboard.html';
690
689
  }
691
690
 
692
691
  // Load version info
@@ -712,12 +711,43 @@
712
711
  }
713
712
  }
714
713
 
715
- // Check authentication status on page load
716
- document.addEventListener('DOMContentLoaded', () => {
717
- checkAuthAndRedirect();
714
+ // Initialize page on load
715
+ document.addEventListener('DOMContentLoaded', async () => {
716
+ await updateHeaderAndCTA();
718
717
  loadVersionInfo();
719
718
  });
720
719
 
720
+ // Update header and CTA based on auth status
721
+ async function updateHeaderAndCTA() {
722
+ const authStatus = await checkAuthStatus();
723
+
724
+ // Update header navigation
725
+ const headerNav = document.querySelector('nav');
726
+ if (authStatus.isAuthenticated) {
727
+ // Show user info + dashboard link
728
+ headerNav.innerHTML = `
729
+ <div class="logo">Shell Mirror</div>
730
+ <div style="display: flex; align-items: center; gap: 15px;">
731
+ <span style="color: white; opacity: 0.9;">Welcome, ${authStatus.user.name || authStatus.user.email}</span>
732
+ <a href="/app/dashboard.html" class="cta-button">Dashboard</a>
733
+ </div>
734
+ `;
735
+ } else {
736
+ // Show sign in option
737
+ headerNav.innerHTML = `
738
+ <div class="logo">Shell Mirror</div>
739
+ <button class="cta-button" onclick="handleGoogleLogin()">Sign In</button>
740
+ `;
741
+ }
742
+
743
+ // Update main CTA buttons
744
+ const primaryButtons = document.querySelectorAll('.btn-primary');
745
+ primaryButtons.forEach(button => {
746
+ button.textContent = 'Open Dashboard';
747
+ button.onclick = openDashboard;
748
+ });
749
+ }
750
+
721
751
  // Smooth scrolling for anchor links
722
752
  document.querySelectorAll('a[href^="#"]').forEach(anchor => {
723
753
  anchor.addEventListener('click', function (e) {