/** * image-pages.js - helpers shared by the GE Aerospace image tabs. * Loaded by templates/_image_header.html. Exposes window.PxeImage: * * PxeImage.postJSON(url, body) POST JSON with the CSRF header; * resolves {ok, status, data}. * PxeImage.renderDiff(el, files) draw [{name, changed, diff, reformat}] * (services/control_files.public) into el. * PxeImage.reviewAndSave(opts) preview -> diff modal -> save -> reload. * opts: previewUrl, saveUrl, body (object), title (optional), * onSaved(data) (optional; default reloads the page). */ (function () { 'use strict'; function csrfToken() { var m = document.querySelector('meta[name="csrf-token"]'); return m ? m.getAttribute('content') : ''; } function postJSON(url, body) { return fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken() }, body: JSON.stringify(body || {}), credentials: 'same-origin' }).then(function (resp) { return resp.json().catch(function () { return { error: 'HTTP ' + resp.status }; }) .then(function (data) { return { ok: resp.ok, status: resp.status, data: data }; }); }); } function el(tag, cls, text) { var e = document.createElement(tag); if (cls) e.className = cls; if (text !== undefined) e.textContent = text; return e; } function renderDiff(container, files) { container.innerHTML = ''; var changed = (files || []).filter(function (f) { return f.changed; }); if (!changed.length) { container.appendChild(el('div', 'alert alert-info mb-0', 'No changes: the files already hold exactly this.')); return 0; } changed.forEach(function (f) { var box = el('div', 'diff-file'); var head = el('div', 'diff-file-head'); head.appendChild(el('i', 'bi bi-file-earmark-diff')); head.appendChild(el('span', 'mono', f.name)); if (!f.existed) head.appendChild(el('span', 'badge badge-info', 'new file')); if (f.reformat) { var w = el('span', 'badge badge-warning', 'whitespace also changes'); w.title = 'The current file is not laid out the way it will be written; ' + 'unchanged lines may show as changed.'; head.appendChild(w); } box.appendChild(head); var pre = el('pre', 'diff-view'); String(f.diff || '').split('\n').forEach(function (line, i, all) { if (i === all.length - 1 && line === '') return; var c = ''; if (line.indexOf('+++') === 0 || line.indexOf('---') === 0) c = 'd-meta'; else if (line.indexOf('@@') === 0) c = 'd-hunk'; else if (line.charAt(0) === '+') c = 'd-add'; else if (line.charAt(0) === '-') c = 'd-del'; pre.appendChild(el('span', c, line || ' ')); }); box.appendChild(pre); container.appendChild(box); }); return changed.length; } function reviewAndSave(opts) { var modalEl = document.getElementById('imageReviewModal'); var body = document.getElementById('imageReviewBody'); var err = document.getElementById('imageReviewError'); var confirmBtn = document.getElementById('imageReviewConfirm'); var title = document.getElementById('imageReviewTitle'); if (!modalEl || !window.bootstrap) return; title.textContent = opts.title || 'Review changes'; body.innerHTML = '
Building the preview...
'; err.classList.add('d-none'); confirmBtn.disabled = true; var modal = bootstrap.Modal.getOrCreateInstance(modalEl); modal.show(); function fail(msg) { err.textContent = msg; err.classList.remove('d-none'); } postJSON(opts.previewUrl, opts.body).then(function (r) { if (!r.ok) { body.innerHTML = ''; fail(r.data.error || 'Preview failed.'); return; } var n = renderDiff(body, r.data.files); var expect = {}; (r.data.files || []).forEach(function (f) { expect[f.name] = f.sha_before; }); confirmBtn.disabled = n === 0; confirmBtn.onclick = function () { confirmBtn.disabled = true; var payload = Object.assign({}, opts.body, { expect: expect }); postJSON(opts.saveUrl, payload).then(function (s) { if (!s.ok) { fail(s.data.error || 'Save failed.'); confirmBtn.disabled = false; return; } if (opts.onSaved) opts.onSaved(s.data); else window.location.reload(); }).catch(function (e) { fail('Save failed: ' + e); confirmBtn.disabled = false; }); }; }).catch(function (e) { body.innerHTML = ''; fail('Preview failed: ' + e); }); } window.PxeImage = { postJSON: postJSON, renderDiff: renderDiff, reviewAndSave: reviewAndSave }; })();