(function () { 'use strict'; var base = document.body.getAttribute('data-base-path') || ''; function buildStatusIconHtml(status) { var s = String(status || '').toLowerCase(); if (s === 'running' || s === 'queued') { return '' + ''; } if (s === 'passed') { return ''; } return '' + ''; } function updateBuildStatusCell(cell, status, phase) { if (!cell) return; var icon = cell.querySelector('.build-status-icon'); var label = cell.querySelector('.build-status-label'); var html = buildStatusIconHtml(status); if (icon) { icon.outerHTML = html; } else { cell.insertAdjacentHTML('afterbegin', html); } if (label) { label.textContent = status || ''; } if (phase !== undefined) { var row = cell.closest('tr'); if (row) { var phaseCell = row.querySelector('.build-phase-cell'); if (phaseCell) { phaseCell.textContent = phase || ''; } } } } function updateBuildRow(row, build) { if (!row || !build) return; row.setAttribute('data-build-status', build.status || ''); var cell = row.querySelector('[data-build-status-cell="' + build.id + '"]'); updateBuildStatusCell(cell, build.status, build.phase); var play = row.querySelector('.build-transport-play'); var stop = row.querySelector('.build-transport-stop'); var active = build.status === 'running' || build.status === 'queued'; if (play) { play.setAttribute('data-build-status', build.status || ''); } if (stop) { stop.setAttribute('data-build-status', build.status || ''); stop.disabled = !active; } } function refreshRecentBuilds() { var tbody = document.getElementById('builds-recent-tbody'); if (!tbody) return; var xhr = new XMLHttpRequest(); xhr.open('GET', base + '/api/builds.php', true); xhr.onload = function () { var j = null; try { j = JSON.parse(xhr.responseText); } catch (e) { j = null; } if (!j || !j.ok || !j.builds) return; j.builds.forEach(function (b) { var row = tbody.querySelector('tr[data-build-id="' + b.id + '"]'); updateBuildRow(row, b); }); }; xhr.send(); setTimeout(refreshRecentBuilds, 1000); } function bindBuildListTransport() { var tbody = document.getElementById('builds-recent-tbody'); if (!tbody) return; tbody.addEventListener('click', function (ev) { var play = ev.target.closest('.build-transport-play'); var stop = ev.target.closest('.build-transport-stop'); if (play) { var pid = Number(play.getAttribute('data-build-id')); var pst = play.getAttribute('data-build-status') || ''; if (!pid) return; var msg = pst === 'running' || pst === 'queued' ? 'Force re-run? This stops the current run and starts a new build with the same parameters.' : null; if (msg && !confirm(msg)) return; postBuildAction({ action: 'force_rerun', build_id: pid }).then(function (j) { if (j.build && j.build.id) { window.location.href = base + '/?view=build&id=' + j.build.id; } }).catch(function (err) { alert(err.message || 'Re-run failed'); }); return; } if (stop) { var sid = Number(stop.getAttribute('data-build-id')); if (!sid || stop.disabled) return; if (!confirm('Stop this build now?')) return; postBuildAction({ action: 'stop', build_id: sid }).then(function () { refreshRecentBuilds(); }).catch(function (err) { alert(err.message || 'Stop failed'); }); } }); } if (document.body.getAttribute('data-view') === 'home' && document.getElementById('builds-recent-tbody')) { bindBuildListTransport(); refreshRecentBuilds(); } var themeSel = document.getElementById('theme-select'); if (themeSel) { var cur = localStorage.getItem('crash_console_theme') || 'dark'; themeSel.value = cur; themeSel.addEventListener('change', function () { document.documentElement.setAttribute('data-theme', themeSel.value); localStorage.setItem('crash_console_theme', themeSel.value); }); } var form = document.getElementById('build-trigger-form'); if (form) { form.addEventListener('submit', function (ev) { ev.preventDefault(); var status = document.getElementById('build-trigger-status'); var fd = new FormData(form); var payload = { git_ref: fd.get('git_ref') || 'next', gradle_task: fd.get('gradle_task') || 'assembleDebug', ota_channel: fd.get('ota_channel') || 'staging', run_tests: !!form.querySelector('[name=run_tests]').checked, run_native: !!form.querySelector('[name=run_native]').checked, run_apk: !!form.querySelector('[name=run_apk]').checked, auto_ota: !!form.querySelector('[name=auto_ota]').checked, auto_deploy: !!form.querySelector('[name=auto_deploy]').checked }; status.textContent = 'Starting…'; fetch(base + '/api/build_trigger.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'same-origin', body: JSON.stringify(payload) }).then(function (r) { return r.text().then(function (text) { var j = null; try { j = JSON.parse(text); } catch (e) { throw new Error(r.status + ': ' + (text.slice(0, 120) || r.statusText)); } if (!r.ok) { throw new Error((j && j.error) || ('HTTP ' + r.status)); } return j; }); }).then(function (j) { if (j.ok && j.build && j.build.id) { window.location.href = base + '/?view=build&id=' + j.build.id; } else { status.textContent = j.error || 'Failed'; } }).catch(function (err) { status.textContent = err && err.message ? err.message : 'Network error'; }); }); } function postBuildAction(payload) { return fetch(base + '/api/build_actions.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'same-origin', body: JSON.stringify(payload) }).then(function (r) { return r.text().then(function (text) { var j = null; try { j = JSON.parse(text); } catch (e) { throw new Error(r.status + ': ' + (text.slice(0, 120) || r.statusText)); } if (!r.ok || !j.ok) { throw new Error((j && j.error) || ('HTTP ' + r.status)); } return j; }); }); } var rerunBtn = document.getElementById('build-rerun-btn'); var rerunFailedBtn = document.getElementById('build-rerun-failed-btn'); var rerunSshBtn = document.getElementById('build-rerun-ssh-btn'); var stopBtn = document.getElementById('build-stop-btn'); var rerunAsToggle = document.getElementById('build-rerun-as-toggle'); var rerunAsForm = document.getElementById('build-rerun-as-form'); var actionStatus = document.getElementById('build-action-status'); function goToBuild(j) { if (j.build && j.build.id) { window.location.href = base + '/?view=build&id=' + j.build.id; } } function bindRerunAction(btn, action, confirmMsg) { if (!btn) return; btn.addEventListener('click', function () { var id = Number(btn.getAttribute('data-build-id')); if (!id) return; if (confirmMsg && !confirm(confirmMsg)) return; if (actionStatus) actionStatus.textContent = 'Starting…'; postBuildAction({ action: action, build_id: id }).then(goToBuild).catch(function (err) { if (actionStatus) actionStatus.textContent = err.message || 'Failed'; }); }); } if (rerunAsToggle && rerunAsForm) { rerunAsToggle.addEventListener('click', function () { rerunAsForm.hidden = !rerunAsForm.hidden; }); } bindRerunAction(rerunBtn, 'rerun', null); bindRerunAction(rerunFailedBtn, 'rerun_from_failed', null); bindRerunAction( rerunSshBtn, 'rerun_with_ssh', 'Start an SSH debug session on the builder? You will need shell access to the builder host.' ); if (stopBtn) { stopBtn.addEventListener('click', function () { var id = Number(stopBtn.getAttribute('data-build-id')); if (!id || !confirm('Stop this build?')) return; if (actionStatus) actionStatus.textContent = 'Stopping…'; postBuildAction({ action: 'stop', build_id: id }).then(function () { window.location.reload(); }).catch(function (err) { if (actionStatus) actionStatus.textContent = err.message || 'Failed'; }); }); } if (rerunAsForm) { rerunAsForm.addEventListener('submit', function (ev) { ev.preventDefault(); var id = Number(rerunBtn && rerunBtn.getAttribute('data-build-id')); if (!id) return; var fd = new FormData(rerunAsForm); var params = { git_ref: fd.get('git_ref') || 'next', gradle_task: fd.get('gradle_task') || 'assembleDebug', ota_channel: fd.get('ota_channel') || 'staging', run_tests: !!rerunAsForm.querySelector('[name=run_tests]').checked, run_native: !!rerunAsForm.querySelector('[name=run_native]').checked, run_apk: !!rerunAsForm.querySelector('[name=run_apk]').checked, auto_ota: !!rerunAsForm.querySelector('[name=auto_ota]').checked, auto_deploy: !!rerunAsForm.querySelector('[name=auto_deploy]').checked }; if (actionStatus) actionStatus.textContent = 'Starting…'; postBuildAction({ action: 'rerun_as', build_id: id, params: params }).then(function (j) { if (j.build && j.build.id) { window.location.href = base + '/?view=build&id=' + j.build.id; } }).catch(function (err) { if (actionStatus) actionStatus.textContent = err.message || 'Failed'; }); }); } var buildId = document.body.getAttribute('data-build-id'); var buildStatus = document.body.getAttribute('data-build-status') || ''; var logEl = document.getElementById('build-log-tail'); if (buildId && logEl) { var offset = 0; var full = ''; var showFull = false; var DISPLAY_TAIL = 50000; function renderLog() { logEl.textContent = showFull ? full : full.slice(-DISPLAY_TAIL); logEl.scrollTop = logEl.scrollHeight; } function poll() { var url = base + '/api/build_log.php?id=' + encodeURIComponent(buildId) + '&offset=' + offset; fetch(url) .then(function (r) { return r.json(); }) .then(function (j) { if (!j.ok) return; if (j.log && j.log.text) { full += j.log.text; offset = j.log.offset || offset; renderLog(); } if (j.build) { var iconEl = document.getElementById('build-status-icon'); var textEl = document.getElementById('build-status-text'); if (iconEl) { iconEl.innerHTML = buildStatusIconHtml(j.build.status); } if (textEl) { var errEl = document.getElementById('build-error-message'); var err = j.build.error_message ? (' · ' + String(j.build.error_message).replace(/&/g, '&').replace(//g, '>') + '') : ''; textEl.innerHTML = 'Status: ' + (j.build.status || '') + ' · Phase: ' + (j.build.phase || '') + err; } document.body.setAttribute('data-build-status', j.build.status || ''); } var running = j.build && j.build.status === 'running'; var eof = j.log && j.log.eof; if (running || !eof) { setTimeout(poll, running ? 1000 : 250); } else if (j.build && j.build.phase === 'ssh_debug' && !document.getElementById('build-ssh-panel')) { window.location.reload(); } }).catch(function () { setTimeout(poll, 2000); }); } function loadFullLog() { return fetch(base + '/api/build_log.php?id=' + encodeURIComponent(buildId) + '&full=1') .then(function (r) { return r.json(); }) .then(function (j) { if (j.ok && j.log && j.log.text) { full = j.log.text; offset = j.log.offset || 0; renderLog(); } }); } if (buildStatus && buildStatus !== 'running' && buildStatus !== 'queued') { loadFullLog().catch(function () { poll(); }); } else { poll(); } var expand = document.getElementById('build-log-expand'); if (expand) { expand.addEventListener('click', function () { showFull = true; if (full === '') { loadFullLog().then(function () { renderLog(); }); } else { renderLog(); } expand.textContent = 'Showing full log'; expand.disabled = true; }); } } })();