Add connected technician tracking to dashboard

- Add ViewerInfo struct to track viewer name and connection time
- Update session manager to track viewers with names
- Update API to return viewer list for each session
- Update dashboard to display "Mike Connected (3 min)" on machine bars
- Update viewer.html to pass viewer_name parameter

🤖 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 19:17:47 -07:00
parent f3b76b7b62
commit 448d3b75ac
5 changed files with 117 additions and 16 deletions

View File

@@ -673,6 +673,39 @@
}
}
// Format duration since connection
function formatDuration(connectedAt) {
const now = new Date();
const connected = new Date(connectedAt);
const diffMs = now - connected;
const diffMins = Math.floor(diffMs / 60000);
const diffHours = Math.floor(diffMins / 60);
if (diffMins < 1) return 'just now';
if (diffMins < 60) return diffMins + ' min';
if (diffHours < 24) return diffHours + ' hr';
return Math.floor(diffHours / 24) + ' day';
}
// Format connected technicians display
function formatViewers(viewers) {
if (!viewers || viewers.length === 0) return '';
// Get names (filter out default "Technician" if there's a real name)
const names = viewers.map(v => v.name || 'Technician');
// Find earliest connection for duration
const earliest = viewers.reduce((min, v) => {
const t = new Date(v.connected_at);
return t < min ? t : min;
}, new Date(viewers[0].connected_at));
const duration = formatDuration(earliest);
const nameList = names.join(', ');
return nameList + ' Connected (' + duration + ')';
}
function renderMachinesList() {
const container = document.getElementById("machinesList");
@@ -697,13 +730,19 @@
const isSelected = selectedMachine?.id === m.id;
const statusColor = m.is_online ? 'hsl(142, 76%, 50%)' : 'hsl(0, 0%, 50%)';
const statusText = m.is_online ? 'Online' : 'Offline';
const viewersText = formatViewers(m.viewers);
const viewersHtml = viewersText
? '<div style="font-size: 11px; color: hsl(142, 76%, 50%); margin-left: auto; white-space: nowrap;">' + escapeHtml(viewersText) + '</div>'
: '';
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: ' + statusColor + ';"></div>' +
'<div>' +
'<div style="display: flex; align-items: center; gap: 12px; width: 100%;">' +
'<div style="width: 10px; height: 10px; border-radius: 50%; background: ' + statusColor + '; flex-shrink: 0;"></div>' +
'<div style="flex: 1; min-width: 0;">' +
'<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));">' + statusText + ' • ' + started + '</div>' +
'</div>' +
viewersHtml +
'</div>' +
'</div>';
}).join("") + '</div>';
@@ -836,7 +875,8 @@
}
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
const wsUrl = `${protocol}//${window.location.host}/ws/viewer?session_id=${sessionId}`;
const viewerName = user?.name || user?.email || "Technician";
const wsUrl = `${protocol}//${window.location.host}/ws/viewer?session_id=${sessionId}&viewer_name=${encodeURIComponent(viewerName)}`;
console.log("Connecting chat to:", wsUrl);
chatSocket = new WebSocket(wsUrl);