shell-mirror 1.5.113 → 1.5.115
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.css +12 -7
- package/public/app/dashboard.js +76 -23
- package/public/app/terminal.js +14 -5
package/package.json
CHANGED
package/public/app/dashboard.css
CHANGED
|
@@ -239,7 +239,7 @@ body {
|
|
|
239
239
|
.agent-name-row {
|
|
240
240
|
display: flex;
|
|
241
241
|
align-items: center;
|
|
242
|
-
|
|
242
|
+
justify-content: space-between;
|
|
243
243
|
margin-bottom: 4px;
|
|
244
244
|
}
|
|
245
245
|
|
|
@@ -247,11 +247,15 @@ body {
|
|
|
247
247
|
font-weight: 600;
|
|
248
248
|
font-size: 1rem;
|
|
249
249
|
color: var(--text-primary);
|
|
250
|
+
flex: 1;
|
|
251
|
+
min-width: 0;
|
|
250
252
|
}
|
|
251
253
|
|
|
252
254
|
/* Agent Menu Dropdown */
|
|
253
255
|
.agent-menu {
|
|
254
256
|
position: relative;
|
|
257
|
+
flex-shrink: 0;
|
|
258
|
+
margin-left: 8px;
|
|
255
259
|
}
|
|
256
260
|
|
|
257
261
|
.btn-agent-menu {
|
|
@@ -263,6 +267,7 @@ body {
|
|
|
263
267
|
padding: 4px 8px;
|
|
264
268
|
border-radius: 4px;
|
|
265
269
|
transition: all 0.15s ease;
|
|
270
|
+
line-height: 1;
|
|
266
271
|
}
|
|
267
272
|
|
|
268
273
|
.btn-agent-menu:hover {
|
|
@@ -273,14 +278,14 @@ body {
|
|
|
273
278
|
.agent-menu-dropdown {
|
|
274
279
|
display: none;
|
|
275
280
|
position: absolute;
|
|
276
|
-
right:
|
|
281
|
+
right: -8px;
|
|
277
282
|
top: calc(100% + 4px);
|
|
278
|
-
background: var(--bg-
|
|
279
|
-
border: 1px solid var(--border);
|
|
280
|
-
border-radius:
|
|
283
|
+
background: var(--bg-primary);
|
|
284
|
+
border: 1px solid var(--border-light);
|
|
285
|
+
border-radius: 8px;
|
|
281
286
|
min-width: 160px;
|
|
282
|
-
z-index:
|
|
283
|
-
box-shadow: 0
|
|
287
|
+
z-index: 1000;
|
|
288
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
|
|
284
289
|
}
|
|
285
290
|
|
|
286
291
|
.agent-menu-dropdown.show {
|
package/public/app/dashboard.js
CHANGED
|
@@ -25,6 +25,7 @@ class ShellMirrorDashboard {
|
|
|
25
25
|
this.isRefreshing = false;
|
|
26
26
|
this.connectionStatusDebounce = null;
|
|
27
27
|
this.currentConnectionStatus = null;
|
|
28
|
+
this.sessionBroadcast = null; // For cross-tab session sync
|
|
28
29
|
this.init();
|
|
29
30
|
}
|
|
30
31
|
|
|
@@ -60,6 +61,7 @@ class ShellMirrorDashboard {
|
|
|
60
61
|
this.updateLastRefreshTime();
|
|
61
62
|
this.enableHttpOnlyMode(); // Use HTTP-only mode (no persistent WebSocket)
|
|
62
63
|
this.startAutoRefresh(); // Start auto-refresh for authenticated users
|
|
64
|
+
this.setupSessionSync(); // Listen for real-time session updates from terminal tabs
|
|
63
65
|
} else {
|
|
64
66
|
this.renderUnauthenticatedDashboard();
|
|
65
67
|
}
|
|
@@ -76,13 +78,50 @@ class ShellMirrorDashboard {
|
|
|
76
78
|
if (this.refreshInterval) {
|
|
77
79
|
clearInterval(this.refreshInterval);
|
|
78
80
|
}
|
|
79
|
-
|
|
80
|
-
// Refresh agent data every
|
|
81
|
+
|
|
82
|
+
// Refresh agent data every 10 seconds (sessions sync instantly via BroadcastChannel)
|
|
81
83
|
this.refreshInterval = setInterval(async () => {
|
|
82
84
|
if (this.isAuthenticated && !this.isRefreshing) {
|
|
83
85
|
await this.refreshDashboardData();
|
|
84
86
|
}
|
|
85
|
-
},
|
|
87
|
+
}, 10000);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
setupSessionSync() {
|
|
91
|
+
// Use BroadcastChannel for instant cross-tab session sync
|
|
92
|
+
try {
|
|
93
|
+
this.sessionBroadcast = new BroadcastChannel('shell-mirror-sessions');
|
|
94
|
+
this.sessionBroadcast.onmessage = (event) => {
|
|
95
|
+
console.log('[DASHBOARD] 📡 Received session update from terminal:', event.data);
|
|
96
|
+
if (event.data.type === 'session-update') {
|
|
97
|
+
this.handleSessionUpdate(event.data);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
console.log('[DASHBOARD] 📡 Session sync channel established');
|
|
101
|
+
} catch (e) {
|
|
102
|
+
console.log('[DASHBOARD] BroadcastChannel not supported, using localStorage polling');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Also listen for localStorage changes (fallback for same-origin tabs)
|
|
106
|
+
window.addEventListener('storage', (event) => {
|
|
107
|
+
if (event.key === 'shell-mirror-sessions') {
|
|
108
|
+
console.log('[DASHBOARD] 💾 localStorage session update detected');
|
|
109
|
+
this.loadSessionsFromLocalStorage();
|
|
110
|
+
this.updateAgentsDisplay();
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Initial load from localStorage
|
|
115
|
+
this.loadSessionsFromLocalStorage();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
handleSessionUpdate(data) {
|
|
119
|
+
const { agentId, sessions } = data;
|
|
120
|
+
if (agentId && sessions) {
|
|
121
|
+
this.agentSessions[agentId] = sessions;
|
|
122
|
+
console.log('[DASHBOARD] ✅ Sessions updated for agent:', agentId, sessions);
|
|
123
|
+
this.updateAgentsDisplay();
|
|
124
|
+
}
|
|
86
125
|
}
|
|
87
126
|
|
|
88
127
|
async refreshDashboardData() {
|
|
@@ -434,15 +473,22 @@ class ShellMirrorDashboard {
|
|
|
434
473
|
this.agents = agentsData.data.agents;
|
|
435
474
|
|
|
436
475
|
// Populate agentSessions from API response (sessions are sent via agent heartbeat)
|
|
437
|
-
|
|
476
|
+
// But DON'T reset - merge with localStorage sessions for instant updates
|
|
438
477
|
this.agents.forEach(agent => {
|
|
439
478
|
if (agent.sessions && agent.sessions.length > 0) {
|
|
440
|
-
|
|
479
|
+
// Merge API sessions with any localStorage sessions
|
|
480
|
+
const localSessions = this.agentSessions[agent.agentId] || [];
|
|
481
|
+
const apiSessionIds = new Set(agent.sessions.map(s => s.id));
|
|
482
|
+
|
|
483
|
+
// Keep local sessions that aren't in API yet (just created)
|
|
484
|
+
const newLocalSessions = localSessions.filter(s => !apiSessionIds.has(s.id));
|
|
485
|
+
|
|
486
|
+
this.agentSessions[agent.agentId] = [...agent.sessions, ...newLocalSessions];
|
|
441
487
|
}
|
|
442
488
|
});
|
|
443
489
|
|
|
444
|
-
//
|
|
445
|
-
|
|
490
|
+
// Load sessions from localStorage (created by terminal tabs)
|
|
491
|
+
this.loadSessionsFromLocalStorage();
|
|
446
492
|
} else {
|
|
447
493
|
this.agents = [];
|
|
448
494
|
this.agentSessions = {};
|
|
@@ -1108,7 +1154,7 @@ class ShellMirrorDashboard {
|
|
|
1108
1154
|
</div>
|
|
1109
1155
|
<div class="modal-body">
|
|
1110
1156
|
<p style="margin-bottom: 12px; color: var(--text-secondary);">
|
|
1111
|
-
Are you sure you want to shut down
|
|
1157
|
+
Are you sure you want to shut down<br><strong style="color: var(--text-primary); word-break: break-all;">${agentName}</strong>?
|
|
1112
1158
|
</p>
|
|
1113
1159
|
<p style="font-size: 0.85rem; color: var(--text-muted);">
|
|
1114
1160
|
This will unregister the agent from the dashboard. If the agent is still running, it will re-register on next heartbeat.
|
|
@@ -1197,34 +1243,41 @@ class ShellMirrorDashboard {
|
|
|
1197
1243
|
return `${days}d ago`;
|
|
1198
1244
|
}
|
|
1199
1245
|
|
|
1200
|
-
// Session storage management
|
|
1201
|
-
|
|
1246
|
+
// Session storage management - merge localStorage sessions with API sessions
|
|
1247
|
+
loadSessionsFromLocalStorage() {
|
|
1202
1248
|
try {
|
|
1203
1249
|
const storedSessions = localStorage.getItem('shell-mirror-sessions');
|
|
1204
|
-
|
|
1250
|
+
|
|
1205
1251
|
if (storedSessions) {
|
|
1206
1252
|
const sessionData = JSON.parse(storedSessions);
|
|
1207
|
-
|
|
1208
|
-
// Filter out old sessions (older than
|
|
1253
|
+
|
|
1254
|
+
// Filter out old sessions (older than 1 hour for inactive ones)
|
|
1209
1255
|
const now = Date.now();
|
|
1210
|
-
const maxAge =
|
|
1211
|
-
|
|
1256
|
+
const maxAge = 60 * 60 * 1000; // 1 hour
|
|
1257
|
+
|
|
1212
1258
|
Object.keys(sessionData).forEach(agentId => {
|
|
1213
|
-
const
|
|
1214
|
-
|
|
1215
|
-
|
|
1259
|
+
const localSessions = sessionData[agentId] || [];
|
|
1260
|
+
|
|
1261
|
+
// Only keep recent active sessions
|
|
1262
|
+
const validSessions = localSessions.filter(session => {
|
|
1216
1263
|
const age = now - session.lastActivity;
|
|
1217
|
-
return age < maxAge;
|
|
1264
|
+
return session.status === 'active' && age < maxAge;
|
|
1218
1265
|
});
|
|
1219
|
-
|
|
1266
|
+
|
|
1220
1267
|
if (validSessions.length > 0) {
|
|
1221
|
-
|
|
1268
|
+
// Merge with existing API sessions (avoid duplicates)
|
|
1269
|
+
const existingSessions = this.agentSessions[agentId] || [];
|
|
1270
|
+
const existingIds = new Set(existingSessions.map(s => s.id));
|
|
1271
|
+
|
|
1272
|
+
const newSessions = validSessions.filter(s => !existingIds.has(s.id));
|
|
1273
|
+
this.agentSessions[agentId] = [...existingSessions, ...newSessions];
|
|
1274
|
+
|
|
1275
|
+
console.log(`[DASHBOARD] 💾 Merged ${newSessions.length} localStorage sessions for ${agentId}`);
|
|
1222
1276
|
}
|
|
1223
1277
|
});
|
|
1224
|
-
} else {
|
|
1225
1278
|
}
|
|
1226
1279
|
} catch (error) {
|
|
1227
|
-
console.error('[DASHBOARD] ❌ Error loading sessions from
|
|
1280
|
+
console.error('[DASHBOARD] ❌ Error loading sessions from localStorage:', error);
|
|
1228
1281
|
}
|
|
1229
1282
|
}
|
|
1230
1283
|
|
package/public/app/terminal.js
CHANGED
|
@@ -1324,11 +1324,20 @@ function saveSessionToLocalStorage(agentId, sessionInfo) {
|
|
|
1324
1324
|
|
|
1325
1325
|
localStorage.setItem('shell-mirror-sessions', JSON.stringify(sessionData));
|
|
1326
1326
|
console.log('[CLIENT] 💾 Session saved to storage:', sessionToStore);
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1327
|
+
|
|
1328
|
+
// Broadcast to dashboard tabs for instant sync
|
|
1329
|
+
try {
|
|
1330
|
+
const broadcast = new BroadcastChannel('shell-mirror-sessions');
|
|
1331
|
+
broadcast.postMessage({
|
|
1332
|
+
type: 'session-update',
|
|
1333
|
+
agentId: agentId,
|
|
1334
|
+
sessions: sessionData[agentId]
|
|
1335
|
+
});
|
|
1336
|
+
broadcast.close();
|
|
1337
|
+
console.log('[CLIENT] 📡 Session update broadcasted to dashboard');
|
|
1338
|
+
} catch (e) {
|
|
1339
|
+
// BroadcastChannel not supported, localStorage event will handle it
|
|
1340
|
+
}
|
|
1332
1341
|
} catch (error) {
|
|
1333
1342
|
console.error('[CLIENT] ❌ Error saving session to storage:', error);
|
|
1334
1343
|
}
|