118 lines
4.0 KiB
JavaScript
118 lines
4.0 KiB
JavaScript
// Recent activity view for tester/admin auditing
|
|
|
|
import { api } from './api.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',
|
|
};
|
|
|
|
export async function renderActivity() {
|
|
const outlet = document.getElementById('router-outlet');
|
|
outlet.innerHTML = `<div class="empty-state"><div class="spinner"></div></div>`;
|
|
|
|
try {
|
|
const data = await api.audit.recent(150);
|
|
const events = data.events || [];
|
|
|
|
outlet.innerHTML = `
|
|
<div class="page-header">
|
|
<div>
|
|
<div class="page-title">Recent Activity</div>
|
|
<div class="page-subtitle">Who connected, selected accounts, migrated templates, and sent verification envelopes.</div>
|
|
</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>
|
|
`}
|
|
`;
|
|
} 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);
|
|
}
|