shell-mirror 1.5.112 → 1.5.114
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 +56 -13
- package/public/app/dashboard.js +79 -14
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,22 +247,65 @@ body {
|
|
|
247
247
|
font-weight: 600;
|
|
248
248
|
font-size: 1rem;
|
|
249
249
|
color: var(--text-primary);
|
|
250
|
+
flex: 1;
|
|
251
|
+
min-width: 0;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/* Agent Menu Dropdown */
|
|
255
|
+
.agent-menu {
|
|
256
|
+
position: relative;
|
|
257
|
+
flex-shrink: 0;
|
|
258
|
+
margin-left: 8px;
|
|
250
259
|
}
|
|
251
260
|
|
|
252
|
-
.btn-
|
|
261
|
+
.btn-agent-menu {
|
|
253
262
|
background: none;
|
|
254
263
|
border: none;
|
|
264
|
+
color: var(--text-muted);
|
|
265
|
+
font-size: 1.2rem;
|
|
255
266
|
cursor: pointer;
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
transition:
|
|
259
|
-
|
|
260
|
-
|
|
267
|
+
padding: 4px 8px;
|
|
268
|
+
border-radius: 4px;
|
|
269
|
+
transition: all 0.15s ease;
|
|
270
|
+
line-height: 1;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.btn-agent-menu:hover {
|
|
274
|
+
color: var(--text-primary);
|
|
275
|
+
background: var(--bg-hover);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.agent-menu-dropdown {
|
|
279
|
+
display: none;
|
|
280
|
+
position: absolute;
|
|
281
|
+
right: 0;
|
|
282
|
+
top: calc(100% + 2px);
|
|
283
|
+
background: var(--bg-secondary);
|
|
284
|
+
border: 1px solid var(--border);
|
|
285
|
+
border-radius: 6px;
|
|
286
|
+
min-width: 160px;
|
|
287
|
+
z-index: 100;
|
|
288
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.agent-menu-dropdown.show {
|
|
292
|
+
display: block;
|
|
261
293
|
}
|
|
262
294
|
|
|
263
|
-
.
|
|
264
|
-
|
|
295
|
+
.agent-menu-dropdown button {
|
|
296
|
+
width: 100%;
|
|
297
|
+
padding: 10px 14px;
|
|
298
|
+
background: none;
|
|
299
|
+
border: none;
|
|
265
300
|
color: var(--danger);
|
|
301
|
+
text-align: left;
|
|
302
|
+
cursor: pointer;
|
|
303
|
+
font-size: 0.85rem;
|
|
304
|
+
transition: background 0.15s ease;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.agent-menu-dropdown button:hover {
|
|
308
|
+
background: var(--bg-hover);
|
|
266
309
|
}
|
|
267
310
|
|
|
268
311
|
.agent-status {
|
|
@@ -275,7 +318,7 @@ body {
|
|
|
275
318
|
}
|
|
276
319
|
|
|
277
320
|
.agent-status.online {
|
|
278
|
-
color: var(--
|
|
321
|
+
color: var(--text-secondary);
|
|
279
322
|
background: none;
|
|
280
323
|
padding: 0;
|
|
281
324
|
}
|
|
@@ -289,7 +332,7 @@ body {
|
|
|
289
332
|
}
|
|
290
333
|
|
|
291
334
|
.agent-status.recent {
|
|
292
|
-
color: var(--
|
|
335
|
+
color: var(--text-secondary);
|
|
293
336
|
background: none;
|
|
294
337
|
padding: 0;
|
|
295
338
|
}
|
|
@@ -303,7 +346,7 @@ body {
|
|
|
303
346
|
}
|
|
304
347
|
|
|
305
348
|
.agent-status.offline {
|
|
306
|
-
color: var(--
|
|
349
|
+
color: var(--text-secondary);
|
|
307
350
|
background: none;
|
|
308
351
|
padding: 0;
|
|
309
352
|
}
|
|
@@ -571,7 +614,7 @@ body {
|
|
|
571
614
|
.command-box code {
|
|
572
615
|
font-family: 'Monaco', 'Menlo', monospace;
|
|
573
616
|
font-size: 0.85rem;
|
|
574
|
-
color: var(--
|
|
617
|
+
color: var(--text-primary);
|
|
575
618
|
flex: 1;
|
|
576
619
|
}
|
|
577
620
|
|
package/public/app/dashboard.js
CHANGED
|
@@ -76,13 +76,13 @@ class ShellMirrorDashboard {
|
|
|
76
76
|
if (this.refreshInterval) {
|
|
77
77
|
clearInterval(this.refreshInterval);
|
|
78
78
|
}
|
|
79
|
-
|
|
80
|
-
// Refresh agent data every
|
|
79
|
+
|
|
80
|
+
// Refresh agent data every 5 seconds for responsive session updates
|
|
81
81
|
this.refreshInterval = setInterval(async () => {
|
|
82
82
|
if (this.isAuthenticated && !this.isRefreshing) {
|
|
83
83
|
await this.refreshDashboardData();
|
|
84
84
|
}
|
|
85
|
-
},
|
|
85
|
+
}, 5000);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
async refreshDashboardData() {
|
|
@@ -586,9 +586,12 @@ class ShellMirrorDashboard {
|
|
|
586
586
|
<div class="agent-info">
|
|
587
587
|
<div class="agent-name-row">
|
|
588
588
|
<span class="agent-name">${agent.machineName || agent.agentId}</span>
|
|
589
|
-
<
|
|
590
|
-
|
|
591
|
-
|
|
589
|
+
<div class="agent-menu">
|
|
590
|
+
<button class="btn-agent-menu" onclick="event.stopPropagation(); dashboard.toggleAgentMenu('${agent.agentId}')">⋮</button>
|
|
591
|
+
<div class="agent-menu-dropdown" id="agent-menu-${agent.agentId}">
|
|
592
|
+
<button onclick="dashboard.showShutdownConfirm('${agent.agentId}')">Shut down agent</button>
|
|
593
|
+
</div>
|
|
594
|
+
</div>
|
|
592
595
|
</div>
|
|
593
596
|
<div class="agent-status ${agent.status}">
|
|
594
597
|
${statusText}${sessionCount > 0 ? ` · ${sessionCount} session${sessionCount !== 1 ? 's' : ''}` : ''}
|
|
@@ -1065,13 +1068,66 @@ class ShellMirrorDashboard {
|
|
|
1065
1068
|
return true;
|
|
1066
1069
|
}
|
|
1067
1070
|
|
|
1068
|
-
|
|
1071
|
+
toggleAgentMenu(agentId) {
|
|
1072
|
+
// Close all other menus first
|
|
1073
|
+
document.querySelectorAll('.agent-menu-dropdown.show').forEach(el => {
|
|
1074
|
+
el.classList.remove('show');
|
|
1075
|
+
});
|
|
1076
|
+
const menu = document.getElementById(`agent-menu-${agentId}`);
|
|
1077
|
+
if (menu) {
|
|
1078
|
+
menu.classList.toggle('show');
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
showShutdownConfirm(agentId) {
|
|
1083
|
+
// Close menu
|
|
1084
|
+
document.querySelectorAll('.agent-menu-dropdown.show').forEach(el => {
|
|
1085
|
+
el.classList.remove('show');
|
|
1086
|
+
});
|
|
1087
|
+
this.showShutdownModal(agentId);
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
showShutdownModal(agentId) {
|
|
1069
1091
|
const agent = this.agents.find(a => a.agentId === agentId);
|
|
1070
1092
|
const agentName = agent ? (agent.machineName || agentId) : agentId;
|
|
1071
1093
|
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1094
|
+
const modalOverlay = document.createElement('div');
|
|
1095
|
+
modalOverlay.className = 'modal-overlay';
|
|
1096
|
+
modalOverlay.id = 'shutdown-modal';
|
|
1097
|
+
modalOverlay.onclick = (e) => {
|
|
1098
|
+
if (e.target === modalOverlay) {
|
|
1099
|
+
document.body.removeChild(modalOverlay);
|
|
1100
|
+
}
|
|
1101
|
+
};
|
|
1102
|
+
|
|
1103
|
+
modalOverlay.innerHTML = `
|
|
1104
|
+
<div class="modal" onclick="event.stopPropagation()">
|
|
1105
|
+
<div class="modal-header">
|
|
1106
|
+
<h3>Shut down agent</h3>
|
|
1107
|
+
<button class="modal-close" onclick="document.getElementById('shutdown-modal').remove()">×</button>
|
|
1108
|
+
</div>
|
|
1109
|
+
<div class="modal-body">
|
|
1110
|
+
<p style="margin-bottom: 12px; color: var(--text-secondary);">
|
|
1111
|
+
Are you sure you want to shut down<br><strong style="color: var(--text-primary); word-break: break-all;">${agentName}</strong>?
|
|
1112
|
+
</p>
|
|
1113
|
+
<p style="font-size: 0.85rem; color: var(--text-muted);">
|
|
1114
|
+
This will unregister the agent from the dashboard. If the agent is still running, it will re-register on next heartbeat.
|
|
1115
|
+
</p>
|
|
1116
|
+
</div>
|
|
1117
|
+
<div class="modal-footer" style="display: flex; justify-content: flex-end; gap: 10px; padding: 16px; border-top: 1px solid var(--border);">
|
|
1118
|
+
<button onclick="document.getElementById('shutdown-modal').remove()" style="padding: 8px 16px; background: var(--bg-tertiary); color: var(--text-secondary); border: 1px solid var(--border); border-radius: 6px; cursor: pointer;">Cancel</button>
|
|
1119
|
+
<button onclick="dashboard.shutdownAgent('${agentId}')" style="padding: 8px 16px; background: var(--danger); color: white; border: none; border-radius: 6px; cursor: pointer;">Shut down</button>
|
|
1120
|
+
</div>
|
|
1121
|
+
</div>
|
|
1122
|
+
`;
|
|
1123
|
+
|
|
1124
|
+
document.body.appendChild(modalOverlay);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
async shutdownAgent(agentId) {
|
|
1128
|
+
// Close modal
|
|
1129
|
+
const modal = document.getElementById('shutdown-modal');
|
|
1130
|
+
if (modal) modal.remove();
|
|
1075
1131
|
|
|
1076
1132
|
try {
|
|
1077
1133
|
const response = await fetch('/php-backend/api/delete-agent.php', {
|
|
@@ -1083,14 +1139,14 @@ class ShellMirrorDashboard {
|
|
|
1083
1139
|
|
|
1084
1140
|
const data = await response.json();
|
|
1085
1141
|
if (data.success) {
|
|
1086
|
-
console.log('[DASHBOARD] Agent
|
|
1142
|
+
console.log('[DASHBOARD] Agent shut down:', agentId);
|
|
1087
1143
|
await this.refreshDashboardData();
|
|
1088
1144
|
} else {
|
|
1089
|
-
alert('Failed to
|
|
1145
|
+
alert('Failed to shut down agent: ' + (data.message || 'Unknown error'));
|
|
1090
1146
|
}
|
|
1091
1147
|
} catch (error) {
|
|
1092
|
-
console.error('[DASHBOARD]
|
|
1093
|
-
alert('Failed to
|
|
1148
|
+
console.error('[DASHBOARD] Shutdown agent failed:', error);
|
|
1149
|
+
alert('Failed to shut down agent: ' + error.message);
|
|
1094
1150
|
}
|
|
1095
1151
|
}
|
|
1096
1152
|
|
|
@@ -1322,4 +1378,13 @@ window.addEventListener('beforeunload', () => {
|
|
|
1322
1378
|
if (dashboard && dashboard.refreshInterval) {
|
|
1323
1379
|
clearInterval(dashboard.refreshInterval);
|
|
1324
1380
|
}
|
|
1381
|
+
});
|
|
1382
|
+
|
|
1383
|
+
// Close agent menu when clicking outside
|
|
1384
|
+
document.addEventListener('click', (e) => {
|
|
1385
|
+
if (!e.target.closest('.agent-menu')) {
|
|
1386
|
+
document.querySelectorAll('.agent-menu-dropdown.show').forEach(el => {
|
|
1387
|
+
el.classList.remove('show');
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1325
1390
|
});
|