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

This commit is contained in:
2026-09-04 15:43:55 +01:00
parent 2c150b5b2e
commit 98f969ca0f
104 changed files with 3559 additions and 447 deletions
+85 -7
View File
@@ -72,9 +72,13 @@
var allBlackout = hasClients && clients.every(function (client) {
return Boolean(client && client.blackout);
});
var isClientsMobile = Boolean(document.querySelector('.clients-screen-command-card'));
var useShortMobileLabels = isClientsMobile && window.innerWidth < 768;
if (action === 'pause') {
var pauseLabel = allPaused ? 'Resume ' + (isAllScreens ? 'all clients' : 'screen') : 'Pause ' + (isAllScreens ? 'all clients' : 'screen');
var pauseLabel = useShortMobileLabels
? (allPaused ? 'Resume' : 'Pause')
: (allPaused ? 'Resume ' + (isAllScreens ? 'all clients' : 'screen') : 'Pause ' + (isAllScreens ? 'all clients' : 'screen'));
var pauseConfirm = allPaused ? 'Resume ' + (isAllScreens ? 'all connected clients' : selectedLabel) + '?' : 'Pause ' + (isAllScreens ? 'all connected clients' : selectedLabel) + '?';
var pauseIcon = allPaused ? 'bi-play-fill' : 'bi-pause-fill';
button.innerHTML = '<i class="bi ' + pauseIcon + ' me-1" aria-hidden="true"></i>' + escapeHtml(pauseLabel);
@@ -92,7 +96,9 @@
}
if (action === 'blackout') {
var blackoutLabel = allBlackout ? 'Restore ' + (isAllScreens ? 'all clients' : 'screen') : 'Blackout ' + (isAllScreens ? 'all clients' : 'screen');
var blackoutLabel = useShortMobileLabels
? (allBlackout ? 'Restore' : 'Blackout')
: (allBlackout ? 'Restore ' + (isAllScreens ? 'all clients' : 'screen') : 'Blackout ' + (isAllScreens ? 'all clients' : 'screen'));
var blackoutConfirm = allBlackout ? 'Restore ' + (isAllScreens ? 'all connected clients' : selectedLabel) + '?' : 'Blackout ' + (isAllScreens ? 'all connected clients' : selectedLabel) + '?';
var blackoutIcon = allBlackout ? 'bi-eye' : 'bi-eye-slash';
button.innerHTML = '<i class="bi ' + blackoutIcon + ' me-1" aria-hidden="true"></i>' + escapeHtml(blackoutLabel);
@@ -147,6 +153,9 @@
}
if (button) {
if (Boolean(document.querySelector('.clients-screen-command-card')) && window.innerWidth < 768) {
button.innerHTML = '<i class="bi bi-arrow-repeat me-1" aria-hidden="true"></i>Reload';
}
button.setAttribute('aria-label', selectedName ? selectedName : 'Selected screen group');
}
});
@@ -372,7 +381,7 @@
var connectionId = String(row.getAttribute('data-client-client-id') || row.getAttribute('data-client-id') || '').trim();
var clientId = String(row.getAttribute('data-client-client-id') || '').trim();
var playerBaseUrl = String(row.getAttribute('data-client-player-base-url') || '').trim();
var clientNameCell = row.querySelector('td[data-label="Client"] > div');
var clientNameCell = row.querySelector('td[data-label="Client"] > div, [data-mobile-client-name]');
var clientName = String(clientNameCell && clientNameCell.textContent || '').trim();
var options = Array.prototype.slice.call(elements.targetSelect.options || []);
@@ -826,7 +835,7 @@
if (typeof elements.modal.addEventListener === 'function') {
elements.modal.addEventListener('show.bs.modal', function (event) {
var trigger = event && event.relatedTarget ? event.relatedTarget : null;
var row = trigger && trigger.closest ? trigger.closest('tr[data-client-key]') : null;
var row = trigger && trigger.closest ? trigger.closest('tr[data-client-key], article[data-mobile-client-key]') : null;
if (row) {
updateClientMoveModalFromRow(row);
}
@@ -843,7 +852,7 @@
event.preventDefault();
}
var row = moveButton.closest ? moveButton.closest('tr[data-client-key]') : null;
var row = moveButton.closest ? moveButton.closest('tr[data-client-key], article[data-mobile-client-key]') : null;
if (!row) {
return;
}
@@ -1038,6 +1047,60 @@
blackoutButton.setAttribute('aria-label', label);
}
function updateMobileClientCards(state) {
document.querySelectorAll('[data-mobile-client-id]').forEach(function (card) {
var id = String(card.getAttribute('data-mobile-client-id') || '').trim();
var clientId = String(card.getAttribute('data-mobile-client-client-id') || '').trim();
var client = (state.clients || []).find(function (candidate) {
return String(candidate.id || '').trim() === id || String(candidate.clientId || '').trim() === clientId;
});
if (!client) {
return;
}
var paused = Boolean(client.paused);
var blackout = Boolean(client.blackout);
var badge = card.querySelector('.badge');
var heading = card.querySelector('h4');
var details = card.querySelectorAll('.clients-mobile-client-details strong');
var pauseButton = card.querySelector('button[type="submit"].btn-info');
var blackoutButton = card.querySelector('button[type="submit"].btn-secondary, button[type="submit"].btn-success');
card.setAttribute('data-client-screen-slug', client.screen_slug || '');
card.setAttribute('data-client-client-id', client.clientId || client.id || '');
card.setAttribute('data-client-player-base-url', client.player_url || '');
card.classList.toggle('card-warning', paused);
card.classList.toggle('card-primary', !paused);
if (badge) {
badge.className = 'badge ' + (blackout ? 'text-bg-secondary' : paused ? 'text-bg-warning' : 'text-bg-success');
badge.textContent = blackout ? 'Blackout' : paused ? 'Paused' : 'Live';
}
if (heading) {
heading.textContent = client.client_name || 'Unknown';
}
if (details[0]) {
details[0].textContent = client.screen_name || client.screen_slug || 'Unknown';
}
if (details[1]) {
details[1].textContent = client.currentSlideTitle || 'No slide currently showing';
}
if (pauseButton) {
pauseButton.innerHTML = '<i class="bi ' + (paused ? 'bi-play-fill' : 'bi-pause-fill') + ' me-1" aria-hidden="true"></i>' + (paused ? 'Resume' : 'Pause');
}
if (blackoutButton) {
blackoutButton.innerHTML = '<i class="bi ' + (blackout ? 'bi-eye' : 'bi-eye-slash') + ' me-1" aria-hidden="true"></i>' + (blackout ? 'Restore' : 'Blackout');
blackoutButton.className = 'btn btn-sm ' + (blackout ? 'btn-success' : 'btn-secondary') + ' w-100';
}
card.querySelectorAll('form').forEach(function (form) {
var connectionInput = form.querySelector('input[name="connectionId"]');
var baseUrlInput = form.querySelector('input[name="playerBaseUrl"]');
if (connectionInput) connectionInput.value = client.clientId || client.id || '';
if (baseUrlInput) baseUrlInput.value = client.player_url || '';
form.action = '/clients/' + encodeURIComponent(client.screen_slug || '') + '/commands';
var blackoutInput = form.querySelector('input[name="blackout"]');
if (blackoutInput) blackoutInput.value = blackout ? 'false' : 'true';
});
});
}
function handleDashboardState(state) {
if (!state) {
return;
@@ -1047,6 +1110,7 @@
updateStats(state);
updateScreenGrid(state);
updateClientTable(state);
updateMobileClientCards(state);
updateKioskLauncherModal(state);
updateDashboardQuickActions(state);
updateScreenCommandControls();
@@ -1142,10 +1206,24 @@
return;
}
document.querySelectorAll('article[data-mobile-client-key] .clients-mobile-client-actions').forEach(function (actions) {
if (actions.querySelector('button[data-action="move-screen"]')) {
return;
}
var button = document.createElement('button');
button.type = 'button';
button.className = 'btn btn-sm btn-danger';
button.setAttribute('data-action', 'move-screen');
button.setAttribute('aria-label', 'Change screen');
button.setAttribute('title', 'Change screen');
button.innerHTML = '<i class="bi bi-display" aria-hidden="true"></i>';
actions.insertBefore(button, actions.children[1] || null);
});
if (typeof elements.modal.addEventListener === 'function') {
elements.modal.addEventListener('show.bs.modal', function (event) {
var trigger = event && event.relatedTarget ? event.relatedTarget : null;
var row = trigger && trigger.closest ? trigger.closest('tr[data-client-key]') : null;
var row = trigger && trigger.closest ? trigger.closest('tr[data-client-key], article[data-mobile-client-key]') : null;
if (row) {
updateClientMoveModalFromRow(row);
}
@@ -1162,7 +1240,7 @@
event.preventDefault();
}
var row = moveButton.closest ? moveButton.closest('tr[data-client-key]') : null;
var row = moveButton.closest ? moveButton.closest('tr[data-client-key], article[data-mobile-client-key]') : null;
if (!row) {
return;
}