shell-mirror 1.5.32 → 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 +2 -0
- package/lib/auto-start.js +8 -4
- package/package.json +1 -1
- package/public/app/dashboard.css +558 -0
- package/public/app/dashboard.html +76 -0
- package/public/app/dashboard.js +307 -0
- package/public/app/terminal.html +31 -0
- package/public/index.html +44 -14
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/lib/auto-start.js
CHANGED
|
@@ -449,10 +449,10 @@ WS_URL=${wsConfig.wsUrl}` : ''}
|
|
|
449
449
|
|
|
450
450
|
try {
|
|
451
451
|
// Register this agent with the cloud backend
|
|
452
|
-
await this.registerAsMacAgent(authInfo);
|
|
452
|
+
const agentId = await this.registerAsMacAgent(authInfo);
|
|
453
453
|
|
|
454
454
|
// Start the terminal agent with cloud WebSocket connection
|
|
455
|
-
await this.startTerminalAgent(authInfo);
|
|
455
|
+
await this.startTerminalAgent(authInfo, agentId);
|
|
456
456
|
|
|
457
457
|
} catch (error) {
|
|
458
458
|
console.error('❌ Failed to start Shell Mirror:', error.message);
|
|
@@ -465,8 +465,10 @@ WS_URL=${wsConfig.wsUrl}` : ''}
|
|
|
465
465
|
console.log(` User: ${authInfo.email}`);
|
|
466
466
|
console.log(` Machine: ${os.hostname()}`);
|
|
467
467
|
|
|
468
|
+
// Generate agent ID outside try block so it's always available
|
|
469
|
+
const agentId = `local-${os.hostname()}-${Date.now()}`;
|
|
470
|
+
|
|
468
471
|
try {
|
|
469
|
-
const agentId = `local-${os.hostname()}-${Date.now()}`;
|
|
470
472
|
const registrationData = {
|
|
471
473
|
agentId: agentId,
|
|
472
474
|
ownerEmail: authInfo.email,
|
|
@@ -530,9 +532,11 @@ WS_URL=${wsConfig.wsUrl}` : ''}
|
|
|
530
532
|
console.log(` Error details: ${error.stack}`);
|
|
531
533
|
console.log(' Server will run locally only');
|
|
532
534
|
}
|
|
535
|
+
|
|
536
|
+
return agentId; // Return the agent ID for use in startTerminalAgent
|
|
533
537
|
}
|
|
534
538
|
|
|
535
|
-
async startTerminalAgent(authInfo) {
|
|
539
|
+
async startTerminalAgent(authInfo, agentId) {
|
|
536
540
|
console.log('');
|
|
537
541
|
console.log('🔄 Starting terminal agent with cloud WebSocket connection...');
|
|
538
542
|
|
package/package.json
CHANGED
|
@@ -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
|
+
});
|
package/public/app/terminal.html
CHANGED
|
@@ -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
|
|
666
|
-
async function
|
|
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
|
-
|
|
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
|
-
//
|
|
716
|
-
document.addEventListener('DOMContentLoaded', () => {
|
|
717
|
-
|
|
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) {
|