<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test: Clickable Agent ID in Activity Log</title>
<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="/css/themes/dark.css">
<link rel="stylesheet" href="/css/components/activity-log.css">
<link rel="stylesheet" href="/css/components/node-detail-modal.css">
<style>
body {
padding: 2rem;
background: var(--bg-primary);
}
.test-container {
max-width: 800px;
margin: 0 auto;
}
.test-info {
background: var(--bg-secondary);
padding: 1rem;
margin-bottom: 1rem;
border-radius: 4px;
border: 1px solid var(--border-color);
}
.test-info h2 {
margin-top: 0;
}
.test-info ul {
margin: 0.5rem 0;
}
</style>
</head>
<body>
<div class="test-container">
<div class="test-info">
<h2>Test: Clickable Agent ID in Activity Log</h2>
<p><strong>Task:</strong> bn-6735 - Frontend: Agent ID click opens modal</p>
<p><strong>Expected behavior:</strong></p>
<ul>
<li>Agent IDs in owner badges should be clickable (e.g., 🤖 bn-34ef)</li>
<li>Clicking an agent ID should open the node detail modal</li>
<li>Hovering over an agent ID should show visual feedback (slight lift + shadow)</li>
<li>The cursor should change to pointer when hovering over agent IDs</li>
</ul>
<p><strong>Test:</strong> The sample log entries below include agent IDs. Try clicking them!</p>
</div>
<div id="activity-log"></div>
</div>
<script type="module">
import { showNodeDetailModal } from '/js/components/node-detail-modal.js';
const container = document.getElementById('activity-log');
container.className = 'activity-log-container';
const sampleEntries = [
{
timestamp: new Date(Date.now() - 120000).toISOString(), user: 'bn-34ef',
command: 'task',
args: ['update', 'bn-6735', '--status', 'in_progress'],
exit_code: 0,
success: true
},
{
timestamp: new Date(Date.now() - 300000).toISOString(), user: 'bn-486c',
command: 'ready',
args: [],
exit_code: 0,
success: true
},
{
timestamp: new Date(Date.now() - 600000).toISOString(), user: 'alice',
command: 'task',
args: ['create', '"Implement feature"', '-p', '1'],
exit_code: 0,
success: true
},
{
timestamp: new Date(Date.now() - 900000).toISOString(), user: 'bn-abc1',
command: 'task',
args: ['close', 'bn-1234', '--reason', '"Completed"'],
exit_code: 0,
success: true
}
];
function escapeHtml(str) {
if (!str) return '';
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
function formatRelativeTime(isoString) {
if (!isoString) return 'N/A';
try {
const date = new Date(isoString);
const now = new Date();
const diffMs = now - date;
const diffSec = Math.floor(diffMs / 1000);
const diffMin = Math.floor(diffSec / 60);
const diffHour = Math.floor(diffMin / 60);
if (diffHour > 0) {
return `${diffHour}h ago`;
} else if (diffMin > 0) {
return `${diffMin}m ago`;
} else {
return `${diffSec}s ago`;
}
} catch (e) {
return isoString;
}
}
function getStatusColorClass(success) {
return success ? 'log-status-success' : 'log-status-failure';
}
function renderOwnerBadge(user) {
if (!user) return '<span class="owner-badge unknown">Unknown</span>';
const isAgent = user.startsWith('bn-');
const icon = isAgent ? '🤖' : '👤';
const className = isAgent ? 'owner-badge agent' : 'owner-badge user';
if (isAgent) {
return `<span class="${className} clickable-entity-id" data-entity-id="${escapeHtml(user)}" title="Click to view ${escapeHtml(user)}">${icon} ${escapeHtml(user)}</span>`;
}
return `<span class="${className}">${icon} ${escapeHtml(user)}</span>`;
}
const ENTITY_ID_PATTERN = /\b(bn[a-z]?-[a-f0-9]{4})\b/gi;
function makeEntityIdsClickable(text) {
if (!text || !ENTITY_ID_PATTERN.test(text)) {
return escapeHtml(text);
}
ENTITY_ID_PATTERN.lastIndex = 0;
let result = '';
let lastIndex = 0;
let match;
while ((match = ENTITY_ID_PATTERN.exec(text)) !== null) {
result += escapeHtml(text.slice(lastIndex, match.index));
const entityId = match[0];
result += `<span class="clickable-entity-id" data-entity-id="${entityId}" title="Click to view ${entityId}">${entityId}</span>`;
lastIndex = ENTITY_ID_PATTERN.lastIndex;
}
result += escapeHtml(text.slice(lastIndex));
return result;
}
function renderLogEntry(entry) {
const timestamp = formatRelativeTime(entry.timestamp);
const statusClass = getStatusColorClass(entry.success);
const ownerBadge = renderOwnerBadge(entry.user);
const command = escapeHtml(entry.command || 'unknown');
const exitCode = entry.exit_code !== undefined ? entry.exit_code : '?';
const argsWithClickableIds = entry.args && entry.args.length > 0
? makeEntityIdsClickable(entry.args.join(' '))
: '';
return `
<div class="log-entry ${statusClass}">
<div class="log-entry-header">
<span class="log-timestamp">${timestamp}</span>
${ownerBadge}
<span class="log-command">${command}</span>
<span class="log-exit-code">Exit: ${exitCode}</span>
</div>
${entry.args && entry.args.length > 0 ? `
<div class="log-entry-details">
<span class="log-args">${argsWithClickableIds}</span>
</div>
` : ''}
</div>
`;
}
container.innerHTML = `
<div class="activity-log-header">
<h3>Sample Log Entries (Test)</h3>
</div>
<div class="log-entries">
${sampleEntries.map(renderLogEntry).join('')}
</div>
`;
container.addEventListener('click', (e) => {
const clickableId = e.target.closest('.clickable-entity-id');
if (!clickableId) return;
e.preventDefault();
e.stopPropagation();
const entityId = clickableId.dataset.entityId;
if (entityId) {
console.log('Clicked entity ID:', entityId);
alert(`Would open modal for: ${entityId}`);
}
});
</script>
</body>
</html>