shell-mirror 1.5.79 → 1.5.81
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/public/app/dashboard.js +3 -2
- package/public/app/terminal.html +67 -155
- package/public/app/terminal.js +45 -122
package/package.json
CHANGED
package/public/app/dashboard.js
CHANGED
|
@@ -46,6 +46,7 @@ class ShellMirrorDashboard {
|
|
|
46
46
|
this.user = authStatus.user;
|
|
47
47
|
await this.loadDashboardData();
|
|
48
48
|
this.renderAuthenticatedDashboard();
|
|
49
|
+
this.updateLastRefreshTime();
|
|
49
50
|
this.enableHttpOnlyMode(); // Use HTTP-only mode (no persistent WebSocket)
|
|
50
51
|
this.startAutoRefresh(); // Start auto-refresh for authenticated users
|
|
51
52
|
} else {
|
|
@@ -99,7 +100,7 @@ class ShellMirrorDashboard {
|
|
|
99
100
|
this.lastRefresh = Date.now();
|
|
100
101
|
const refreshStatus = document.getElementById('refresh-status');
|
|
101
102
|
if (refreshStatus) {
|
|
102
|
-
refreshStatus.textContent = `
|
|
103
|
+
refreshStatus.textContent = `Agents updated: ${new Date(this.lastRefresh).toLocaleTimeString()}`;
|
|
103
104
|
}
|
|
104
105
|
}
|
|
105
106
|
|
|
@@ -400,7 +401,7 @@ class ShellMirrorDashboard {
|
|
|
400
401
|
const data = await response.json();
|
|
401
402
|
|
|
402
403
|
if (data.success && data.data && data.data.authenticated) {
|
|
403
|
-
return { isAuthenticated: true, user: data.data };
|
|
404
|
+
return { isAuthenticated: true, user: data.data.user };
|
|
404
405
|
}
|
|
405
406
|
} catch (error) {
|
|
406
407
|
console.log('Auth check failed:', error);
|
package/public/app/terminal.html
CHANGED
|
@@ -110,154 +110,101 @@
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
/* Session Info Button */
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
border: 1px solid #444;
|
|
116
|
-
color: #ccc;
|
|
117
|
-
padding: 4px 12px;
|
|
118
|
-
border-radius: 4px;
|
|
119
|
-
cursor: pointer;
|
|
113
|
+
/* Session Tab Bar */
|
|
114
|
+
.session-tab-bar {
|
|
120
115
|
display: flex;
|
|
116
|
+
gap: 4px;
|
|
117
|
+
padding: 8px;
|
|
118
|
+
background: #2a2a2a;
|
|
119
|
+
overflow-x: auto;
|
|
120
|
+
-webkit-overflow-scrolling: touch;
|
|
121
|
+
flex: 1;
|
|
121
122
|
align-items: center;
|
|
122
|
-
gap: 8px;
|
|
123
|
-
font-size: 0.9em;
|
|
124
|
-
transition: all 0.2s ease;
|
|
125
123
|
}
|
|
126
124
|
|
|
127
|
-
.session-
|
|
128
|
-
|
|
129
|
-
border-color: #667eea;
|
|
125
|
+
.session-tab-bar::-webkit-scrollbar {
|
|
126
|
+
height: 4px;
|
|
130
127
|
}
|
|
131
128
|
|
|
132
|
-
.session-
|
|
133
|
-
|
|
134
|
-
|
|
129
|
+
.session-tab-bar::-webkit-scrollbar-thumb {
|
|
130
|
+
background: #555;
|
|
131
|
+
border-radius: 2px;
|
|
135
132
|
}
|
|
136
133
|
|
|
137
|
-
.session-
|
|
134
|
+
.session-tab {
|
|
135
|
+
display: flex;
|
|
136
|
+
align-items: center;
|
|
137
|
+
gap: 6px;
|
|
138
|
+
padding: 8px 12px;
|
|
139
|
+
background: transparent;
|
|
140
|
+
border: none;
|
|
141
|
+
border-bottom: 3px solid transparent;
|
|
138
142
|
color: #999;
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
font-size: 0.7em;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/* Session Info Dropdown */
|
|
148
|
-
.session-info-dropdown {
|
|
149
|
-
display: none;
|
|
150
|
-
position: absolute;
|
|
151
|
-
top: 100%;
|
|
152
|
-
left: 0;
|
|
153
|
-
margin-top: 8px;
|
|
154
|
-
background: #2a2a2a;
|
|
155
|
-
border: 1px solid #555;
|
|
156
|
-
border-radius: 4px;
|
|
157
|
-
min-width: 280px;
|
|
158
|
-
box-shadow: 0 4px 12px rgba(0,0,0,0.5);
|
|
159
|
-
z-index: 1000;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
.session-info-dropdown.show {
|
|
163
|
-
display: block;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
.dropdown-section {
|
|
167
|
-
padding: 12px 16px;
|
|
143
|
+
cursor: pointer;
|
|
144
|
+
min-width: 120px;
|
|
145
|
+
white-space: nowrap;
|
|
146
|
+
transition: all 0.2s ease;
|
|
147
|
+
flex-shrink: 0;
|
|
168
148
|
}
|
|
169
149
|
|
|
170
|
-
.
|
|
171
|
-
|
|
172
|
-
|
|
150
|
+
.session-tab:hover {
|
|
151
|
+
background: #3a3a3a;
|
|
152
|
+
color: #ccc;
|
|
173
153
|
}
|
|
174
154
|
|
|
175
|
-
.session-
|
|
176
|
-
font-weight: bold;
|
|
155
|
+
.session-tab.active {
|
|
177
156
|
color: #fff;
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
.session-detail-id {
|
|
182
|
-
color: #999;
|
|
183
|
-
font-size: 0.85em;
|
|
184
|
-
font-family: monospace;
|
|
157
|
+
font-weight: 600;
|
|
158
|
+
border-bottom-color: #667eea;
|
|
159
|
+
background: #1a1a1a;
|
|
185
160
|
}
|
|
186
161
|
|
|
187
|
-
.
|
|
188
|
-
|
|
189
|
-
|
|
162
|
+
.session-tab-status {
|
|
163
|
+
width: 8px;
|
|
164
|
+
height: 8px;
|
|
165
|
+
border-radius: 50%;
|
|
166
|
+
flex-shrink: 0;
|
|
190
167
|
}
|
|
191
168
|
|
|
192
|
-
.
|
|
193
|
-
|
|
194
|
-
|
|
169
|
+
.session-tab-status.connected {
|
|
170
|
+
background: #44ff44;
|
|
171
|
+
box-shadow: 0 0 6px rgba(68, 255, 68, 0.5);
|
|
195
172
|
}
|
|
196
173
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
display: inline-block;
|
|
174
|
+
.session-tab-status.connecting {
|
|
175
|
+
background: #ffaa44;
|
|
176
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
201
177
|
}
|
|
202
178
|
|
|
203
|
-
.session-
|
|
204
|
-
background:
|
|
205
|
-
color: #ccc;
|
|
206
|
-
border: 1px solid #444;
|
|
207
|
-
padding: 4px 12px;
|
|
208
|
-
border-radius: 4px;
|
|
209
|
-
cursor: pointer;
|
|
210
|
-
font-size: 0.9em;
|
|
211
|
-
display: flex;
|
|
212
|
-
align-items: center;
|
|
213
|
-
gap: 4px;
|
|
214
|
-
transition: all 0.2s ease;
|
|
179
|
+
.session-tab-status.disconnected {
|
|
180
|
+
background: #ff4444;
|
|
215
181
|
}
|
|
216
182
|
|
|
217
|
-
.session-
|
|
218
|
-
|
|
219
|
-
|
|
183
|
+
.session-tab-name {
|
|
184
|
+
overflow: hidden;
|
|
185
|
+
text-overflow: ellipsis;
|
|
220
186
|
}
|
|
221
187
|
|
|
222
|
-
.session-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
background: #2a2a2a;
|
|
226
|
-
border: 1px solid #555;
|
|
188
|
+
.session-tab-new {
|
|
189
|
+
padding: 6px 12px;
|
|
190
|
+
background: #667eea;
|
|
227
191
|
border-radius: 4px;
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
top: 100%;
|
|
231
|
-
margin-top: 8px;
|
|
232
|
-
min-width: 200px;
|
|
233
|
-
z-index: 1000;
|
|
234
|
-
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
.session-dropdown-content.show {
|
|
238
|
-
display: block;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
.session-dropdown-item {
|
|
242
|
-
padding: 8px 12px;
|
|
192
|
+
color: white;
|
|
193
|
+
border: none;
|
|
243
194
|
cursor: pointer;
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
.session-dropdown-item:last-child {
|
|
251
|
-
border-bottom: none;
|
|
195
|
+
margin-left: 4px;
|
|
196
|
+
font-weight: 500;
|
|
197
|
+
transition: all 0.2s ease;
|
|
198
|
+
flex-shrink: 0;
|
|
252
199
|
}
|
|
253
200
|
|
|
254
|
-
.session-
|
|
255
|
-
background: #
|
|
201
|
+
.session-tab-new:hover {
|
|
202
|
+
background: #5568d3;
|
|
256
203
|
}
|
|
257
204
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
205
|
+
@keyframes pulse {
|
|
206
|
+
0%, 100% { opacity: 1; }
|
|
207
|
+
50% { opacity: 0.5; }
|
|
261
208
|
}
|
|
262
209
|
|
|
263
210
|
/* Header Buttons */
|
|
@@ -434,47 +381,12 @@
|
|
|
434
381
|
</div>
|
|
435
382
|
<div id="terminal-container">
|
|
436
383
|
<div class="session-header" id="session-header" style="display: none;">
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
<span class="session-name" id="session-name">Terminal Session</span>
|
|
441
|
-
<span class="session-id" id="session-id"></span>
|
|
442
|
-
<span class="dropdown-arrow">▼</span>
|
|
443
|
-
</button>
|
|
444
|
-
|
|
445
|
-
<!-- Session Info Dropdown -->
|
|
446
|
-
<div class="session-info-dropdown" id="session-info-dropdown">
|
|
447
|
-
<div class="dropdown-section">
|
|
448
|
-
<div class="session-detail-name" id="detail-name">Session 1</div>
|
|
449
|
-
<div class="session-detail-id" id="detail-id">ses_1764...</div>
|
|
450
|
-
</div>
|
|
451
|
-
<div class="dropdown-divider"></div>
|
|
452
|
-
<div class="dropdown-section">
|
|
453
|
-
<div class="connection-detail" id="connection-detail">
|
|
454
|
-
🟢 Connected via WebRTC
|
|
455
|
-
</div>
|
|
456
|
-
</div>
|
|
457
|
-
<div class="dropdown-divider"></div>
|
|
458
|
-
<div class="dropdown-section version-info">
|
|
459
|
-
<div id="version-info-dropdown">v1.5.76 • Built Nov 26, 2025</div>
|
|
460
|
-
</div>
|
|
461
|
-
</div>
|
|
462
|
-
</div>
|
|
463
|
-
|
|
464
|
-
<div class="header-center">
|
|
465
|
-
<div class="session-dropdown">
|
|
466
|
-
<button class="session-dropdown-btn" id="session-dropdown-btn">
|
|
467
|
-
<span>Sessions</span>
|
|
468
|
-
<span>▼</span>
|
|
469
|
-
</button>
|
|
470
|
-
<div class="session-dropdown-content" id="session-dropdown-content">
|
|
471
|
-
<div class="session-dropdown-item" onclick="createNewSession()">
|
|
472
|
-
<span>+ New Session</span>
|
|
473
|
-
</div>
|
|
474
|
-
</div>
|
|
475
|
-
</div>
|
|
384
|
+
<!-- Session Tab Bar -->
|
|
385
|
+
<div class="session-tab-bar" id="session-tab-bar">
|
|
386
|
+
<!-- Tabs will be rendered here by JavaScript -->
|
|
476
387
|
</div>
|
|
477
388
|
|
|
389
|
+
<!-- Header Right Section -->
|
|
478
390
|
<div class="header-right">
|
|
479
391
|
<button class="header-btn help-btn" onclick="showHelpModal()">
|
|
480
392
|
<span>📖</span>
|
package/public/app/terminal.js
CHANGED
|
@@ -170,8 +170,10 @@ function updateConnectionStatus(status) {
|
|
|
170
170
|
break;
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
// Update
|
|
174
|
-
|
|
173
|
+
// Update tab status indicators
|
|
174
|
+
if (currentSession) {
|
|
175
|
+
renderTabs();
|
|
176
|
+
}
|
|
175
177
|
}
|
|
176
178
|
|
|
177
179
|
// Cleanup timer for chunk assembler
|
|
@@ -246,42 +248,6 @@ async function loadVersionInfo() {
|
|
|
246
248
|
}
|
|
247
249
|
|
|
248
250
|
// Update connection detail in dropdown
|
|
249
|
-
function updateConnectionDetail(status, method = 'WebRTC') {
|
|
250
|
-
const detail = document.getElementById('connection-detail');
|
|
251
|
-
if (!detail) return;
|
|
252
|
-
|
|
253
|
-
const statusIcons = {
|
|
254
|
-
connected: '🟢',
|
|
255
|
-
connecting: '🟡',
|
|
256
|
-
disconnected: '🔴'
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
const statusTexts = {
|
|
260
|
-
connected: 'Connected',
|
|
261
|
-
connecting: 'Connecting',
|
|
262
|
-
disconnected: 'Disconnected'
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
detail.textContent = `${statusIcons[status] || '⚪'} ${statusTexts[status] || 'Unknown'} via ${method}`;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Update session details in dropdown
|
|
269
|
-
function updateSessionDetails() {
|
|
270
|
-
if (currentSession) {
|
|
271
|
-
const detailName = document.getElementById('detail-name');
|
|
272
|
-
const detailId = document.getElementById('detail-id');
|
|
273
|
-
|
|
274
|
-
if (detailName) {
|
|
275
|
-
detailName.textContent = currentSession.name || 'Terminal Session';
|
|
276
|
-
}
|
|
277
|
-
if (detailId) {
|
|
278
|
-
detailId.textContent = `ID: ${currentSession.id}`;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
251
|
function startConnection() {
|
|
286
252
|
updateConnectionStatus('connecting');
|
|
287
253
|
connectContainer.style.display = 'none';
|
|
@@ -957,50 +923,54 @@ function sendMessage(message) {
|
|
|
957
923
|
// Session Management Functions
|
|
958
924
|
function updateSessionDisplay() {
|
|
959
925
|
const sessionHeader = document.getElementById('session-header');
|
|
960
|
-
const sessionName = document.getElementById('session-name');
|
|
961
|
-
const sessionId = document.getElementById('session-id');
|
|
962
926
|
|
|
963
927
|
if (currentSession) {
|
|
964
928
|
sessionHeader.style.display = 'flex';
|
|
965
|
-
sessionName.textContent = currentSession.name;
|
|
966
|
-
sessionId.textContent = `(${currentSession.id.substring(0, 8)}...)`;
|
|
967
|
-
|
|
968
|
-
// Update available sessions dropdown
|
|
969
|
-
updateSessionDropdown();
|
|
970
929
|
|
|
971
|
-
// Update
|
|
972
|
-
|
|
930
|
+
// Update tabs
|
|
931
|
+
renderTabs();
|
|
973
932
|
|
|
974
933
|
console.log('[CLIENT] 📋 Session display updated:', currentSession);
|
|
975
934
|
}
|
|
976
935
|
}
|
|
977
936
|
|
|
978
|
-
function
|
|
979
|
-
const
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
// Add available sessions
|
|
937
|
+
function renderTabs() {
|
|
938
|
+
const tabBar = document.getElementById('session-tab-bar');
|
|
939
|
+
if (!tabBar) return;
|
|
940
|
+
|
|
941
|
+
// Get connection status
|
|
942
|
+
const connectionStatus = getConnectionStatus();
|
|
943
|
+
|
|
944
|
+
// Build tabs HTML
|
|
945
|
+
let tabsHTML = '';
|
|
946
|
+
|
|
989
947
|
if (availableSessions && availableSessions.length > 0) {
|
|
990
|
-
availableSessions.
|
|
991
|
-
const
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
<small>${formatLastActivity(session.lastActivity)}</small>
|
|
948
|
+
tabsHTML = availableSessions.map(session => {
|
|
949
|
+
const isActive = currentSession && session.id === currentSession.id;
|
|
950
|
+
return `
|
|
951
|
+
<button class="session-tab ${isActive ? 'active' : ''}"
|
|
952
|
+
onclick="switchToSession('${session.id}')"
|
|
953
|
+
${isActive ? 'disabled' : ''}>
|
|
954
|
+
<span class="session-tab-status ${connectionStatus}"></span>
|
|
955
|
+
<span class="session-tab-name">${session.name}</span>
|
|
956
|
+
</button>
|
|
1000
957
|
`;
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
958
|
+
}).join('');
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// Add new session button
|
|
962
|
+
tabsHTML += '<button class="session-tab-new" onclick="createNewSession()">+</button>';
|
|
963
|
+
|
|
964
|
+
tabBar.innerHTML = tabsHTML;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
function getConnectionStatus() {
|
|
968
|
+
if (dataChannel && dataChannel.readyState === 'open') {
|
|
969
|
+
return 'connected';
|
|
970
|
+
} else if (dataChannel && dataChannel.readyState === 'connecting') {
|
|
971
|
+
return 'connecting';
|
|
972
|
+
} else {
|
|
973
|
+
return 'disconnected';
|
|
1004
974
|
}
|
|
1005
975
|
}
|
|
1006
976
|
|
|
@@ -1022,15 +992,12 @@ function switchToSession(sessionId) {
|
|
|
1022
992
|
console.error('[CLIENT] ❌ Cannot switch session - data channel not open');
|
|
1023
993
|
return;
|
|
1024
994
|
}
|
|
1025
|
-
|
|
995
|
+
|
|
1026
996
|
console.log('[CLIENT] 🔄 Switching to session:', sessionId);
|
|
1027
|
-
dataChannel.send(JSON.stringify({
|
|
1028
|
-
type: 'session-switch',
|
|
1029
|
-
sessionId: sessionId
|
|
997
|
+
dataChannel.send(JSON.stringify({
|
|
998
|
+
type: 'session-switch',
|
|
999
|
+
sessionId: sessionId
|
|
1030
1000
|
}));
|
|
1031
|
-
|
|
1032
|
-
// Hide dropdown
|
|
1033
|
-
document.getElementById('session-dropdown-content').classList.remove('show');
|
|
1034
1001
|
}
|
|
1035
1002
|
|
|
1036
1003
|
function createNewSession() {
|
|
@@ -1040,50 +1007,6 @@ function createNewSession() {
|
|
|
1040
1007
|
window.location.href = url.toString();
|
|
1041
1008
|
}
|
|
1042
1009
|
|
|
1043
|
-
// Setup dropdown toggles
|
|
1044
|
-
document.addEventListener('DOMContentLoaded', () => {
|
|
1045
|
-
// Session info dropdown
|
|
1046
|
-
const sessionInfoBtn = document.getElementById('session-info-btn');
|
|
1047
|
-
const sessionInfoDropdown = document.getElementById('session-info-dropdown');
|
|
1048
|
-
|
|
1049
|
-
// Sessions menu dropdown
|
|
1050
|
-
const dropdownBtn = document.getElementById('session-dropdown-btn');
|
|
1051
|
-
const dropdownContent = document.getElementById('session-dropdown-content');
|
|
1052
|
-
|
|
1053
|
-
// Session info dropdown toggle
|
|
1054
|
-
if (sessionInfoBtn && sessionInfoDropdown) {
|
|
1055
|
-
sessionInfoBtn.onclick = (e) => {
|
|
1056
|
-
e.stopPropagation();
|
|
1057
|
-
sessionInfoDropdown.classList.toggle('show');
|
|
1058
|
-
// Close sessions dropdown if open
|
|
1059
|
-
if (dropdownContent) {
|
|
1060
|
-
dropdownContent.classList.remove('show');
|
|
1061
|
-
}
|
|
1062
|
-
};
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
// Sessions menu dropdown toggle
|
|
1066
|
-
if (dropdownBtn && dropdownContent) {
|
|
1067
|
-
dropdownBtn.onclick = (e) => {
|
|
1068
|
-
e.stopPropagation();
|
|
1069
|
-
dropdownContent.classList.toggle('show');
|
|
1070
|
-
// Close session info dropdown if open
|
|
1071
|
-
if (sessionInfoDropdown) {
|
|
1072
|
-
sessionInfoDropdown.classList.remove('show');
|
|
1073
|
-
}
|
|
1074
|
-
};
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
// Close both dropdowns when clicking outside
|
|
1078
|
-
document.addEventListener('click', () => {
|
|
1079
|
-
if (dropdownContent) {
|
|
1080
|
-
dropdownContent.classList.remove('show');
|
|
1081
|
-
}
|
|
1082
|
-
if (sessionInfoDropdown) {
|
|
1083
|
-
sessionInfoDropdown.classList.remove('show');
|
|
1084
|
-
}
|
|
1085
|
-
});
|
|
1086
|
-
});
|
|
1087
1010
|
|
|
1088
1011
|
// Handle session-related data channel messages
|
|
1089
1012
|
function handleSessionMessage(message) {
|