Add onboarding weather and template gradients
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile, web, pulse-signage-web) (push) Successful in 1m53s
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile.player, player, pulse-signage-player) (push) Successful in 34s

This commit is contained in:
2026-08-28 20:05:23 +01:00
parent aa07a78912
commit 3960931ebe
80 changed files with 12750 additions and 519 deletions
@@ -3,7 +3,7 @@ const TASK = {
title: 'Onboarding device prune',
category: 'cleanup',
trigger: 'scheduled recurring task, hourly',
purpose: 'remove stale onboarding device bindings that have been idle for more than one minute.',
purpose: 'remove unbound onboarding devices that have been idle for more than one minute.',
taskType: 'recurring-run',
intervalMs: 60 * 60 * 1000
};
+11 -1
View File
@@ -25,7 +25,17 @@ function enrichScreensWithConnections(screens, connectionsBySlug, onboardingName
function buildClientRows(screens, connectionsBySlug, onboardingNameBySlug, onboardingNameByDeviceId, playerIdentifierByBaseUrl, formatDashboardDate) {
return (screens || []).flatMap(function (screen) {
const connectionState = connectionsBySlug[screen.slug] || { count: 0, connections: [] };
return (connectionState.connections || []).map(function (connection) {
const seenClientKeys = new Set();
return (connectionState.connections || []).filter(function (connection) {
const deviceId = String(connection && connection.deviceId || '').trim();
const clientId = String(connection && connection.clientId || '').trim();
const key = deviceId && clientId ? `${deviceId}:${clientId}` : String(connection && connection.id || '').trim();
if (seenClientKeys.has(key)) {
return false;
}
seenClientKeys.add(key);
return true;
}).map(function (connection) {
const deviceId = String(connection.deviceId || '').trim();
const playerBaseUrl = normalizePlayerBaseUrl(connection.playerPublicBaseUrl);
return Object.assign({}, connection, {
@@ -186,6 +186,7 @@ function buildThumbnailPreviewPayload(slide, options) {
canvasWidth: canvasSize.width,
canvasHeight: canvasSize.height,
backgroundColor: template && template.background_color ? String(template.background_color) : '#111111',
backgroundGradient: template && template.background_gradient ? String(template.background_gradient) : '',
backgroundImagePath: template && template.background_image_path ? String(template.background_image_path) : '',
fontStylesheetHref: String(options && options.fontStylesheetHref || '').trim(),
html: buildThumbnailPreviewMarkup(slide, options && options.baseUrl)
+1
View File
@@ -311,6 +311,7 @@ function buildThumbnailPreviewPayload(slide, options) {
canvasWidth: canvasSize.width,
canvasHeight: canvasSize.height,
backgroundColor: template && template.background_color ? String(template.background_color) : '#111111',
backgroundGradient: template && template.background_gradient ? String(template.background_gradient) : '',
backgroundImagePath: template && template.background_image_path ? String(template.background_image_path) : '',
fontStylesheetHref: String(options && options.fontStylesheetHref || '').trim(),
html: buildThumbnailPreviewMarkup(slide, options && options.baseUrl, options)
+38 -22
View File
@@ -216,35 +216,51 @@ function createPlayerActionService(options) {
}
async function forwardAnnouncementRefresh(slug) {
const authHeaders = createRequestAuthHeaders({
method: 'POST',
pathname: `/api/screens/${encodeURIComponent(slug)}/announcements/refresh`,
body: { command: 'announcement-refresh' }
});
const resolvedPlayerInternalBaseUrl = await getPlayerInternalBaseUrl();
if (!resolvedPlayerInternalBaseUrl) {
const targetBaseUrls = Array.from(new Set([
resolvedPlayerInternalBaseUrl,
configuredBridgeInternalBaseUrl
].map(normalizeBaseUrl).filter(Boolean)));
if (!targetBaseUrls.length) {
throw new Error('Unable to resolve the player internal base URL.');
}
const response = await fetch(`${resolvedPlayerInternalBaseUrl}/api/screens/${encodeURIComponent(slug)}/announcements/refresh`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
...authHeaders
},
body: JSON.stringify({ command: 'announcement-refresh' })
});
const results = await Promise.allSettled(targetBaseUrls.map(async function (targetBaseUrl) {
const authHeaders = createRequestAuthHeaders({
method: 'POST',
pathname: `/api/screens/${encodeURIComponent(slug)}/announcements/refresh`,
body: { command: 'announcement-refresh' }
});
const response = await fetch(`${targetBaseUrl}/api/screens/${encodeURIComponent(slug)}/announcements/refresh`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
...authHeaders
},
body: JSON.stringify({ command: 'announcement-refresh' })
});
if (!response.ok) {
const errorText = await response.text().catch(function () { return ''; });
const error = new Error(errorText || `Unable to refresh announcements for player ${slug}.`);
error.statusCode = response.status;
throw error;
if (!response.ok) {
const errorText = await response.text().catch(function () { return ''; });
const error = new Error(errorText || `Unable to refresh announcements for player ${slug}.`);
error.statusCode = response.status;
throw error;
}
return response.json().catch(function () {
return { ok: true };
});
}));
const successfulResults = results.filter(function (result) {
return result.status === 'fulfilled';
});
if (!successfulResults.length) {
throw (results[0] && results[0].reason) || new Error(`Unable to refresh announcements for player ${slug}.`);
}
return response.json().catch(function () {
return { ok: true };
return successfulResults.map(function (result) {
return result.value;
});
}