Updates group: - Dell drivers and BIOS: check Dell's catalogs against all three driver maps and models.txt, download with a shared speed cap, repack, preview, apply with backups and rollback. - iPXE boot files: check GitHub for the signed Secure Boot set, verify the sha256 and each file's signer, install atomically, roll back. Includes the by-hand procedure. - Blancco: upload a release, stage it, test-boot it from the menu, activate, roll back. - BPRT sync: validate a batch uploaded from the MediaCreator Lite cache (scripts/windows/Sync-BPRT.ps1 or a zip), show the diff, swap with the site overlay files kept. Images: one sidebar entry per image with Overview, Drivers, Unattend and OS and Packages tabs. Every save shows a diff first, keeps the file's format, makes a backup and can be undone. Driver lint runs inline. Enrollment: upload standard/engineer MCL packages, one per tenant/purpose, with archive and restore. Hardware coverage counts engineer as Win11.
115 lines
4.7 KiB
JavaScript
115 lines
4.7 KiB
JavaScript
/**
|
|
* 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 = '<div class="text-muted py-3">Building the preview...</div>';
|
|
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 };
|
|
})();
|