Ditzzy AF commited on
Commit
7a87633
·
1 Parent(s): ca87d12

fix: Remove redundant CMD in Dockerfile and optimize terminal.ejs styles

Browse files

- Removed the redundant `CMD` instruction from the Dockerfile as it was commented out and not in use.
- Optimized the `terminal.ejs` file by cleaning up and organizing the CSS styles, reducing the file size, and improving readability.
- Updated the title tag to better reflect the purpose of the page.

Files changed (2) hide show
  1. app.js +0 -14
  2. views/terminal.ejs +272 -692
app.js CHANGED
@@ -595,21 +595,7 @@ process.on('unhandledRejection', (reason, promise) => {
595
  });
596
 
597
  // === Graceful Shutdown ===
598
- process.on('SIGTERM', () => {
599
- console.log('SIGTERM received, shutting down gracefully');
600
- httpServer.close(() => {
601
- console.log('Process terminated');
602
- process.exit(0);
603
- });
604
- });
605
 
606
- process.on('SIGINT', () => {
607
- console.log('SIGINT received, shutting down gracefully');
608
- httpServer.close(() => {
609
- console.log('Process terminated');
610
- process.exit(0);
611
- });
612
- });
613
 
614
  // === Start Server ===
615
  httpServer.listen(PORT, () => {
 
595
  });
596
 
597
  // === Graceful Shutdown ===
 
 
 
 
 
 
 
598
 
 
 
 
 
 
 
 
599
 
600
  // === Start Server ===
601
  httpServer.listen(PORT, () => {
views/terminal.ejs CHANGED
@@ -3,568 +3,342 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Terminal</title>
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
- <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600&display=swap" rel="stylesheet">
10
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css">
11
  <style>
12
- * {
13
- margin: 0;
14
- padding: 0;
15
- box-sizing: border-box;
16
- }
17
-
 
 
 
 
 
 
 
 
 
18
  body {
19
  font-family: 'JetBrains Mono', monospace;
20
- background: linear-gradient(135deg, #0c0c0c 0%, #1a1a1a 100%);
21
- color: #e4e4e7;
22
  min-height: 100vh;
23
- overflow: hidden;
24
  }
25
-
26
- .container {
27
- display: grid;
28
- grid-template-columns: 1fr 320px;
29
- grid-template-rows: 60px 1fr;
30
- height: 100vh;
31
- gap: 1px;
32
- background: #27272a;
33
- }
34
-
35
  .header {
36
- grid-column: 1 / -1;
37
- background: rgba(39, 39, 42, 0.95);
38
- backdrop-filter: blur(10px);
39
- border-bottom: 1px solid #3f3f46;
40
  display: flex;
41
  align-items: center;
42
  justify-content: space-between;
43
- padding: 0 24px;
44
- position: relative;
45
- }
46
-
47
- .header::before {
48
- content: '';
49
- position: absolute;
50
  top: 0;
51
- left: 0;
52
- right: 0;
53
- height: 1px;
54
- background: linear-gradient(90deg, transparent, #10b981, transparent);
55
  }
56
-
57
  .logo {
58
  display: flex;
59
  align-items: center;
60
- gap: 8px;
61
  font-weight: 600;
62
- font-size: 16px;
 
63
  }
64
-
65
- .status-dot {
66
- width: 8px;
67
- height: 8px;
68
- background: #10b981;
69
  border-radius: 50%;
70
  animation: pulse 2s infinite;
71
  }
72
-
73
  @keyframes pulse {
74
  0%, 100% { opacity: 1; }
75
  50% { opacity: 0.3; }
76
  }
77
-
78
  .header-actions {
79
  display: flex;
80
- gap: 12px;
81
- align-items: center;
82
  }
83
-
84
  .btn {
85
- background: rgba(255, 255, 255, 0.05);
86
- border: 1px solid rgba(255, 255, 255, 0.1);
87
- color: #e4e4e7;
88
- padding: 6px 12px;
89
  border-radius: 6px;
90
- font-size: 12px;
91
  cursor: pointer;
92
- transition: all 0.2s ease;
93
  font-family: inherit;
 
94
  }
95
-
96
- .btn:hover {
97
- background: rgba(255, 255, 255, 0.1);
98
- border-color: rgba(255, 255, 255, 0.2);
99
- }
100
-
101
- .btn.danger:hover {
102
- background: rgba(239, 68, 68, 0.2);
103
- border-color: #ef4444;
104
  }
105
-
106
- .terminal-section {
107
- background: #18181b;
108
- border-right: 1px solid #3f3f46;
109
- position: relative;
 
 
 
110
  }
111
-
112
  .terminal-card {
113
- background: #18181b;
114
- border-radius: 16px;
115
- box-shadow: 0 4px 24px rgba(0,0,0,0.25);
116
- border: 1px solid #27272a;
117
- overflow: hidden;
118
- margin-bottom: 16px;
 
 
 
 
119
  }
120
-
121
  .terminal-header {
122
- border-radius: 16px 16px 0 0;
123
- border-bottom: 1px solid #3f3f46;
124
- background: rgba(39, 39, 42, 0.9);
125
- padding: 12px 20px;
126
- font-size: 13px;
127
- font-weight: 500;
128
  display: flex;
129
  align-items: center;
130
- gap: 8px;
 
 
 
 
 
 
131
  }
132
-
133
  .terminal-icon {
134
- color: #10b981;
 
135
  }
136
-
137
  #terminal {
138
- height: calc(100vh - 60px - 45px);
139
- padding: 12px;
140
- background: #0c0c0c;
141
- border-radius: 0 0 16px 16px;
142
  min-height: 180px;
 
143
  }
144
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  .sidebar {
146
- background: #18181b;
 
147
  display: flex;
148
  flex-direction: column;
 
 
 
 
 
 
 
 
 
149
  }
150
-
151
  .sidebar-section {
152
- border-bottom: 1px solid #3f3f46;
153
- padding: 20px;
154
- background: rgba(24, 24, 27, 0.5);
155
- margin: 0;
156
- border-radius: 0;
157
- }
158
-
159
- .sidebar-section:last-child {
160
- border-bottom: none;
161
- flex: 1;
162
  }
163
-
164
  .section-title {
165
  font-size: 13px;
166
- font-weight: 600;
167
- color: #a1a1aa;
168
- margin-bottom: 12px;
169
- text-transform: uppercase;
170
  letter-spacing: 0.5px;
171
- }
172
-
173
- .system-info {
174
  display: flex;
175
- flex-direction: column;
176
- gap: 8px;
177
  }
178
-
179
  .info-item {
180
  display: flex;
181
  justify-content: space-between;
182
  align-items: center;
183
  font-size: 12px;
184
- line-height: 1.4;
185
  }
186
-
187
- .info-label {
188
- color: #71717a;
189
- display: flex;
190
- align-items: center;
191
- gap: 6px;
192
- }
193
-
194
- .info-value {
195
- color: #e4e4e7;
196
- font-weight: 500;
197
- }
198
-
199
  .metric-bar {
200
  width: 100%;
201
- height: 4px;
202
- background: rgba(255, 255, 255, 0.05);
203
  border-radius: 2px;
204
- margin-top: 6px;
205
  overflow: hidden;
206
  }
207
-
208
  .metric-fill {
209
  height: 100%;
210
  border-radius: 2px;
211
- transition: width 0.3s ease;
212
  }
213
-
214
- .cpu-fill {
215
- background: linear-gradient(90deg, #10b981, #059669);
216
- }
217
-
218
- .memory-fill {
219
- background: linear-gradient(90deg, #3b82f6, #2563eb);
220
- }
221
-
222
- .disk-fill {
223
- background: linear-gradient(90deg, #f59e0b, #d97706);
224
- }
225
-
226
- .network-fill {
227
- background: linear-gradient(90deg, #8b5cf6, #7c3aed);
228
- }
229
-
230
  .mini-chart {
231
- height: 60px;
232
- background: rgba(255, 255, 255, 0.02);
233
  border-radius: 8px;
234
- margin-top: 12px;
235
- position: relative;
236
- overflow: hidden;
237
  display: flex;
238
  align-items: end;
239
- padding: 4px;
240
  gap: 1px;
 
241
  }
242
-
243
  .chart-bar {
244
  flex: 1;
245
- background: rgba(16, 185, 129, 0.3);
246
  border-radius: 1px 1px 0 0;
247
  min-height: 2px;
248
- transition: height 0.3s ease;
249
  }
250
-
251
  .processes {
252
- max-height: 200px;
253
  overflow-y: auto;
254
  }
255
-
256
- .processes::-webkit-scrollbar {
257
- width: 4px;
258
- }
259
-
260
- .processes::-webkit-scrollbar-track {
261
- background: rgba(255, 255, 255, 0.05);
262
- }
263
-
264
- .processes::-webkit-scrollbar-thumb {
265
- background: rgba(255, 255, 255, 0.2);
266
- border-radius: 2px;
267
- }
268
-
269
  .process-item {
270
  display: flex;
271
  justify-content: space-between;
272
  align-items: center;
273
- padding: 6px 0;
274
- font-size: 11px;
275
- border-bottom: 1px solid rgba(255, 255, 255, 0.05);
276
- }
277
-
278
- .process-item:last-child {
279
- border-bottom: none;
280
- }
281
-
282
- .process-name {
283
- color: #e4e4e7;
284
- flex: 1;
285
- white-space: nowrap;
286
- overflow: hidden;
287
- text-overflow: ellipsis;
288
- }
289
-
290
- .process-cpu {
291
- color: #10b981;
292
- font-weight: 500;
293
- margin-left: 8px;
294
- }
295
-
296
- .connection-status {
297
- position: absolute;
298
- top: 12px;
299
- right: 20px;
300
  font-size: 11px;
301
- color: #71717a;
302
- display: flex;
303
- align-items: center;
304
- gap: 6px;
305
- }
306
-
307
- .connection-dot {
308
- width: 6px;
309
- height: 6px;
310
- border-radius: 50%;
311
- background: #ef4444;
312
- transition: background-color 0.3s ease;
313
  }
314
-
315
- .connection-dot.connected {
316
- background: #10b981;
317
- }
318
-
319
  /* Mobile Styles */
320
- @media (max-width: 768px) {
321
- .container {
322
- grid-template-columns: 1fr;
323
- grid-template-rows: 50px 1fr 0px;
324
- gap: 0;
325
- }
326
-
327
- .header {
328
- padding: 0 16px;
329
- font-size: 14px;
330
- z-index: 1000;
331
- position: relative;
332
- }
333
-
334
- .logo {
335
- font-size: 14px;
336
- gap: 6px;
337
- }
338
-
339
- .status-dot {
340
- width: 6px;
341
- height: 6px;
342
- }
343
-
344
- .btn {
345
- padding: 4px 8px;
346
- font-size: 11px;
347
- }
348
-
349
- .terminal-section {
350
- border-right: none;
351
- position: relative;
352
- display: flex;
353
- flex-direction: column;
354
- }
355
-
356
- .terminal-card {
357
- background: #18181b;
358
- border-radius: 16px;
359
- box-shadow: 0 4px 24px rgba(0,0,0,0.25);
360
- border: 1px solid #27272a;
361
- overflow: hidden;
362
- margin-bottom: 16px;
363
- }
364
-
365
- .terminal-header {
366
- border-radius: 16px 16px 0 0;
367
- border-bottom: 1px solid #3f3f46;
368
- background: rgba(39, 39, 42, 0.9);
369
- padding: 12px 20px;
370
- }
371
-
372
- #terminal {
373
- border-radius: 0 0 16px 16px;
374
- background: #0c0c0c;
375
- padding: 12px;
376
- min-height: 180px;
377
- }
378
-
379
- .connection-status {
380
- right: 16px;
381
- top: 10px;
382
- font-size: 10px;
383
- }
384
-
385
  .sidebar {
386
- display: none; /* Hidden by default on mobile */
387
  position: fixed;
388
- bottom: 0;
389
- left: 0;
390
- right: 0;
391
- background: #18181b;
392
- border-top: 2px solid #3f3f46;
393
- max-height: 70vh;
394
- overflow-y: auto;
395
  z-index: 1001;
396
  transform: translateY(100%);
397
- transition: transform 0.3s ease;
398
- border-radius: 16px 16px 0 0;
399
- box-shadow: 0 -10px 40px rgba(0, 0, 0, 0.6);
400
- }
401
-
402
- .sidebar.show {
403
  display: flex;
404
- transform: translateY(0);
405
  }
406
-
407
- .sidebar-section {
408
- padding: 16px 20px;
409
- margin: 8px 16px;
410
- background: rgba(39, 39, 42, 0.6);
411
- border-radius: 12px;
412
- border: 1px solid rgba(63, 63, 70, 0.3);
413
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
414
- backdrop-filter: blur(10px);
415
- }
416
-
417
- .sidebar-section:first-child {
418
- margin-top: 16px;
419
- }
420
-
421
- .sidebar-section:last-child {
422
- margin-bottom: 16px;
423
- flex: none;
424
- }
425
-
426
- .section-title {
427
- font-size: 12px;
428
- margin-bottom: 12px;
429
- color: #10b981;
430
- display: flex;
431
- align-items: center;
432
- gap: 8px;
433
- }
434
-
435
- .section-title::before {
436
- content: '';
437
- width: 3px;
438
- height: 12px;
439
- background: linear-gradient(135deg, #10b981, #059669);
440
- border-radius: 2px;
441
- }
442
-
443
- .info-item {
444
- font-size: 11px;
445
- padding: 4px 0;
446
- }
447
-
448
- .processes {
449
- max-height: 120px;
450
- }
451
-
452
- .mini-chart {
453
- height: 50px;
454
- margin-top: 8px;
455
- }
456
-
457
- /* Mobile toggle button */
458
  .mobile-toggle {
459
- display: block;
460
  position: fixed;
461
- bottom: 20px;
462
- right: 20px;
463
- width: 56px;
464
- height: 56px;
465
- background: linear-gradient(135deg, #10b981, #059669);
466
  border: none;
467
  border-radius: 50%;
468
  color: white;
469
- font-size: 24px;
470
  cursor: pointer;
471
- z-index: 1002;
472
- box-shadow: 0 8px 25px rgba(16, 185, 129, 0.4);
473
- transition: all 0.3s ease;
474
- display: flex;
475
  align-items: center;
476
  justify-content: center;
 
477
  }
478
-
479
- .mobile-toggle:hover {
480
- transform: scale(1.1);
481
- box-shadow: 0 12px 35px rgba(16, 185, 129, 0.6);
482
- }
483
-
484
  .mobile-toggle.active {
485
- background: linear-gradient(135deg, #ef4444, #dc2626);
486
  transform: rotate(45deg);
487
  }
488
-
489
- /* Mobile overlay */
 
 
 
490
  .mobile-overlay {
491
- display: none;
492
  position: fixed;
493
- top: 0;
494
- left: 0;
495
- right: 0;
496
- bottom: 0;
497
- background: rgba(0, 0, 0, 0.5);
498
  z-index: 1000;
499
  opacity: 0;
500
- transition: opacity 0.3s ease;
 
501
  }
502
-
503
  .mobile-overlay.show {
504
- display: block;
505
  opacity: 1;
506
- }
507
- }
508
-
509
- @media (max-width: 480px) {
510
- .container {
511
- grid-template-rows: 45px 1fr 0px;
512
- }
513
-
514
- .header {
515
- padding: 0 12px;
516
- }
517
-
518
- .header-actions {
519
- gap: 8px;
520
- }
521
-
522
- .sidebar-section {
523
- margin: 6px 12px;
524
- padding: 12px 16px;
525
- }
526
-
527
- .mobile-toggle {
528
- width: 50px;
529
- height: 50px;
530
- bottom: 16px;
531
- right: 16px;
532
- font-size: 20px;
533
- }
534
- }
535
-
536
- /* Desktop styles - hide mobile elements */
537
- @media (min-width: 769px) {
538
- .mobile-toggle,
539
- .mobile-overlay {
540
- display: none !important;
541
- }
542
-
543
- .sidebar {
544
- display: flex !important;
545
- position: relative !important;
546
- transform: none !important;
547
  }
548
  }
549
  </style>
550
  </head>
551
  <body>
552
- <div class="container">
553
- <div class="header">
554
- <div class="logo">
555
- <div class="status-dot"></div>
556
- Terminal
557
- </div>
558
- <div class="header-actions">
559
- <button class="btn" id="clear-btn">Clear</button>
560
- <a href="/logout" class="btn danger">Logout</a>
561
- </div>
562
  </div>
563
-
564
- <div class="terminal-section">
565
- <div class="terminal-card">
 
566
  <div class="terminal-header">
567
- <span class="terminal-icon">></span>
568
  Shell Session
569
  </div>
570
  <div id="terminal"></div>
@@ -574,130 +348,59 @@
574
  </div>
575
  </div>
576
  </div>
577
-
578
- <div class="sidebar">
579
- <div class="sidebar-section">
580
- <div class="section-title">System Overview</div>
581
- <div class="system-info">
582
- <div class="info-item">
583
- <div class="info-label">🖥️ Host</div>
584
- <div class="info-value" id="hostname">-</div>
585
- </div>
586
- <div class="info-item">
587
- <div class="info-label">⏱️ Uptime</div>
588
- <div class="info-value" id="uptime">-</div>
589
- </div>
590
- <div class="info-item">
591
- <div class="info-label">👥 Users</div>
592
- <div class="info-value" id="users">-</div>
593
- </div>
594
  </div>
595
- </div>
596
-
597
- <div class="sidebar-section">
598
- <div class="section-title">Performance</div>
599
- <div class="system-info">
600
- <div class="info-item">
601
- <div class="info-label">🧠 CPU</div>
602
- <div class="info-value" id="cpu-usage">0%</div>
603
- </div>
604
- <div class="metric-bar">
605
- <div class="metric-fill cpu-fill" id="cpu-bar" style="width: 0%"></div>
606
- </div>
607
-
608
- <div class="info-item" style="margin-top: 16px;">
609
- <div class="info-label">💾 Memory</div>
610
- <div class="info-value" id="memory-usage">0%</div>
611
- </div>
612
- <div class="metric-bar">
613
- <div class="metric-fill memory-fill" id="memory-bar" style="width: 0%"></div>
614
- </div>
615
-
616
- <div class="info-item" style="margin-top: 16px;">
617
- <div class="info-label">💿 Disk</div>
618
- <div class="info-value" id="disk-usage">0%</div>
619
- </div>
620
- <div class="metric-bar">
621
- <div class="metric-fill disk-fill" id="disk-bar" style="width: 0%"></div>
622
- </div>
623
-
624
  <div class="mini-chart" id="cpu-chart"></div>
625
  </div>
626
- </div>
627
-
628
- <div class="sidebar-section">
629
- <div class="section-title">Network & Storage</div>
630
- <div class="system-info">
631
- <div class="info-item">
632
- <div class="info-label">🌐 Network RX</div>
633
- <div class="info-value" id="network-rx">0 KB/s</div>
634
- </div>
635
- <div class="info-item">
636
- <div class="info-label">📡 Network TX</div>
637
- <div class="info-value" id="network-tx">0 KB/s</div>
638
- </div>
639
- <div class="info-item" style="margin-top: 8px;">
640
- <div class="info-label">💾 Memory Used</div>
641
- <div class="info-value" id="memory-details">0 MB / 0 MB</div>
642
- </div>
643
- <div class="info-item">
644
- <div class="info-label">💿 Disk Space</div>
645
- <div class="info-value" id="disk-details">0 GB / 0 GB</div>
646
- </div>
647
  </div>
648
- </div>
649
-
650
- <div class="sidebar-section">
651
- <div class="section-title">System Details</div>
652
- <div class="system-info">
653
- <div class="info-item">
654
- <div class="info-label">🏗️ Platform</div>
655
- <div class="info-value" id="platform">-</div>
656
- </div>
657
- <div class="info-item">
658
- <div class="info-label">🔧 Architecture</div>
659
- <div class="info-value" id="arch">-</div>
660
- </div>
661
- <div class="info-item">
662
- <div class="info-label">🧠 CPU Model</div>
663
- <div class="info-value" id="cpu-model">-</div>
664
- </div>
665
- <div class="info-item">
666
- <div class="info-label">⚡ CPU Cores</div>
667
- <div class="info-value" id="cpu-cores">-</div>
668
- </div>
669
- <div class="info-item">
670
- <div class="info-label">📊 Load Avg</div>
671
- <div class="info-value" id="load-avg">-</div>
672
- </div>
673
  </div>
674
- </div>
675
- <div class="sidebar-section">
676
- <div class="section-title">Top Processes</div>
677
- <div class="processes" id="processes">
678
- <div class="process-item">
679
- <div class="process-name">Loading...</div>
680
- <div class="process-cpu">-</div>
681
  </div>
682
  </div>
683
  </div>
684
  </div>
685
  </div>
686
-
687
  <!-- Mobile elements -->
688
  <div class="mobile-overlay" id="mobile-overlay"></div>
689
  <button class="mobile-toggle" id="mobile-toggle">📊</button>
690
-
691
  <script src="/socket.io/socket.io.js"></script>
692
  <script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js"></script>
693
  <script>
694
- // Initialize terminal
695
- let terminal;
696
- let socket;
697
- let isConnected = false;
698
- let cpuHistory = [];
699
-
700
- // Initialize when page loads
701
  function initializeTerminal() {
702
  terminal = new Terminal({
703
  cursorBlink: true,
@@ -707,7 +410,7 @@
707
  background: '#0c0c0c',
708
  foreground: '#e4e4e7',
709
  cursor: '#10b981',
710
- selection: 'rgba(16, 185, 129, 0.3)',
711
  black: '#27272a',
712
  red: '#ef4444',
713
  green: '#10b981',
@@ -718,128 +421,71 @@
718
  white: '#f4f4f5'
719
  }
720
  });
721
-
722
  terminal.open(document.getElementById('terminal'));
723
  terminal.focus();
724
-
725
- // Connect to server
726
  connectToServer();
727
-
728
- // Add event listeners for buttons
729
  document.getElementById('clear-btn').addEventListener('click', clearTerminal);
730
-
731
- // Mobile functionality
732
  initializeMobileToggle();
733
  }
734
-
735
  function initializeMobileToggle() {
736
  const mobileToggle = document.getElementById('mobile-toggle');
737
- const sidebar = document.querySelector('.sidebar');
738
  const overlay = document.getElementById('mobile-overlay');
739
-
740
  if (mobileToggle && sidebar && overlay) {
741
  mobileToggle.addEventListener('click', () => {
742
  const isVisible = sidebar.classList.contains('show');
743
-
744
- if (isVisible) {
745
- hideMobileSidebar();
746
- } else {
747
- showMobileSidebar();
748
- }
749
- });
750
-
751
- overlay.addEventListener('click', () => {
752
- hideMobileSidebar();
753
  });
754
-
755
- // Handle escape key
756
  document.addEventListener('keydown', (e) => {
757
- if (e.key === 'Escape' && sidebar.classList.contains('show')) {
758
- hideMobileSidebar();
759
- }
760
  });
761
  }
762
  }
763
-
764
  function showMobileSidebar() {
765
- const sidebar = document.querySelector('.sidebar');
766
  const overlay = document.getElementById('mobile-overlay');
767
  const toggle = document.getElementById('mobile-toggle');
768
-
769
  sidebar.classList.add('show');
770
  overlay.classList.add('show');
771
  toggle.classList.add('active');
772
  toggle.innerHTML = '✕';
773
-
774
- // Prevent body scroll
775
  document.body.style.overflow = 'hidden';
776
  }
777
-
778
  function hideMobileSidebar() {
779
- const sidebar = document.querySelector('.sidebar');
780
  const overlay = document.getElementById('mobile-overlay');
781
  const toggle = document.getElementById('mobile-toggle');
782
-
783
  sidebar.classList.remove('show');
784
  overlay.classList.remove('show');
785
  toggle.classList.remove('active');
786
  toggle.innerHTML = '📊';
787
-
788
- // Restore body scroll
789
  document.body.style.overflow = '';
790
  }
791
-
792
  function connectToServer() {
793
- // Initialize Socket.io connection
794
  socket = io();
795
-
796
  socket.on('connect', () => {
797
  isConnected = true;
798
  updateConnectionStatus(true);
799
- console.log('Connected to server');
800
  });
801
-
802
  socket.on('disconnect', () => {
803
  isConnected = false;
804
  updateConnectionStatus(false);
805
- console.log('Disconnected from server');
806
  terminal.writeln('\r\n\x1b[31m✗ Connection lost. Reconnecting...\x1b[0m');
807
  });
808
-
809
  socket.on('connect_error', (error) => {
810
- console.error('Connection error:', error);
811
  updateConnectionStatus(false);
812
  terminal.writeln('\r\n\x1b[31m✗ Connection failed\x1b[0m');
813
  });
814
-
815
- // Terminal communication
816
- socket.on('terminal_output', (data) => {
817
- terminal.write(data);
818
- });
819
-
820
- socket.on('system_info', (data) => {
821
- updateSystemInfo(data);
822
- });
823
-
824
- // Handle user input
825
- terminal.onData(data => {
826
- if (isConnected && socket) {
827
- socket.emit('terminal_input', data);
828
- }
829
- });
830
-
831
- // Handle terminal resize
832
- terminal.onResize(({ cols, rows }) => {
833
- if (isConnected && socket) {
834
- socket.emit('terminal_resize', { cols, rows });
835
- }
836
- });
837
  }
838
-
839
  function updateConnectionStatus(connected) {
840
  const dot = document.getElementById('connection-dot');
841
  const text = document.getElementById('connection-text');
842
-
843
  if (connected) {
844
  dot.classList.add('connected');
845
  text.textContent = 'Connected';
@@ -848,82 +494,52 @@
848
  text.textContent = 'Disconnected';
849
  }
850
  }
851
-
852
  function updateSystemInfo(data) {
853
  if (!data) return;
854
-
855
- // Update basic info
856
  document.getElementById('hostname').textContent = data.hostname || '-';
857
  document.getElementById('uptime').textContent = data.uptime || '-';
858
  document.getElementById('users').textContent = data.user_info ? '1' : '-';
859
-
860
- // Update metrics
861
  document.getElementById('cpu-usage').textContent = `${Math.round(data.cpu_usage || 0)}%`;
862
  document.getElementById('cpu-bar').style.width = `${data.cpu_usage || 0}%`;
863
-
864
  document.getElementById('memory-usage').textContent = `${Math.round(data.mem_usage || 0)}%`;
865
  document.getElementById('memory-bar').style.width = `${data.mem_usage || 0}%`;
866
-
867
  document.getElementById('disk-usage').textContent = `${Math.round(data.disk_usage || 0)}%`;
868
  document.getElementById('disk-bar').style.width = `${data.disk_usage || 0}%`;
869
-
870
- // Update detailed info
871
  document.getElementById('memory-details').textContent = `${data.used_mem || 0} MB / ${data.total_mem || 0} MB`;
872
-
873
- // Format disk info
874
  const diskUsed = typeof data.disk_used === 'number' ? `${data.disk_used} GB` : data.disk_used || '0';
875
  const diskTotal = typeof data.disk_total === 'number' ? `${data.disk_total} GB` : data.disk_total || '0';
876
  document.getElementById('disk-details').textContent = `${diskUsed} / ${diskTotal}`;
877
-
878
- // Update network info
879
  document.getElementById('network-rx').textContent = formatBytes(data.network_rx || 0) + '/s';
880
  document.getElementById('network-tx').textContent = formatBytes(data.network_tx || 0) + '/s';
881
-
882
- // Update system details
883
  document.getElementById('platform').textContent = data.platform || '-';
884
  document.getElementById('arch').textContent = data.arch || '-';
885
  document.getElementById('cpu-model').textContent = truncateText(data.cpu_model || '-', 20);
886
  document.getElementById('cpu-cores').textContent = data.cpu_cores || '-';
887
-
888
- // Update load average
889
  if (data.loadavg && Array.isArray(data.loadavg)) {
890
  document.getElementById('load-avg').textContent = data.loadavg.map(l => l.toFixed(2)).join(', ');
891
  } else {
892
  document.getElementById('load-avg').textContent = '-';
893
  }
894
-
895
- // Update CPU history chart
896
  if (data.cpu_usage !== undefined) {
897
  cpuHistory.push(data.cpu_usage);
898
- if (cpuHistory.length > 20) {
899
- cpuHistory.shift();
900
- }
901
  updateCpuChart();
902
  }
903
-
904
- // Update processes
905
- if (data.processes && Array.isArray(data.processes)) {
906
- updateProcessList(data.processes);
907
- }
908
  }
909
-
910
  function formatBytes(bytes) {
911
  if (bytes === 0) return '0 B';
912
- const k = 1024;
913
- const sizes = ['B', 'KB', 'MB', 'GB'];
914
  const i = Math.floor(Math.log(Math.abs(bytes)) / Math.log(k));
915
  return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
916
  }
917
-
918
  function truncateText(text, maxLength) {
919
  if (text.length <= maxLength) return text;
920
  return text.substring(0, maxLength - 3) + '...';
921
  }
922
-
923
  function updateCpuChart() {
924
  const chartContainer = document.getElementById('cpu-chart');
925
  chartContainer.innerHTML = '';
926
-
927
  cpuHistory.forEach(value => {
928
  const bar = document.createElement('div');
929
  bar.className = 'chart-bar';
@@ -931,7 +547,6 @@
931
  chartContainer.appendChild(bar);
932
  });
933
  }
934
-
935
  function updateProcessList(processes) {
936
  const container = document.getElementById('processes');
937
  if (!processes || !Array.isArray(processes) || processes.length === 0) {
@@ -943,7 +558,6 @@
943
  `;
944
  return;
945
  }
946
-
947
  container.innerHTML = processes.map(proc => `
948
  <div class="process-item">
949
  <div class="process-name">${proc.name || 'unknown'}</div>
@@ -951,69 +565,35 @@
951
  </div>
952
  `).join('');
953
  }
954
-
955
- function clearTerminal() {
956
- if (terminal) {
957
- terminal.clear();
958
- }
959
- }
960
-
961
- // Initialize when page loads
962
  document.addEventListener('DOMContentLoaded', initializeTerminal);
963
-
964
- // Handle window resize with proper terminal fitting
965
  let resizeTimeout;
966
  window.addEventListener('resize', () => {
967
  clearTimeout(resizeTimeout);
968
  resizeTimeout = setTimeout(() => {
969
  if (terminal && terminal.element && terminal.element.offsetParent !== null) {
970
  try {
971
- // Get the terminal container dimensions
972
  const container = document.getElementById('terminal');
973
  if (container) {
974
  const containerRect = container.getBoundingClientRect();
975
-
976
- // Only fit if container has dimensions
977
  if (containerRect.width > 0 && containerRect.height > 0) {
978
- // Use fitAddon if available, otherwise manual resize
979
- if (window.FitAddon) {
980
- const fitAddon = new FitAddon();
981
- terminal.loadAddon(fitAddon);
982
- fitAddon.fit();
983
- } else {
984
- // Manual calculation for terminal size
985
- const cols = Math.floor(containerRect.width / 9); // approx char width
986
- const rows = Math.floor(containerRect.height / 17); // approx line height
987
-
988
- if (cols > 10 && rows > 5) { // minimum viable size
989
- terminal.resize(cols, rows);
990
-
991
- // Notify server of new size
992
- if (isConnected && socket) {
993
- socket.emit('terminal_resize', { cols, rows });
994
- }
995
- }
996
  }
997
  }
998
  }
999
  } catch (error) {
1000
- console.warn('Terminal resize failed:', error);
1001
- // Fallback: try simple resize
1002
- try {
1003
- terminal.resize(80, 24);
1004
- } catch (fallbackError) {
1005
- console.warn('Fallback resize also failed:', fallbackError);
1006
- }
1007
  }
1008
  }
1009
- }, 150); // Debounce resize events
1010
  });
1011
-
1012
- // Handle orientation changes on mobile
1013
  window.addEventListener('orientationchange', () => {
1014
- setTimeout(() => {
1015
- window.dispatchEvent(new Event('resize'));
1016
- }, 500);
1017
  });
1018
  </script>
1019
  </body>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Web Terminal</title>
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
10
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css">
11
  <style>
12
+ :root {
13
+ --primary: #10b981;
14
+ --primary-dark: #059669;
15
+ --bg: #0c0c0c;
16
+ --bg-card: #18181b;
17
+ --bg-header: #23232a;
18
+ --border: #27272a;
19
+ --border-light: #3f3f46;
20
+ --text: #e4e4e7;
21
+ --text-muted: #a1a1aa;
22
+ --danger: #ef4444;
23
+ --shadow: 0 4px 24px rgba(0,0,0,0.25);
24
+ }
25
+ * { box-sizing: border-box; margin: 0; padding: 0; }
26
+ html, body { height: 100%; }
27
  body {
28
  font-family: 'JetBrains Mono', monospace;
29
+ background: var(--bg);
30
+ color: var(--text);
31
  min-height: 100vh;
32
+ overflow-x: hidden;
33
  }
 
 
 
 
 
 
 
 
 
 
34
  .header {
35
+ background: var(--bg-header);
36
+ border-bottom: 1px solid var(--border-light);
 
 
37
  display: flex;
38
  align-items: center;
39
  justify-content: space-between;
40
+ padding: 0 20px;
41
+ height: 56px;
42
+ position: sticky;
 
 
 
 
43
  top: 0;
44
+ z-index: 10;
45
+ box-shadow: 0 2px 8px rgba(0,0,0,0.08);
 
 
46
  }
 
47
  .logo {
48
  display: flex;
49
  align-items: center;
50
+ gap: 10px;
51
  font-weight: 600;
52
+ font-size: 18px;
53
+ letter-spacing: 1px;
54
  }
55
+ .logo-dot {
56
+ width: 10px; height: 10px;
57
+ background: var(--primary);
 
 
58
  border-radius: 50%;
59
  animation: pulse 2s infinite;
60
  }
 
61
  @keyframes pulse {
62
  0%, 100% { opacity: 1; }
63
  50% { opacity: 0.3; }
64
  }
 
65
  .header-actions {
66
  display: flex;
67
+ gap: 10px;
 
68
  }
 
69
  .btn {
70
+ background: rgba(255,255,255,0.05);
71
+ border: 1px solid var(--border-light);
72
+ color: var(--text);
73
+ padding: 6px 14px;
74
  border-radius: 6px;
75
+ font-size: 13px;
76
  cursor: pointer;
 
77
  font-family: inherit;
78
+ transition: all 0.2s;
79
  }
80
+ .btn:hover { background: rgba(16,185,129,0.08); border-color: var(--primary); }
81
+ .btn.danger:hover { background: rgba(239,68,68,0.15); border-color: var(--danger); }
82
+ .main {
83
+ display: flex;
84
+ flex-direction: row;
85
+ height: calc(100vh - 56px);
86
+ background: var(--border);
 
 
87
  }
88
+ .terminal-panel {
89
+ flex: 2;
90
+ display: flex;
91
+ flex-direction: column;
92
+ justify-content: stretch;
93
+ background: var(--bg-card);
94
+ border-right: 1px solid var(--border-light);
95
+ min-width: 0;
96
  }
 
97
  .terminal-card {
98
+ background: var(--bg-card);
99
+ border-radius: 18px;
100
+ box-shadow: var(--shadow);
101
+ border: 1px solid var(--border);
102
+ margin: 24px;
103
+ margin-bottom: 0;
104
+ display: flex;
105
+ flex-direction: column;
106
+ flex: 1;
107
+ min-width: 0;
108
  }
 
109
  .terminal-header {
 
 
 
 
 
 
110
  display: flex;
111
  align-items: center;
112
+ gap: 10px;
113
+ padding: 14px 20px;
114
+ border-bottom: 1px solid var(--border-light);
115
+ font-size: 15px;
116
+ font-weight: 600;
117
+ background: rgba(39,39,42,0.92);
118
+ border-radius: 18px 18px 0 0;
119
  }
 
120
  .terminal-icon {
121
+ color: var(--primary);
122
+ font-size: 18px;
123
  }
 
124
  #terminal {
125
+ flex: 1;
126
+ padding: 16px;
127
+ background: var(--bg);
128
+ border-radius: 0 0 18px 18px;
129
  min-height: 180px;
130
+ min-width: 0;
131
  }
132
+ .connection-status {
133
+ position: absolute;
134
+ top: 18px;
135
+ right: 36px;
136
+ font-size: 12px;
137
+ color: var(--text-muted);
138
+ display: flex;
139
+ align-items: center;
140
+ gap: 6px;
141
+ }
142
+ .connection-dot {
143
+ width: 8px; height: 8px;
144
+ border-radius: 50%;
145
+ background: var(--danger);
146
+ transition: background-color 0.3s;
147
+ }
148
+ .connection-dot.connected { background: var(--primary); }
149
+ /* Sidebar */
150
  .sidebar {
151
+ flex: 1;
152
+ background: var(--bg-card);
153
  display: flex;
154
  flex-direction: column;
155
+ border-left: 1px solid var(--border-light);
156
+ min-width: 0;
157
+ max-width: 370px;
158
+ transition: transform 0.3s;
159
+ }
160
+ .sidebar-inner {
161
+ padding: 24px 18px 18px 18px;
162
+ overflow-y: auto;
163
+ height: 100%;
164
  }
 
165
  .sidebar-section {
166
+ background: rgba(24,24,27,0.7);
167
+ border-radius: 14px;
168
+ border: 1px solid var(--border-light);
169
+ margin-bottom: 18px;
170
+ padding: 18px 16px;
171
+ box-shadow: 0 2px 8px rgba(0,0,0,0.08);
 
 
 
 
172
  }
 
173
  .section-title {
174
  font-size: 13px;
175
+ font-weight: 700;
176
+ color: var(--primary);
177
+ margin-bottom: 10px;
 
178
  letter-spacing: 0.5px;
 
 
 
179
  display: flex;
180
+ align-items: center;
181
+ gap: 7px;
182
  }
 
183
  .info-item {
184
  display: flex;
185
  justify-content: space-between;
186
  align-items: center;
187
  font-size: 12px;
188
+ margin-bottom: 7px;
189
  }
190
+ .info-label { color: var(--text-muted); }
191
+ .info-value { color: var(--text); font-weight: 500; }
 
 
 
 
 
 
 
 
 
 
 
192
  .metric-bar {
193
  width: 100%;
194
+ height: 5px;
195
+ background: rgba(255,255,255,0.07);
196
  border-radius: 2px;
197
+ margin-bottom: 10px;
198
  overflow: hidden;
199
  }
 
200
  .metric-fill {
201
  height: 100%;
202
  border-radius: 2px;
203
+ transition: width 0.3s;
204
  }
205
+ .cpu-fill { background: linear-gradient(90deg, #10b981, #059669); }
206
+ .memory-fill { background: linear-gradient(90deg, #3b82f6, #2563eb); }
207
+ .disk-fill { background: linear-gradient(90deg, #f59e0b, #d97706); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  .mini-chart {
209
+ height: 48px;
210
+ background: rgba(255,255,255,0.03);
211
  border-radius: 8px;
212
+ margin-top: 10px;
 
 
213
  display: flex;
214
  align-items: end;
 
215
  gap: 1px;
216
+ padding: 3px;
217
  }
 
218
  .chart-bar {
219
  flex: 1;
220
+ background: rgba(16,185,129,0.3);
221
  border-radius: 1px 1px 0 0;
222
  min-height: 2px;
223
+ transition: height 0.3s;
224
  }
 
225
  .processes {
226
+ max-height: 120px;
227
  overflow-y: auto;
228
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
  .process-item {
230
  display: flex;
231
  justify-content: space-between;
232
  align-items: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
  font-size: 11px;
234
+ padding: 4px 0;
235
+ border-bottom: 1px solid rgba(255,255,255,0.05);
 
 
 
 
 
 
 
 
 
 
236
  }
237
+ .process-item:last-child { border-bottom: none; }
238
+ .process-name { color: var(--text); flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
239
+ .process-cpu { color: var(--primary); font-weight: 500; margin-left: 8px; }
 
 
240
  /* Mobile Styles */
241
+ @media (max-width: 900px) {
242
+ .main { flex-direction: column; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
  .sidebar {
 
244
  position: fixed;
245
+ left: 0; right: 0; bottom: 0;
246
+ top: unset;
247
+ max-width: 100vw;
248
+ height: 70vh;
 
 
 
249
  z-index: 1001;
250
  transform: translateY(100%);
251
+ box-shadow: 0 -10px 40px rgba(0,0,0,0.6);
252
+ border-radius: 18px 18px 0 0;
253
+ border-left: none;
254
+ border-top: 2px solid var(--border-light);
255
+ background: var(--bg-card);
256
+ transition: transform 0.3s;
257
  display: flex;
 
258
  }
259
+ .sidebar.show { transform: translateY(0); }
260
+ .sidebar-inner { padding: 18px 8px 8px 8px; }
261
+ .terminal-panel { border-right: none; }
262
+ .terminal-card { margin: 12px 6px 0 6px; border-radius: 14px; }
263
+ .terminal-header { border-radius: 14px 14px 0 0; padding: 10px 14px; }
264
+ #terminal { border-radius: 0 0 14px 14px; padding: 10px; }
265
+ .connection-status { right: 16px; top: 10px; font-size: 11px; }
266
+ }
267
+ @media (max-width: 600px) {
268
+ .header { height: 48px; padding: 0 10px; font-size: 14px; }
269
+ .logo { font-size: 14px; }
270
+ .terminal-card { margin: 8px 2px 0 2px; border-radius: 10px; }
271
+ .terminal-header { border-radius: 10px 10px 0 0; padding: 8px 8px; }
272
+ #terminal { border-radius: 0 0 10px 10px; padding: 6px; min-height: 120px; }
273
+ .sidebar { border-radius: 10px 10px 0 0; }
274
+ .sidebar-inner { padding: 8px 2px 2px 2px; }
275
+ }
276
+ /* Mobile toggle button */
277
+ .mobile-toggle {
278
+ display: none;
279
+ }
280
+ @media (max-width: 900px) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
  .mobile-toggle {
282
+ display: flex;
283
  position: fixed;
284
+ bottom: 18px;
285
+ right: 18px;
286
+ width: 54px;
287
+ height: 54px;
288
+ background: linear-gradient(135deg, var(--primary), var(--primary-dark));
289
  border: none;
290
  border-radius: 50%;
291
  color: white;
292
+ font-size: 26px;
293
  cursor: pointer;
294
+ z-index: 1100;
295
+ box-shadow: 0 8px 25px rgba(16,185,129,0.4);
 
 
296
  align-items: center;
297
  justify-content: center;
298
+ transition: all 0.3s;
299
  }
 
 
 
 
 
 
300
  .mobile-toggle.active {
301
+ background: linear-gradient(135deg, var(--danger), #dc2626);
302
  transform: rotate(45deg);
303
  }
304
+ }
305
+ .mobile-overlay {
306
+ display: none;
307
+ }
308
+ @media (max-width: 900px) {
309
  .mobile-overlay {
310
+ display: block;
311
  position: fixed;
312
+ top: 0; left: 0; right: 0; bottom: 0;
313
+ background: rgba(0,0,0,0.5);
 
 
 
314
  z-index: 1000;
315
  opacity: 0;
316
+ pointer-events: none;
317
+ transition: opacity 0.3s;
318
  }
 
319
  .mobile-overlay.show {
 
320
  opacity: 1;
321
+ pointer-events: all;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
322
  }
323
  }
324
  </style>
325
  </head>
326
  <body>
327
+ <div class="header">
328
+ <div class="logo">
329
+ <div class="logo-dot"></div>
330
+ Web Terminal
331
+ </div>
332
+ <div class="header-actions">
333
+ <button class="btn" id="clear-btn">Clear</button>
334
+ <a href="/logout" class="btn danger">Logout</a>
 
 
335
  </div>
336
+ </div>
337
+ <div class="main">
338
+ <div class="terminal-panel">
339
+ <div class="terminal-card" style="position:relative;">
340
  <div class="terminal-header">
341
+ <span class="terminal-icon">🖥️</span>
342
  Shell Session
343
  </div>
344
  <div id="terminal"></div>
 
348
  </div>
349
  </div>
350
  </div>
351
+ <div class="sidebar" id="sidebar">
352
+ <div class="sidebar-inner">
353
+ <div class="sidebar-section">
354
+ <div class="section-title">System Overview</div>
355
+ <div class="info-item"><span class="info-label">🖥️ Host</span><span class="info-value" id="hostname">-</span></div>
356
+ <div class="info-item"><span class="info-label">⏱️ Uptime</span><span class="info-value" id="uptime">-</span></div>
357
+ <div class="info-item"><span class="info-label">👥 Users</span><span class="info-value" id="users">-</span></div>
 
 
 
 
 
 
 
 
 
 
358
  </div>
359
+ <div class="sidebar-section">
360
+ <div class="section-title">Performance</div>
361
+ <div class="info-item"><span class="info-label">🧠 CPU</span><span class="info-value" id="cpu-usage">0%</span></div>
362
+ <div class="metric-bar"><div class="metric-fill cpu-fill" id="cpu-bar" style="width:0%"></div></div>
363
+ <div class="info-item"><span class="info-label">💾 Memory</span><span class="info-value" id="memory-usage">0%</span></div>
364
+ <div class="metric-bar"><div class="metric-fill memory-fill" id="memory-bar" style="width:0%"></div></div>
365
+ <div class="info-item"><span class="info-label">💿 Disk</span><span class="info-value" id="disk-usage">0%</span></div>
366
+ <div class="metric-bar"><div class="metric-fill disk-fill" id="disk-bar" style="width:0%"></div></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367
  <div class="mini-chart" id="cpu-chart"></div>
368
  </div>
369
+ <div class="sidebar-section">
370
+ <div class="section-title">Network & Storage</div>
371
+ <div class="info-item"><span class="info-label">🌐 Network RX</span><span class="info-value" id="network-rx">0 KB/s</span></div>
372
+ <div class="info-item"><span class="info-label">📡 Network TX</span><span class="info-value" id="network-tx">0 KB/s</span></div>
373
+ <div class="info-item"><span class="info-label">💾 Memory Used</span><span class="info-value" id="memory-details">0 MB / 0 MB</span></div>
374
+ <div class="info-item"><span class="info-label">💿 Disk Space</span><span class="info-value" id="disk-details">0 GB / 0 GB</span></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
375
  </div>
376
+ <div class="sidebar-section">
377
+ <div class="section-title">System Details</div>
378
+ <div class="info-item"><span class="info-label">🏗️ Platform</span><span class="info-value" id="platform">-</span></div>
379
+ <div class="info-item"><span class="info-label">🔧 Architecture</span><span class="info-value" id="arch">-</span></div>
380
+ <div class="info-item"><span class="info-label">🧠 CPU Model</span><span class="info-value" id="cpu-model">-</span></div>
381
+ <div class="info-item"><span class="info-label">⚡ CPU Cores</span><span class="info-value" id="cpu-cores">-</span></div>
382
+ <div class="info-item"><span class="info-label">📊 Load Avg</span><span class="info-value" id="load-avg">-</span></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
383
  </div>
384
+ <div class="sidebar-section">
385
+ <div class="section-title">Top Processes</div>
386
+ <div class="processes" id="processes">
387
+ <div class="process-item">
388
+ <div class="process-name">Loading...</div>
389
+ <div class="process-cpu">-</div>
390
+ </div>
391
  </div>
392
  </div>
393
  </div>
394
  </div>
395
  </div>
 
396
  <!-- Mobile elements -->
397
  <div class="mobile-overlay" id="mobile-overlay"></div>
398
  <button class="mobile-toggle" id="mobile-toggle">📊</button>
 
399
  <script src="/socket.io/socket.io.js"></script>
400
  <script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js"></script>
401
  <script>
402
+ // Terminal & socket logic (sama seperti sebelumnya)
403
+ let terminal, socket, isConnected = false, cpuHistory = [];
 
 
 
 
 
404
  function initializeTerminal() {
405
  terminal = new Terminal({
406
  cursorBlink: true,
 
410
  background: '#0c0c0c',
411
  foreground: '#e4e4e7',
412
  cursor: '#10b981',
413
+ selection: 'rgba(16,185,129,0.3)',
414
  black: '#27272a',
415
  red: '#ef4444',
416
  green: '#10b981',
 
421
  white: '#f4f4f5'
422
  }
423
  });
 
424
  terminal.open(document.getElementById('terminal'));
425
  terminal.focus();
 
 
426
  connectToServer();
 
 
427
  document.getElementById('clear-btn').addEventListener('click', clearTerminal);
 
 
428
  initializeMobileToggle();
429
  }
 
430
  function initializeMobileToggle() {
431
  const mobileToggle = document.getElementById('mobile-toggle');
432
+ const sidebar = document.getElementById('sidebar');
433
  const overlay = document.getElementById('mobile-overlay');
 
434
  if (mobileToggle && sidebar && overlay) {
435
  mobileToggle.addEventListener('click', () => {
436
  const isVisible = sidebar.classList.contains('show');
437
+ if (isVisible) hideMobileSidebar();
438
+ else showMobileSidebar();
 
 
 
 
 
 
 
 
439
  });
440
+ overlay.addEventListener('click', hideMobileSidebar);
 
441
  document.addEventListener('keydown', (e) => {
442
+ if (e.key === 'Escape' && sidebar.classList.contains('show')) hideMobileSidebar();
 
 
443
  });
444
  }
445
  }
 
446
  function showMobileSidebar() {
447
+ const sidebar = document.getElementById('sidebar');
448
  const overlay = document.getElementById('mobile-overlay');
449
  const toggle = document.getElementById('mobile-toggle');
 
450
  sidebar.classList.add('show');
451
  overlay.classList.add('show');
452
  toggle.classList.add('active');
453
  toggle.innerHTML = '✕';
 
 
454
  document.body.style.overflow = 'hidden';
455
  }
 
456
  function hideMobileSidebar() {
457
+ const sidebar = document.getElementById('sidebar');
458
  const overlay = document.getElementById('mobile-overlay');
459
  const toggle = document.getElementById('mobile-toggle');
 
460
  sidebar.classList.remove('show');
461
  overlay.classList.remove('show');
462
  toggle.classList.remove('active');
463
  toggle.innerHTML = '📊';
 
 
464
  document.body.style.overflow = '';
465
  }
 
466
  function connectToServer() {
 
467
  socket = io();
 
468
  socket.on('connect', () => {
469
  isConnected = true;
470
  updateConnectionStatus(true);
 
471
  });
 
472
  socket.on('disconnect', () => {
473
  isConnected = false;
474
  updateConnectionStatus(false);
 
475
  terminal.writeln('\r\n\x1b[31m✗ Connection lost. Reconnecting...\x1b[0m');
476
  });
 
477
  socket.on('connect_error', (error) => {
 
478
  updateConnectionStatus(false);
479
  terminal.writeln('\r\n\x1b[31m✗ Connection failed\x1b[0m');
480
  });
481
+ socket.on('terminal_output', (data) => { terminal.write(data); });
482
+ socket.on('system_info', (data) => { updateSystemInfo(data); });
483
+ terminal.onData(data => { if (isConnected && socket) socket.emit('terminal_input', data); });
484
+ terminal.onResize(({ cols, rows }) => { if (isConnected && socket) socket.emit('terminal_resize', { cols, rows }); });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
485
  }
 
486
  function updateConnectionStatus(connected) {
487
  const dot = document.getElementById('connection-dot');
488
  const text = document.getElementById('connection-text');
 
489
  if (connected) {
490
  dot.classList.add('connected');
491
  text.textContent = 'Connected';
 
494
  text.textContent = 'Disconnected';
495
  }
496
  }
 
497
  function updateSystemInfo(data) {
498
  if (!data) return;
 
 
499
  document.getElementById('hostname').textContent = data.hostname || '-';
500
  document.getElementById('uptime').textContent = data.uptime || '-';
501
  document.getElementById('users').textContent = data.user_info ? '1' : '-';
 
 
502
  document.getElementById('cpu-usage').textContent = `${Math.round(data.cpu_usage || 0)}%`;
503
  document.getElementById('cpu-bar').style.width = `${data.cpu_usage || 0}%`;
 
504
  document.getElementById('memory-usage').textContent = `${Math.round(data.mem_usage || 0)}%`;
505
  document.getElementById('memory-bar').style.width = `${data.mem_usage || 0}%`;
 
506
  document.getElementById('disk-usage').textContent = `${Math.round(data.disk_usage || 0)}%`;
507
  document.getElementById('disk-bar').style.width = `${data.disk_usage || 0}%`;
 
 
508
  document.getElementById('memory-details').textContent = `${data.used_mem || 0} MB / ${data.total_mem || 0} MB`;
 
 
509
  const diskUsed = typeof data.disk_used === 'number' ? `${data.disk_used} GB` : data.disk_used || '0';
510
  const diskTotal = typeof data.disk_total === 'number' ? `${data.disk_total} GB` : data.disk_total || '0';
511
  document.getElementById('disk-details').textContent = `${diskUsed} / ${diskTotal}`;
 
 
512
  document.getElementById('network-rx').textContent = formatBytes(data.network_rx || 0) + '/s';
513
  document.getElementById('network-tx').textContent = formatBytes(data.network_tx || 0) + '/s';
 
 
514
  document.getElementById('platform').textContent = data.platform || '-';
515
  document.getElementById('arch').textContent = data.arch || '-';
516
  document.getElementById('cpu-model').textContent = truncateText(data.cpu_model || '-', 20);
517
  document.getElementById('cpu-cores').textContent = data.cpu_cores || '-';
 
 
518
  if (data.loadavg && Array.isArray(data.loadavg)) {
519
  document.getElementById('load-avg').textContent = data.loadavg.map(l => l.toFixed(2)).join(', ');
520
  } else {
521
  document.getElementById('load-avg').textContent = '-';
522
  }
 
 
523
  if (data.cpu_usage !== undefined) {
524
  cpuHistory.push(data.cpu_usage);
525
+ if (cpuHistory.length > 20) cpuHistory.shift();
 
 
526
  updateCpuChart();
527
  }
528
+ if (data.processes && Array.isArray(data.processes)) updateProcessList(data.processes);
 
 
 
 
529
  }
 
530
  function formatBytes(bytes) {
531
  if (bytes === 0) return '0 B';
532
+ const k = 1024, sizes = ['B', 'KB', 'MB', 'GB'];
 
533
  const i = Math.floor(Math.log(Math.abs(bytes)) / Math.log(k));
534
  return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
535
  }
 
536
  function truncateText(text, maxLength) {
537
  if (text.length <= maxLength) return text;
538
  return text.substring(0, maxLength - 3) + '...';
539
  }
 
540
  function updateCpuChart() {
541
  const chartContainer = document.getElementById('cpu-chart');
542
  chartContainer.innerHTML = '';
 
543
  cpuHistory.forEach(value => {
544
  const bar = document.createElement('div');
545
  bar.className = 'chart-bar';
 
547
  chartContainer.appendChild(bar);
548
  });
549
  }
 
550
  function updateProcessList(processes) {
551
  const container = document.getElementById('processes');
552
  if (!processes || !Array.isArray(processes) || processes.length === 0) {
 
558
  `;
559
  return;
560
  }
 
561
  container.innerHTML = processes.map(proc => `
562
  <div class="process-item">
563
  <div class="process-name">${proc.name || 'unknown'}</div>
 
565
  </div>
566
  `).join('');
567
  }
568
+ function clearTerminal() { if (terminal) terminal.clear(); }
 
 
 
 
 
 
 
569
  document.addEventListener('DOMContentLoaded', initializeTerminal);
570
+ // Responsive terminal resize
 
571
  let resizeTimeout;
572
  window.addEventListener('resize', () => {
573
  clearTimeout(resizeTimeout);
574
  resizeTimeout = setTimeout(() => {
575
  if (terminal && terminal.element && terminal.element.offsetParent !== null) {
576
  try {
 
577
  const container = document.getElementById('terminal');
578
  if (container) {
579
  const containerRect = container.getBoundingClientRect();
 
 
580
  if (containerRect.width > 0 && containerRect.height > 0) {
581
+ const cols = Math.floor(containerRect.width / 9);
582
+ const rows = Math.floor(containerRect.height / 17);
583
+ if (cols > 10 && rows > 5) {
584
+ terminal.resize(cols, rows);
585
+ if (isConnected && socket) socket.emit('terminal_resize', { cols, rows });
 
 
 
 
 
 
 
 
 
 
 
 
 
586
  }
587
  }
588
  }
589
  } catch (error) {
590
+ try { terminal.resize(80, 24); } catch {}
 
 
 
 
 
 
591
  }
592
  }
593
+ }, 150);
594
  });
 
 
595
  window.addEventListener('orientationchange', () => {
596
+ setTimeout(() => { window.dispatchEvent(new Event('resize')); }, 500);
 
 
597
  });
598
  </script>
599
  </body>