adobe-to-docusign-migrator/web/static/js/activity.js

135 lines
4.8 KiB
JavaScript

// Recent activity view for tester/admin auditing
import { api } from './api.js';
import { state } from './state.js';
import { escHtml, formatDateTime } from './utils.js';
const ACTION_LABELS = {
adobe_connected: 'Adobe connected',
adobe_disconnected: 'Adobe disconnected',
docusign_authorization_requested: 'DocuSign auth requested',
docusign_authorization_started: 'DocuSign auth started',
docusign_connected: 'DocuSign connected',
docusign_account_selected: 'DocuSign account selected',
docusign_disconnected: 'DocuSign disconnected',
migration_run: 'Migration run',
migration_batch_started: 'Batch migration started',
migration_batch_completed: 'Batch migration completed',
verification_sent: 'Verification sent',
verification_voided: 'Verification voided',
};
const ACTIVITY_SCOPE_KEY = 'migrator_activity_scope';
export async function renderActivity() {
const outlet = document.getElementById('router-outlet');
outlet.innerHTML = `<div class="empty-state"><div class="spinner"></div></div>`;
const showAll = state.auth.isAdmin && localStorage.getItem(ACTIVITY_SCOPE_KEY) === 'all';
try {
const data = await api.audit.recent(150, showAll);
const events = data.events || [];
const isAdmin = !!data.is_admin;
const scope = data.scope || 'session';
outlet.innerHTML = `
<div class="page-header">
<div>
<div class="page-title">Recent Activity</div>
<div class="page-subtitle">${scope === 'all'
? 'Admin view of all tester activity.'
: 'Your activity in this browser session.'}</div>
</div>
${isAdmin ? `
<div class="page-actions">
<button class="btn btn-secondary btn-sm" id="btn-toggle-activity-scope">${scope === 'all' ? 'Show My Activity' : 'Show All Activity'}</button>
</div>
` : ''}
</div>
${events.length === 0 ? `
<div class="empty-state">
<div class="empty-state-icon">🧾</div>
<div class="empty-state-title">No activity yet</div>
<div class="empty-state-sub">Recent tester actions will appear here after people connect and use the app.</div>
</div>
` : `
<div class="card">
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Time</th>
<th>Action</th>
<th>DocuSign</th>
<th>Adobe</th>
<th>Session</th>
<th>IP</th>
<th>Details</th>
</tr>
</thead>
<tbody>
${events.map(renderEventRow).join('')}
</tbody>
</table>
</div>
</div>
`}
`;
document.getElementById('btn-toggle-activity-scope')?.addEventListener('click', () => {
localStorage.setItem(ACTIVITY_SCOPE_KEY, scope === 'all' ? 'session' : 'all');
renderActivity();
});
} catch (e) {
outlet.innerHTML = `<div class="callout error"><span class="callout-icon">❌</span>Failed to load activity: ${escHtml(e.message)}</div>`;
}
}
function renderEventRow(event) {
const docusignLabel = event.docusign_account_name || event.docusign_user_name || event.docusign_user_email || '—';
const adobeLabel = event.adobe_account_name || event.adobe_user_name || event.adobe_user_email || '—';
const sessionId = event.session_id ? `${event.session_id.slice(0, 10)}` : '—';
const detailText = formatDetails(event.details);
return `
<tr>
<td style="white-space: nowrap">${escHtml(formatDateTime(event.timestamp))}</td>
<td><span class="badge badge-blue">${escHtml(ACTION_LABELS[event.action] || event.action || 'Activity')}</span></td>
<td>
<div class="table-name">${escHtml(docusignLabel)}</div>
<div class="table-sub">${escHtml(event.docusign_account_id || event.docusign_user_email || '')}</div>
</td>
<td>
<div class="table-name">${escHtml(adobeLabel)}</div>
<div class="table-sub">${escHtml(event.adobe_account_id || event.adobe_user_email || '')}</div>
</td>
<td class="mono">${escHtml(sessionId)}</td>
<td>${escHtml(event.ip || '—')}</td>
<td class="activity-details">${escHtml(detailText)}</td>
</tr>
`;
}
function formatDetails(details) {
if (!details || typeof details !== 'object') {
return '—';
}
const parts = Object.entries(details)
.filter(([, value]) => value !== null && value !== undefined && value !== '')
.map(([key, value]) => `${humanizeKey(key)}: ${formatValue(value)}`);
return parts.length ? parts.join(' | ') : '—';
}
function humanizeKey(key) {
return key.replace(/_/g, ' ');
}
function formatValue(value) {
if (typeof value === 'boolean') {
return value ? 'yes' : 'no';
}
return String(value);
}