Release 2.16.1
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile, web, pulse-signage-web) (push) Successful in 1m14s
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile.player, player, pulse-signage-player) (push) Successful in 31s

This commit is contained in:
lzstealth committed 2026-10-21 00:39:51 +01:00
1 parent 3514d514a3
commit 8ce8228091
25 files changed
+1307 -613

No files matched your search

+182 -17
View File
@@ -12,9 +12,188 @@
var panels = Array.prototype.slice.call(form.querySelectorAll('[data-api-source-auth-panel]'));
var bearerTokenInput = form.querySelector('[data-api-source-bearer-token-input]');
var bearerTokenToggle = form.querySelector('[data-api-source-bearer-token-toggle]');
var requestMethodSelect = form.querySelector('[data-api-source-request-method]');
var requestSection = form.querySelector('[data-api-source-request-section]');
var requestBodyInput = form.querySelector('[data-api-source-request-body]');
var testButton = form.querySelector('[data-api-auth-test-url]') || form.querySelector('[data-api-source-test-url]');
var lastTestButton = testButton;
var isTesting = false;
var requestList = form.querySelector('[data-api-request-list]');
var requestTemplate = form.querySelector('[data-api-request-template]');
var addRequestButton = form.querySelector('[data-api-request-add]');
var diagnostics = form.querySelector('[data-api-source-diagnostics]');
var testStatus = form.querySelector('[data-api-source-test-status]');
var testResults = form.querySelector('[data-api-source-test-results]');
var copyReportButton = form.querySelector('[data-api-source-copy-report]');
var dismissDiagnosticsButton = form.querySelector('[data-api-source-dismiss-diagnostics]');
var diagnosticReport = null;
function addText(parent, tag, text, className) {
var element = document.createElement(tag);
element.textContent = text;
if (className) element.className = className;
parent.appendChild(element);
return element;
}
function addPreview(parent, title, text) {
var details = document.createElement('details');
details.className = 'mb-3';
addText(details, 'summary', title);
var preview = addText(details, 'pre', text, 'small bg-body-tertiary border rounded p-2 mt-2 mb-0');
preview.style.cssText = 'white-space:pre-wrap;overflow-wrap:anywhere;max-height:24rem;overflow:auto;';
parent.appendChild(details);
}
function showReport(report) {
testResults.replaceChildren();
testStatus.textContent = report.ok ? 'Connection test passed. No changes saved.' : 'Connection test failed. No changes saved.';
testStatus.className = report.ok ? 'mb-2 text-success' : 'mb-2 text-danger';
if (report.error) addText(testResults, 'p', report.error, 'text-break');
(report.steps || []).forEach(function (step) {
var block = document.createElement('div');
block.className = 'border-top py-2';
addText(block, 'h4', step.stage + ': ' + (step.skipped ? 'Not required' : step.ok ? 'Passed' : 'Failed'), 'h6');
var metadata = [step.method, step.url, step.status ? 'HTTP ' + step.status : '', step.contentType, step.durationMs !== undefined ? step.durationMs + ' ms' : ''].filter(Boolean);
if (metadata.length) addText(block, 'p', metadata.join(' | '), 'small text-break mb-1');
if (step.error || step.message) addText(block, 'p', step.error || step.message, 'text-break mb-1');
if (step.response) addPreview(block, 'Sanitised response', step.response);
testResults.appendChild(block);
});
if (report.preview) addPreview(testResults, 'JSON preview (up to 12,000 characters)', report.preview);
if (report.paths && report.paths.length) {
addPreview(testResults, 'Available field paths (sample)', report.paths.join('\n'));
}
}
function readRequest(card) {
var request = { id: card.getAttribute('data-request-id') || null };
Array.prototype.forEach.call(card.querySelectorAll('[data-request-field]'), function (input) {
request[input.getAttribute('data-request-field')] = input.type === 'checkbox' ? input.checked : input.value;
});
return request;
}
function serializeRequests() {
return Array.prototype.map.call(requestList.querySelectorAll('[data-api-request-card]'), readRequest);
}
function updateRequestCard(card) {
var fields = readRequest(card);
card.querySelector('[data-api-request-title]').textContent = 'Request - ' + (fields.name || 'Unnamed request');
card.querySelector('[data-api-request-body-section]').hidden = fields.request_method !== 'POST';
}
function updateRequestFromEvent(event) {
var card = event.target.closest('[data-api-request-card]');
if (card) updateRequestCard(card);
}
if (requestList) {
form.addEventListener('formdata', function (event) {
event.formData.set('requests_json', JSON.stringify(serializeRequests()));
});
requestList.addEventListener('input', updateRequestFromEvent);
requestList.addEventListener('change', updateRequestFromEvent);
requestList.addEventListener('click', function (event) {
var remove = event.target.closest('[data-api-request-remove]');
if (remove && !remove.disabled) {
var card = remove.closest('[data-api-request-card]');
if (requestList.querySelectorAll('[data-api-request-card]').length === 1) return;
var requestName = card.querySelector('[data-request-field="name"]').value || 'Unnamed request';
if (!window.confirm('Delete request "' + requestName + '"? This takes effect when you save the API source.')) return;
if (lastTestButton && lastTestButton.closest && lastTestButton.closest('[data-api-request-card]') === card) {
diagnostics.hidden = true;
form.appendChild(diagnostics);
lastTestButton = testButton;
}
card.remove();
addRequestButton.focus();
}
var button = event.target.closest('[data-api-source-test-url]');
if (button && button !== testButton) testConnection({ currentTarget: button });
updateRemoveButtons();
});
addRequestButton.addEventListener('click', function () {
if (requestList.querySelectorAll('[data-api-request-card]').length >= 50) return;
requestList.appendChild(requestTemplate.content.cloneNode(true));
var cards = requestList.querySelectorAll('[data-api-request-card]');
var input = cards[cards.length - 1].querySelector('[data-request-field="name"]');
var names = Array.prototype.map.call(cards, function (card) { return card.querySelector('[data-request-field="name"]').value.toLowerCase(); });
var suffix = cards.length;
while (names.indexOf('request ' + suffix) !== -1) suffix += 1;
input.value = 'Request ' + suffix;
updateRequestCard(cards[cards.length - 1]);
updateRemoveButtons();
input.focus();
input.select();
});
updateRemoveButtons();
}
function updateRemoveButtons() {
if (!requestList) return;
var cards = requestList.querySelectorAll('[data-api-request-card]');
addRequestButton.disabled = cards.length >= 50;
Array.prototype.forEach.call(cards, function (card) {
var button = card.querySelector('[data-api-request-remove]');
if (!button.hasAttribute('data-request-protected')) {
button.disabled = cards.length <= 1;
}
});
}
async function testConnection(event) {
if (isTesting) return;
isTesting = true;
var activeButton = event && event.currentTarget || testButton;
lastTestButton = activeButton;
var card = activeButton.closest ? activeButton.closest('[data-api-request-card]') : null;
var anchor = card || (authCard && !authCard.hidden ? authCard : activeButton.closest && activeButton.closest('.card'));
if (anchor) anchor.after(diagnostics);
diagnostics.hidden = false;
diagnostics.setAttribute('aria-busy', 'true');
activeButton.disabled = true;
copyReportButton.disabled = true;
diagnosticReport = null;
testResults.replaceChildren();
testStatus.className = 'mb-2';
testStatus.textContent = 'Testing connection...';
try {
var payload = Object.fromEntries(new FormData(form));
if (card) Object.assign(payload, readRequest(card));
var response = await fetch(activeButton.getAttribute('data-api-auth-test-url') || activeButton.getAttribute('data-api-source-test-url'), {
method: 'POST', credentials: 'same-origin',
headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' },
body: JSON.stringify(payload)
});
if (!(response.headers.get('content-type') || '').includes('application/json')) {
throw new Error('Connection test unavailable (HTTP ' + response.status + '). Check your session and permissions.');
}
diagnosticReport = await response.json();
showReport(diagnosticReport);
copyReportButton.disabled = false;
} catch (error) {
testStatus.className = 'mb-2 text-danger';
testStatus.textContent = error.message || 'Unable to run connection test.';
} finally {
activeButton.disabled = false;
isTesting = false;
diagnostics.setAttribute('aria-busy', 'false');
}
}
if (testButton) testButton.addEventListener('click', testConnection);
if (dismissDiagnosticsButton) dismissDiagnosticsButton.addEventListener('click', function () {
diagnostics.hidden = true;
if (lastTestButton && lastTestButton.isConnected !== false) lastTestButton.focus();
});
if (copyReportButton) copyReportButton.addEventListener('click', async function () {
if (!diagnosticReport) return;
try {
await navigator.clipboard.writeText(JSON.stringify(diagnosticReport, null, 2));
testStatus.textContent = 'Diagnostic report copied.';
} catch (_error) {
testStatus.textContent = 'Clipboard unavailable. Select the diagnostic results to copy them.';
}
});
function updateBearerTokenToggle() {
if (!bearerTokenInput || !bearerTokenToggle) {
@@ -61,30 +240,16 @@
});
}
function updateRequestBodyVisibility() {
if (!requestMethodSelect || !requestSection || !requestBodyInput) {
return;
}
var isPost = String(requestMethodSelect.value || 'GET').toUpperCase() === 'POST';
requestSection.hidden = !isPost;
}
if (methodSelect) {
methodSelect.addEventListener('change', function () {
updatePanels(true);
});
}
if (requestMethodSelect) {
requestMethodSelect.addEventListener('change', updateRequestBodyVisibility);
}
if (bearerTokenToggle && bearerTokenInput) {
bearerTokenToggle.addEventListener('click', toggleBearerTokenVisibility);
updateBearerTokenToggle();
}
updatePanels(false);
updateRequestBodyVisibility();
}());