Link support codes to agent sessions

- Server: Accept support_code param in WebSocket connection
- Server: Link code to session when agent connects, mark as connected
- Server: Mark code as completed when agent disconnects
- Agent: Accept support code from command line argument
- Agent: Send hostname and support_code in WebSocket params
- Portal: Trigger agent download with code in filename
- Portal: Show code reminder in download instructions
- Dashboard: Add machines list fetching (Access tab)
- Add TODO.md for feature tracking

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-28 14:11:52 -07:00
parent 9af59158b2
commit 1d2ca47771
10 changed files with 447 additions and 21 deletions

View File

@@ -269,17 +269,17 @@
<div class="sidebar-panel">
<div class="sidebar-section">
<div class="sidebar-section-title">Status</div>
<div class="sidebar-item active">
<div class="sidebar-item active" data-filter="all">
<span>All Machines</span>
<span class="sidebar-count">0</span>
<span class="sidebar-count" id="countAll">0</span>
</div>
<div class="sidebar-item">
<div class="sidebar-item" data-filter="online">
<span>Online</span>
<span class="sidebar-count">0</span>
<span class="sidebar-count" id="countOnline">0</span>
</div>
<div class="sidebar-item">
<div class="sidebar-item" data-filter="offline">
<span>Offline</span>
<span class="sidebar-count">0</span>
<span class="sidebar-count" id="countOffline">0</span>
</div>
</div>
<div class="sidebar-section">
@@ -289,13 +289,13 @@
</div>
</div>
</div>
<div class="main-panel">
<div class="main-panel" id="machinesList">
<div class="empty-state">
<h3>No machines</h3>
<p>Install the agent on a machine to see it here</p>
</div>
</div>
<div class="detail-panel">
<div class="detail-panel" id="machineDetail">
<div class="empty-state">
<h3>Select a machine</h3>
<p>Click a machine to view details</p>
@@ -476,6 +476,90 @@
}
loadSessions();
// Load connected machines (Access tab)
let machines = [];
let selectedMachine = null;
async function loadMachines() {
try {
const response = await fetch("/api/sessions");
machines = await response.json();
// Update counts
document.getElementById("countAll").textContent = machines.length;
document.getElementById("countOnline").textContent = machines.length;
document.getElementById("countOffline").textContent = "0";
renderMachinesList();
} catch (err) {
console.error("Failed to load machines:", err);
}
}
function renderMachinesList() {
const container = document.getElementById("machinesList");
if (machines.length === 0) {
container.innerHTML = '<div class="empty-state"><h3>No machines</h3><p>Install the agent on a machine to see it here</p></div>';
return;
}
container.innerHTML = '<div style="padding: 12px;">' + machines.map(m => {
const started = new Date(m.started_at).toLocaleString();
const isSelected = selectedMachine?.id === m.id;
return '<div class="sidebar-item' + (isSelected ? ' active' : '') + '" onclick="selectMachine(\'' + m.id + '\')" style="margin-bottom: 8px; padding: 12px;">' +
'<div style="display: flex; align-items: center; gap: 12px;">' +
'<div style="width: 10px; height: 10px; border-radius: 50%; background: hsl(142, 76%, 50%);"></div>' +
'<div>' +
'<div style="font-weight: 500;">' + (m.agent_name || m.agent_id.slice(0,8)) + '</div>' +
'<div style="font-size: 12px; color: hsl(var(--muted-foreground));">Connected ' + started + '</div>' +
'</div>' +
'</div>' +
'</div>';
}).join("") + '</div>';
}
function selectMachine(id) {
selectedMachine = machines.find(m => m.id === id);
renderMachinesList();
renderMachineDetail();
}
function renderMachineDetail() {
const container = document.getElementById("machineDetail");
if (!selectedMachine) {
container.innerHTML = '<div class="empty-state"><h3>Select a machine</h3><p>Click a machine to view details</p></div>';
return;
}
const m = selectedMachine;
const started = new Date(m.started_at).toLocaleString();
container.innerHTML =
'<div class="detail-section">' +
'<div class="detail-section-title">Machine Info</div>' +
'<div class="detail-row"><span class="detail-label">Agent ID</span><span class="detail-value">' + m.agent_id.slice(0,8) + '...</span></div>' +
'<div class="detail-row"><span class="detail-label">Session ID</span><span class="detail-value">' + m.id.slice(0,8) + '...</span></div>' +
'<div class="detail-row"><span class="detail-label">Connected</span><span class="detail-value">' + started + '</span></div>' +
'<div class="detail-row"><span class="detail-label">Viewers</span><span class="detail-value">' + m.viewer_count + '</span></div>' +
'</div>' +
'<div class="detail-section">' +
'<div class="detail-section-title">Actions</div>' +
'<button class="btn btn-primary" style="width: 100%; margin-bottom: 8px;" onclick="connectToMachine(\'' + m.id + '\')">Connect</button>' +
'<button class="btn btn-outline" style="width: 100%;" disabled>Transfer Files</button>' +
'</div>';
}
function connectToMachine(sessionId) {
// TODO: Open viewer in new window
alert("Viewer not yet implemented.\\n\\nSession ID: " + sessionId + "\\n\\nWebSocket: wss://connect.azcomputerguru.com/ws/viewer?session_id=" + sessionId);
}
// Refresh machines every 5 seconds
loadMachines();
setInterval(loadMachines, 5000);
</script>
</body>
</html>