// History & Audit view β€” filterable, exportable migration history import { api } from './api.js'; import { escHtml, formatDateTime, shortHash, downloadCsv, debounce } from './utils.js'; let _allRecords = []; let _filter = { search: '', status: 'all', from: '', to: '' }; let _sort = { col: 'timestamp', dir: 'desc' }; const PAGE_SIZE = 50; let _page = 0; export async function renderHistory() { const outlet = document.getElementById('router-outlet'); outlet.innerHTML = `
`; try { const data = await api.migrate.history(); _allRecords = (data.history || []).reverse(); // newest first } catch (e) { outlet.innerHTML = `
❌Failed to load history: ${escHtml(e.message)}
`; return; } _page = 0; _render(); } function _render() { const outlet = document.getElementById('router-outlet'); const filtered = _applyFilter(_allRecords); const page = filtered.slice(_page * PAGE_SIZE, (_page + 1) * PAGE_SIZE); const totalPages = Math.ceil(filtered.length / PAGE_SIZE); outlet.innerHTML = `
${filtered.length === 0 ? `
πŸ“‹
${_allRecords.length ? 'No records match your filter' : 'No migration history yet'}
${_allRecords.length ? 'Try clearing the search or filters.' : 'Run a migration to see history here.'}
` : `
${_th('timestamp', 'Time')} ${_th('adobe_template_name', 'Template')} ${_th('action', 'Action')} ${_th('status', 'Status')} ${page.map(r => _historyRow(r)).join('')}
DocuSign ID Checksum
${totalPages > 1 ? ` ` : ''}
`} `; _bindEvents(filtered); } function _th(col, label) { const dir = _sort.col === col ? (_sort.dir === 'asc' ? 'sort-asc' : 'sort-desc') : ''; return `${label}`; } function _historyRow(r) { const statusBadge = r.status === 'success' ? `${escHtml(r.action || 'success')}` : `${escHtml(r.status || 'β€”')}`; const checksum = r.checksum_sha256 || r.checksum || ''; return ` ${(r.timestamp||'').slice(0,19).replace('T',' ')}
${escHtml(r.adobe_template_name || r.adobe_template_id || 'β€”')}
${escHtml(r.adobe_template_id || '')}
${escHtml(r.action || 'β€”')} ${statusBadge} ${r.docusign_template_id ? escHtml(r.docusign_template_id.slice(0,12)) + '…' : 'β€”'} ${checksum ? `${escHtml(shortHash(checksum))}` : 'β€”'} ${(r.blockers || r.warnings || r.error) ? `
${(r.blockers||[]).map(b => `
🚫 ${escHtml(b)}
`).join('')} ${(r.warnings||[]).map(w => `
⚠ ${escHtml(w)}
`).join('')} ${r.error ? `
❌ ${escHtml(r.error)}
` : ''}
` : ''} `; } function _applyFilter(records) { let list = [...records]; if (_filter.search) { const q = _filter.search.toLowerCase(); list = list.filter(r => (r.adobe_template_name || '').toLowerCase().includes(q) || (r.adobe_template_id || '').toLowerCase().includes(q) ); } if (_filter.status !== 'all') { list = list.filter(r => r.status === _filter.status); } if (_filter.from) { list = list.filter(r => r.timestamp >= _filter.from); } if (_filter.to) { list = list.filter(r => r.timestamp <= _filter.to + 'T23:59:59'); } list.sort((a, b) => { const va = a[_sort.col] || ''; const vb = b[_sort.col] || ''; return _sort.dir === 'asc' ? String(va).localeCompare(String(vb)) : String(vb).localeCompare(String(va)); }); return list; } function _bindEvents(filtered) { document.getElementById('hist-search')?.addEventListener('input', debounce(e => { _filter.search = e.target.value; _page = 0; _render(); }, 250)); document.querySelectorAll('.filter-tab[data-status]').forEach(btn => { btn.addEventListener('click', () => { _filter.status = btn.dataset.status; _page = 0; _render(); }); }); document.getElementById('hist-from')?.addEventListener('change', e => { _filter.from = e.target.value; _page = 0; _render(); }); document.getElementById('hist-to')?.addEventListener('change', e => { _filter.to = e.target.value; _page = 0; _render(); }); document.querySelectorAll('th.sortable').forEach(th => { th.addEventListener('click', () => { const col = th.dataset.col; _sort.dir = _sort.col === col && _sort.dir === 'asc' ? 'desc' : 'asc'; _sort.col = col; _page = 0; _render(); }); }); document.getElementById('pg-prev')?.addEventListener('click', () => { if (_page > 0) { _page--; _render(); } }); document.getElementById('pg-next')?.addEventListener('click', () => { _page++; _render(); }); // Expand rows document.querySelectorAll('.row-expandable').forEach(row => { row.addEventListener('click', () => { const next = row.nextElementSibling; if (next && next.classList.contains('row-expanded-content')) { const open = next.style.display !== 'none'; next.style.display = open ? 'none' : 'table-row'; } }); }); document.getElementById('btn-export-history')?.addEventListener('click', () => { downloadCsv('migration-history.csv', filtered.map(r => ({ timestamp: r.timestamp || '', template: r.adobe_template_name || r.adobe_template_id || '', adobe_id: r.adobe_template_id || '', docusign_id: r.docusign_template_id || '', action: r.action || '', status: r.status || '', checksum: r.checksum_sha256 || '', warnings: (r.warnings || []).join('; '), }))); }); }