Rev 36352 | Blame | Compare with Previous | Last modification | View Log | RSS feed
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"/><title>Cron Batches</title><style>* { box-sizing: border-box; }body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif; margin: 0; background: #f5f6f8; color: #222; }header { background: #fff; padding: 16px 24px; border-bottom: 1px solid #e3e5e8; display: flex; align-items: center; justify-content: space-between; }header h1 { margin: 0; font-size: 18px; font-weight: 600; }header .actions { display: flex; gap: 8px; align-items: center; }button.refresh { background: #2563eb; color: #fff; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 14px; }button.refresh:hover { background: #1d4ed8; }button.refresh:disabled { opacity: 0.6; cursor: not-allowed; }.meta { color: #6b7280; font-size: 13px; }main { padding: 16px 24px 48px; }.date-group { margin-top: 20px; }.date-group h2 { font-size: 13px; font-weight: 600; color: #6b7280; text-transform: uppercase; letter-spacing: 0.05em; margin: 8px 0; }table { width: 100%; background: #fff; border-collapse: collapse; border-radius: 6px; overflow: hidden; box-shadow: 0 1px 2px rgba(0,0,0,0.04); }thead th { background: #f9fafb; text-align: left; font-size: 12px; font-weight: 600; color: #6b7280; text-transform: uppercase; padding: 10px 12px; border-bottom: 1px solid #e5e7eb; }tbody tr { cursor: pointer; transition: background 0.1s; }tbody tr:hover { background: #f3f4f6; }tbody td { padding: 10px 12px; font-size: 14px; border-bottom: 1px solid #f3f4f6; }tbody tr:last-child td { border-bottom: none; }.badge { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 12px; font-weight: 500; }.badge-completed { background: #d1fae5; color: #065f46; }.badge-partial { background: #fef3c7; color: #92400e; }.badge-inprogress { background: #dbeafe; color: #1e40af; }.badge-failed { background: #fee2e2; color: #991b1b; }.badge-muted { background: #f3f4f6; color: #9ca3af; }.counts { font-variant-numeric: tabular-nums; }.success-count { color: #059669; font-weight: 600; }.failure-count { color: #dc2626; font-weight: 600; }.muted { color: #9ca3af; }.modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.4); display: none; align-items: center; justify-content: center; z-index: 50; }.modal-overlay.open { display: flex; }.modal { background: #fff; max-width: 900px; width: 95%; max-height: 85vh; display: flex; flex-direction: column; border-radius: 8px; overflow: hidden; }.modal-header { padding: 14px 20px; border-bottom: 1px solid #e5e7eb; display: flex; align-items: center; justify-content: space-between; }.modal-header h3 { margin: 0; font-size: 16px; font-weight: 600; }.modal-header button { background: none; border: none; font-size: 22px; cursor: pointer; color: #6b7280; line-height: 1; }.modal-body { padding: 12px 20px; overflow-y: auto; }.row-failed { background: #fef2f2; }.row-pending { background: #fffbeb; }.empty { text-align: center; color: #9ca3af; padding: 40px 0; }.error-text { color: #991b1b; font-family: monospace; font-size: 12px; word-break: break-word; }</style></head><body><header><h1>Cron Batches</h1><div class="actions"><span class="meta" id="lastUpdated"></span><button class="refresh" id="refreshBtn">Refresh</button></div></header><main><div id="content"></div></main><div class="modal-overlay" id="modal"><div class="modal"><div class="modal-header"><h3 id="modalTitle">Batch items</h3><button id="modalClose" aria-label="Close">×</button></div><div class="modal-body" id="modalBody"></div></div></div><script>(function () {var contentEl = document.getElementById('content');var lastUpdatedEl = document.getElementById('lastUpdated');var refreshBtn = document.getElementById('refreshBtn');var modal = document.getElementById('modal');var modalBody = document.getElementById('modalBody');var modalTitle = document.getElementById('modalTitle');function el(tag, attrs, text) {var e = document.createElement(tag);if (attrs) for (var k in attrs) {if (k === 'class') e.className = attrs[k];else if (k === 'dataset') for (var d in attrs[k]) e.dataset[d] = attrs[k][d];else e.setAttribute(k, attrs[k]);}if (text != null) e.textContent = String(text);return e;}function fmtTime(s) {if (!s) return '-';var t = String(s).replace('T', ' ');return t.length > 19 ? t.substring(0, 19) : t;}function mutedSpan(text) {return el('span', {class: 'muted'}, text);}function timeCell(s) {var td = el('td');if (!s) td.appendChild(mutedSpan('-'));else td.textContent = fmtTime(s);return td;}function batchStatusBadge(s) {var cls = 'badge badge-muted';var label = s || '-';if (s === 'COMPLETED') cls = 'badge badge-completed';else if (s === 'PARTIAL_FAILURE') cls = 'badge badge-partial';else if (s === 'IN_PROGRESS') cls = 'badge badge-inprogress';else if (s === 'FAILED') cls = 'badge badge-failed';return el('span', {class: cls}, label);}function itemStatusBadge(s) {var cls = 'badge badge-muted';var label = s || '-';if (s === 'SUCCESS') cls = 'badge badge-completed';else if (s === 'FAILED') cls = 'badge badge-failed';else if (s === 'PENDING') cls = 'badge badge-partial';return el('span', {class: cls}, label);}function clear(node) { while (node.firstChild) node.removeChild(node.firstChild); }function renderBatches(batches) {clear(contentEl);if (!batches || batches.length === 0) {contentEl.appendChild(el('div', {class: 'empty'}, 'No batches found.'));return;}var groups = {};var order = [];batches.forEach(function (b) {var day = (b.startedAt || '').substring(0, 10) || 'Unknown';if (!groups[day]) { groups[day] = []; order.push(day); }groups[day].push(b);});order.forEach(function (day) {var group = el('div', {class: 'date-group'});group.appendChild(el('h2', null, day));var table = el('table');var thead = el('thead');var headRow = el('tr');['ID', 'Job', 'Started', 'Completed', 'Total', 'Success', 'Failed', 'Status'].forEach(function (h) {headRow.appendChild(el('th', null, h));});thead.appendChild(headRow);table.appendChild(thead);var tbody = el('tbody');groups[day].forEach(function (b) {var tr = el('tr');tr.dataset.batchId = b.id;tr.dataset.jobName = b.jobName || '';tr.addEventListener('click', onRowClick);tr.appendChild(el('td', null, b.id));tr.appendChild(el('td', null, b.jobName || ''));tr.appendChild(timeCell(b.startedAt));tr.appendChild(timeCell(b.completedAt));tr.appendChild(el('td', {class: 'counts'}, b.total));tr.appendChild(el('td', {class: 'counts success-count'}, b.success));tr.appendChild(el('td', {class: 'counts failure-count'}, b.failure || 0));var statusTd = el('td');statusTd.appendChild(batchStatusBadge(b.status));tr.appendChild(statusTd);tbody.appendChild(tr);});table.appendChild(tbody);group.appendChild(table);contentEl.appendChild(group);});}function onRowClick(ev) {var tr = ev.currentTarget;var id = parseInt(tr.dataset.batchId, 10);openBatch(id, tr.dataset.jobName || '');}function loadBatches() {refreshBtn.disabled = true;refreshBtn.textContent = 'Refreshing...';fetch('/admin/cron-batches/list?limit=200', {credentials: 'same-origin'}).then(function (r) { return r.json(); }).then(renderBatches).catch(function (e) {clear(contentEl);contentEl.appendChild(el('div', {class: 'empty'}, 'Failed to load: ' + (e && e.message ? e.message : 'unknown error')));}).then(function () {refreshBtn.disabled = false;refreshBtn.textContent = 'Refresh';lastUpdatedEl.textContent = 'Updated ' + new Date().toLocaleTimeString();});}function renderItems(items) {clear(modalBody);if (!items || items.length === 0) {modalBody.appendChild(el('div', {class: 'empty'}, 'No items.'));return;}var table = el('table');var thead = el('thead');var headRow = el('tr');['Fofo ID', 'Partner', 'Status', 'Started', 'Completed', 'Error'].forEach(function (h) {headRow.appendChild(el('th', null, h));});thead.appendChild(headRow);table.appendChild(thead);var tbody = el('tbody');items.forEach(function (i) {var rowCls = '';if (i.status === 'FAILED') rowCls = 'row-failed';else if (i.status === 'PENDING') rowCls = 'row-pending';var tr = el('tr', {class: rowCls});tr.appendChild(el('td', null, i.fofoId));tr.appendChild(el('td', null, i.partnerName || ''));var statusTd = el('td');statusTd.appendChild(itemStatusBadge(i.status));tr.appendChild(statusTd);tr.appendChild(timeCell(i.startedAt));tr.appendChild(timeCell(i.completedAt));tr.appendChild(el('td', {class: 'error-text'}, i.errorMessage || ''));tbody.appendChild(tr);});table.appendChild(tbody);modalBody.appendChild(table);}function openBatch(batchId, jobName) {modalTitle.textContent = 'Batch #' + batchId + (jobName ? ' | ' + jobName : '');clear(modalBody);modalBody.appendChild(el('div', {class: 'empty'}, 'Loading items...'));modal.classList.add('open');fetch('/admin/cron-batches/' + batchId + '/items', {credentials: 'same-origin'}).then(function (r) { return r.json(); }).then(renderItems).catch(function (e) {clear(modalBody);modalBody.appendChild(el('div', {class: 'empty'}, 'Failed to load items: ' + (e && e.message ? e.message : 'unknown error')));});}function closeModal() { modal.classList.remove('open'); }refreshBtn.addEventListener('click', loadBatches);document.getElementById('modalClose').addEventListener('click', closeModal);modal.addEventListener('click', function (e) { if (e.target === modal) closeModal(); });document.addEventListener('keydown', function (e) { if (e.key === 'Escape') closeModal(); });loadBatches();})();</script></body></html>