Add onboarding weather and template gradients
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
// Admin client command routes for connected screens.
|
||||
|
||||
const { commitDeviceBinding } = require('#src/player/onboarding');
|
||||
const { createPageAuthToken } = require('#src/request-auth');
|
||||
|
||||
module.exports = function registerScreenCommandRoutes(app, deps) {
|
||||
const pool = deps.pool;
|
||||
@@ -227,7 +227,9 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
|
||||
}
|
||||
|
||||
if (command === 'setclientname') {
|
||||
const deviceId = String((req.body && (req.body.deviceId || req.body.clientId || req.body.connectionId)) || req.query.deviceId || req.query.clientId || req.query.connectionId || '').trim();
|
||||
const physicalDeviceId = String((req.body && req.body.deviceId) || req.query.deviceId || '').trim();
|
||||
const clientId = String((req.body && req.body.clientId) || req.query.clientId || '').trim();
|
||||
const deviceId = physicalDeviceId || clientId || String((req.body && req.body.connectionId) || req.query.connectionId || '').trim();
|
||||
const clientName = String((req.body && req.body.clientName) || req.query.clientName || '').trim();
|
||||
if (!deviceId) {
|
||||
return res.status(400).json({ error: 'Device ID is required' });
|
||||
@@ -236,13 +238,26 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
|
||||
return res.status(400).json({ error: 'Client name is required' });
|
||||
}
|
||||
|
||||
const [currentRows] = await pool.query(
|
||||
let bindingDeviceId = deviceId;
|
||||
let [currentRows] = await pool.query(
|
||||
`SELECT client_name
|
||||
FROM d_onboarding_devices
|
||||
WHERE device_id = ?
|
||||
LIMIT 1`,
|
||||
[deviceId]
|
||||
[clientId || deviceId]
|
||||
);
|
||||
if (!currentRows.length && clientId && physicalDeviceId) {
|
||||
bindingDeviceId = physicalDeviceId;
|
||||
[currentRows] = await pool.query(
|
||||
`SELECT client_name
|
||||
FROM d_onboarding_devices
|
||||
WHERE device_id = ?
|
||||
LIMIT 1`,
|
||||
[physicalDeviceId]
|
||||
);
|
||||
} else if (clientId) {
|
||||
bindingDeviceId = clientId;
|
||||
}
|
||||
const onboardingRow = currentRows[0] || null;
|
||||
const currentName = String(onboardingRow && onboardingRow.client_name ? onboardingRow.client_name : '').trim();
|
||||
if (currentName && currentName.toLowerCase() === clientName.toLowerCase()) {
|
||||
@@ -282,7 +297,7 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
|
||||
liveConnections = [];
|
||||
}
|
||||
|
||||
const available = await isClientNameAvailable(pool, clientName, deviceId, liveConnections);
|
||||
const available = await isClientNameAvailable(pool, clientName, bindingDeviceId, liveConnections);
|
||||
if (!available) {
|
||||
return res.status(409).json({ error: 'Client name already exists.' });
|
||||
}
|
||||
@@ -321,11 +336,10 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
|
||||
}
|
||||
|
||||
const [updateResult] = await pool.query(
|
||||
`UPDATE d_onboarding_devices pod
|
||||
JOIN d_screens s ON s.id = pod.screen_id
|
||||
SET pod.client_name = ?, pod.modified_at = CURRENT_TIMESTAMP
|
||||
WHERE s.slug = ? AND pod.device_id = ?`,
|
||||
[clientName, slug, deviceId]
|
||||
`UPDATE d_onboarding_devices
|
||||
SET client_name = ?, modified_at = CURRENT_TIMESTAMP
|
||||
WHERE device_id = ?`,
|
||||
[clientName, bindingDeviceId]
|
||||
);
|
||||
if (!updateResult.affectedRows) {
|
||||
return res.status(404).json({ error: 'Client not found' });
|
||||
@@ -344,24 +358,43 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
|
||||
}
|
||||
|
||||
if (command === 'moveclient') {
|
||||
const deviceId = String((req.body && (req.body.deviceId || req.body.clientId || req.body.connectionId)) || req.query.deviceId || req.query.clientId || req.query.connectionId || '').trim();
|
||||
const legacyDeviceId = String((req.body && req.body.deviceId) || req.query.deviceId || '').trim();
|
||||
let physicalPlayerId = '';
|
||||
const tabClientId = String((req.body && req.body.clientId) || req.query.clientId || '').trim();
|
||||
const clientName = String((req.body && req.body.clientName) || req.query.clientName || '').trim();
|
||||
const targetScreenSlug = String((req.body && (req.body.targetScreenSlug || req.body.screenSlug)) || req.query.targetScreenSlug || req.query.screenSlug || '').trim();
|
||||
const submittedPlayerBaseUrl = normalizeExplicitPlayerBaseUrl(playerBaseUrl);
|
||||
|
||||
if (!deviceId) {
|
||||
return res.status(400).json({ error: 'Device ID is required' });
|
||||
if (!connectionId && !submittedPlayerBaseUrl && !legacyDeviceId) {
|
||||
return res.status(400).json({ error: 'Client connection is required' });
|
||||
}
|
||||
if (!targetScreenSlug) {
|
||||
return res.status(400).json({ error: 'Target screen is required' });
|
||||
}
|
||||
|
||||
if (submittedPlayerBaseUrl) {
|
||||
const [playerRows] = await pool.query(
|
||||
'SELECT identifier FROM d_players WHERE public_base_url = ? LIMIT 1',
|
||||
[submittedPlayerBaseUrl]
|
||||
);
|
||||
const registeredPlayerId = String(playerRows[0] && playerRows[0].identifier || '').trim();
|
||||
if (registeredPlayerId) {
|
||||
physicalPlayerId = registeredPlayerId;
|
||||
}
|
||||
}
|
||||
|
||||
physicalPlayerId = physicalPlayerId || legacyDeviceId;
|
||||
if (!physicalPlayerId) {
|
||||
return res.status(400).json({ error: 'Registered player identity is required' });
|
||||
}
|
||||
|
||||
const [currentRows] = await pool.query(
|
||||
`SELECT d.client_name, s.slug AS current_screen_slug
|
||||
FROM d_onboarding_devices d
|
||||
LEFT JOIN d_screens s ON s.id = d.screen_id
|
||||
WHERE d.device_id = ?
|
||||
LIMIT 1`,
|
||||
[deviceId]
|
||||
[physicalPlayerId]
|
||||
);
|
||||
const onboardingRow = currentRows[0] || null;
|
||||
const resolvedClientName = String(clientName || onboardingRow && onboardingRow.client_name || '').trim();
|
||||
@@ -371,13 +404,13 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
|
||||
return res.status(400).json({ error: 'Client name is required' });
|
||||
}
|
||||
|
||||
if (currentScreenSlug && currentScreenSlug === targetScreenSlug) {
|
||||
if (!connectionId && currentScreenSlug && currentScreenSlug === targetScreenSlug) {
|
||||
return res.json({
|
||||
screen: screenRows[0],
|
||||
screenSlug: slug,
|
||||
command: command,
|
||||
connectionId: connectionId || null,
|
||||
deviceId: deviceId,
|
||||
deviceId: physicalPlayerId,
|
||||
clientName: resolvedClientName,
|
||||
targetScreenSlug: targetScreenSlug,
|
||||
ok: true,
|
||||
@@ -385,19 +418,78 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof commitDeviceBinding !== 'function') {
|
||||
return res.status(500).json({ error: 'Client binding is unavailable.' });
|
||||
}
|
||||
|
||||
const liveConnections = await fetchScreenConnections(slug);
|
||||
|
||||
const status = await commitDeviceBinding(pool, deviceId, resolvedClientName, targetScreenSlug, isClientNameAvailable, liveConnections);
|
||||
let status = null;
|
||||
if (onboardingRow) {
|
||||
const [targetRows] = await pool.query('SELECT id, name, slug FROM d_screens WHERE slug = ? LIMIT 1', [targetScreenSlug]);
|
||||
if (!targetRows.length) {
|
||||
return res.status(404).json({ error: 'Target screen not found' });
|
||||
}
|
||||
if (tabClientId) {
|
||||
const [tabUpdateResult] = await pool.query(
|
||||
`UPDATE d_onboarding_devices
|
||||
SET client_name = ?, screen_id = ?, modified_at = CURRENT_TIMESTAMP
|
||||
WHERE device_id = ?`,
|
||||
[resolvedClientName, targetRows[0].id, tabClientId]
|
||||
);
|
||||
if (!tabUpdateResult.affectedRows) {
|
||||
await pool.query(
|
||||
`INSERT INTO d_onboarding_devices (device_id, client_name, screen_id)
|
||||
VALUES (?, ?, ?)`,
|
||||
[tabClientId, resolvedClientName, targetRows[0].id]
|
||||
);
|
||||
}
|
||||
} else {
|
||||
await pool.query(
|
||||
`UPDATE d_onboarding_devices
|
||||
SET screen_id = ?, modified_at = CURRENT_TIMESTAMP
|
||||
WHERE device_id = ?`,
|
||||
[targetRows[0].id, physicalPlayerId]
|
||||
);
|
||||
}
|
||||
const stablePlayerBaseUrl = normalizeExplicitPlayerBaseUrl(playerBaseUrl);
|
||||
if (stablePlayerBaseUrl && !tabClientId) {
|
||||
const [playerRows] = await pool.query(
|
||||
'SELECT identifier FROM d_players WHERE public_base_url = ? LIMIT 1',
|
||||
[stablePlayerBaseUrl]
|
||||
);
|
||||
const stablePlayer = playerRows[0] || null;
|
||||
if (stablePlayer && stablePlayer.identifier) {
|
||||
await pool.query(
|
||||
`UPDATE d_onboarding_devices
|
||||
SET client_name = ?, screen_id = ?, modified_at = CURRENT_TIMESTAMP
|
||||
WHERE device_id = ?`,
|
||||
[resolvedClientName, targetRows[0].id, stablePlayer.identifier]
|
||||
);
|
||||
await pool.query(
|
||||
`INSERT INTO d_onboarding_devices (device_id, client_name, screen_id)
|
||||
SELECT ?, ?, ?
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM d_onboarding_devices WHERE device_id = ?
|
||||
)`,
|
||||
[stablePlayer.identifier, resolvedClientName, targetRows[0].id, stablePlayer.identifier]
|
||||
);
|
||||
}
|
||||
}
|
||||
status = {
|
||||
client_name: resolvedClientName,
|
||||
screen_id: targetRows[0].id,
|
||||
screen_slug: targetScreenSlug
|
||||
};
|
||||
} else {
|
||||
status = {
|
||||
client_name: resolvedClientName,
|
||||
screen_id: null,
|
||||
screen_slug: targetScreenSlug,
|
||||
live_only: true
|
||||
};
|
||||
}
|
||||
let targetPlayerUrl = '';
|
||||
const liveConnection = Array.isArray(liveConnections)
|
||||
? liveConnections.find(function (connection) {
|
||||
const candidateConnectionId = String(connection && (connection.id || connection.clientId) || '').trim();
|
||||
const candidateDeviceId = String(connection && connection.deviceId || '').trim();
|
||||
return candidateConnectionId === connectionId || candidateConnectionId === deviceId || candidateDeviceId === deviceId;
|
||||
return candidateConnectionId === connectionId || candidateConnectionId === physicalPlayerId || candidateDeviceId === physicalPlayerId;
|
||||
}) || liveConnections[0] || null
|
||||
: null;
|
||||
const sourcePlayerBaseUrl = normalizeExplicitPlayerBaseUrl(playerBaseUrl || (liveConnection && liveConnection.playerPublicBaseUrl) || '');
|
||||
@@ -407,17 +499,24 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
|
||||
if (!targetPlayerUrl) {
|
||||
targetPlayerUrl = `/screen/${encodeURIComponent(targetScreenSlug)}`;
|
||||
}
|
||||
|
||||
const moveToken = createPageAuthToken({
|
||||
scope: 'screen-move',
|
||||
playerId: physicalPlayerId,
|
||||
connectionId: connectionId || null,
|
||||
screenSlug: targetScreenSlug
|
||||
});
|
||||
if (playerBaseUrl) {
|
||||
await forwardPlayerCommandForPlayerBaseUrl(slug, playerBaseUrl, {
|
||||
command: 'redirect',
|
||||
url: targetPlayerUrl
|
||||
}, connectionId || deviceId || undefined, deviceId || null);
|
||||
url: targetPlayerUrl,
|
||||
moveToken: moveToken || null
|
||||
}, connectionId || physicalPlayerId || undefined, physicalPlayerId || null);
|
||||
} else {
|
||||
await forwardPlayerCommandForConnections(slug, liveConnections, {
|
||||
command: 'redirect',
|
||||
url: targetPlayerUrl
|
||||
}, connectionId || deviceId || undefined, deviceId || null);
|
||||
url: targetPlayerUrl,
|
||||
moveToken: moveToken || null
|
||||
}, connectionId || physicalPlayerId || undefined, physicalPlayerId || null);
|
||||
}
|
||||
|
||||
if (typeof broadcastDashboardState === 'function') {
|
||||
@@ -429,7 +528,7 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
|
||||
screenSlug: slug,
|
||||
command: command,
|
||||
connectionId: connectionId || null,
|
||||
deviceId: deviceId,
|
||||
deviceId: physicalPlayerId,
|
||||
clientName: status ? status.client_name : resolvedClientName,
|
||||
targetScreenSlug: targetScreenSlug,
|
||||
playerUrl: targetPlayerUrl,
|
||||
|
||||
@@ -632,8 +632,8 @@ module.exports = function registerContentRoutes(app, deps) {
|
||||
}
|
||||
const actorId = getAuditUserId(req);
|
||||
const [result] = await pool.query(
|
||||
'INSERT INTO c_templates (name, canvas_size_id, background_image_path, background_color, created_by, modified_by) VALUES (?, ?, ?, ?, ?, ?)',
|
||||
[payload.name, payload.canvasSizeId, payload.backgroundImagePath, payload.backgroundColor, actorId, actorId]
|
||||
'INSERT INTO c_templates (name, canvas_size_id, background_image_path, background_color, background_gradient, created_by, modified_by) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
||||
[payload.name, payload.canvasSizeId, payload.backgroundImagePath, payload.backgroundColor, payload.backgroundGradient, actorId, actorId]
|
||||
);
|
||||
for (let i = 0; i < payload.regions.length; i += 1) {
|
||||
const region = payload.regions[i];
|
||||
@@ -707,8 +707,8 @@ module.exports = function registerContentRoutes(app, deps) {
|
||||
regions: normalizeTemplateRegionsForAudit(payload.regions)
|
||||
});
|
||||
await pool.query(
|
||||
'UPDATE c_templates SET name = ?, canvas_size_id = ?, background_image_path = ?, background_color = ?, modified_by = ? WHERE id = ?',
|
||||
[payload.name, payload.canvasSizeId, payload.backgroundImagePath, payload.backgroundColor, actorId, template.id]
|
||||
'UPDATE c_templates SET name = ?, canvas_size_id = ?, background_image_path = ?, background_color = ?, background_gradient = ?, modified_by = ? WHERE id = ?',
|
||||
[payload.name, payload.canvasSizeId, payload.backgroundImagePath, payload.backgroundColor, payload.backgroundGradient, actorId, template.id]
|
||||
);
|
||||
await pool.query('DELETE FROM c_template_regions WHERE template_id = ?', [template.id]);
|
||||
for (let i = 0; i < payload.regions.length; i += 1) {
|
||||
|
||||
@@ -4,6 +4,7 @@ const { buildPagination } = require('../../lib/pagination');
|
||||
const renderWeatherLocationsPage = require('./weather/list');
|
||||
const renderWeatherLocationAddPage = require('./weather/add');
|
||||
const renderWeatherLocationEditPage = require('./weather/edit');
|
||||
const { buildDuplicateWeatherLocationName, buildDuplicateWeatherLocation } = require('./weather/duplicate');
|
||||
const { fetchAppSettings } = require('../../../data/app-settings');
|
||||
|
||||
async function getWeatherLocationUsageIds(pool, common) {
|
||||
@@ -85,6 +86,26 @@ module.exports = function registerWeatherRoutes(app, deps) {
|
||||
} catch (error) { next(error); }
|
||||
});
|
||||
|
||||
app.get('/data-sources/weather/:id/duplicate', requirePermission('weather.read'), requirePermission('weather.create'), async function (req, res, next) {
|
||||
try {
|
||||
const location = await common.fetchWeatherLocationById(pool, Number(req.params.id));
|
||||
if (!location) return res.status(404).send('Weather location not found');
|
||||
|
||||
let duplicateName = buildDuplicateWeatherLocationName(location.name);
|
||||
let duplicateIndex = 2;
|
||||
while (await common.fetchDuplicateName(pool, 'i_weather_locations', duplicateName)) {
|
||||
duplicateName = buildDuplicateWeatherLocationName(location.name) + ' (' + duplicateIndex + ')';
|
||||
duplicateIndex += 1;
|
||||
}
|
||||
|
||||
res.send(renderWeatherLocationAddPage(buildDuplicateWeatherLocation(location, duplicateName), req.query.message ? String(req.query.message) : 'Review the copied values and save when ready.', req.currentUser, {
|
||||
providerAvailability: await getProviderAvailability(),
|
||||
messageVariant: 'info',
|
||||
showSaveSecondaryActions: true
|
||||
}));
|
||||
} catch (error) { next(error); }
|
||||
});
|
||||
|
||||
app.post('/data-sources/weather', requirePermission('weather.create'), async function (req, res, next) {
|
||||
const connection = await pool.getConnection();
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// Weather location duplication helpers.
|
||||
|
||||
function buildDuplicateWeatherLocationName(locationName) {
|
||||
return 'Copy of ' + String(locationName || '').trim();
|
||||
}
|
||||
|
||||
function buildDuplicateWeatherLocation(location, duplicateName) {
|
||||
return Object.assign({}, location, {
|
||||
id: null,
|
||||
name: duplicateName,
|
||||
last_pulled_at: null,
|
||||
last_pull_error: '',
|
||||
last_response_status: null,
|
||||
last_response_content_type: '',
|
||||
last_response_json: ''
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
buildDuplicateWeatherLocationName,
|
||||
buildDuplicateWeatherLocation
|
||||
};
|
||||
@@ -0,0 +1,98 @@
|
||||
const { createRequestAuthHeaders } = require('#src/request-auth');
|
||||
const { fetchPlayerRegistrations } = require('#src/data/player-registry');
|
||||
const { renderView } = require('../view');
|
||||
|
||||
module.exports = function registerOnboardingRoutes(app, deps) {
|
||||
const pool = deps.pool;
|
||||
const playerInternalBaseUrl = String(deps.playerInternalBaseUrl || '').replace(/\/$/, '');
|
||||
const requirePermission = deps.requirePermission;
|
||||
|
||||
app.get('/pairing', requirePermission('pairing.allow'), async function (req, res, next) {
|
||||
try {
|
||||
const [screens] = await pool.query('SELECT id, name, slug FROM d_screens ORDER BY name ASC, id ASC');
|
||||
res.send(renderView('onboarding/pair', {
|
||||
title: 'Pair player',
|
||||
active: 'pairing',
|
||||
currentUser: req.currentUser,
|
||||
clientId: String(req.query.clientId || '').trim(),
|
||||
pairingCode: String(req.query.code || '').trim(),
|
||||
screens: screens,
|
||||
scripts: ['vendor/jsqr/jsQR.js', 'js/onboarding/pair.js?v=' + encodeURIComponent(require('#root/package.json').version)]
|
||||
}));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/pairing', requirePermission('pairing.allow'), async function (req, res) {
|
||||
const pairingCode = String(req.body && req.body.pairingCode || '').trim();
|
||||
const clientId = String(req.body && req.body.clientId || '').trim();
|
||||
const clientName = String(req.body && req.body.clientName || '').trim();
|
||||
const screenSlug = String(req.body && req.body.screenSlug || '').trim();
|
||||
if (!pairingCode || !clientName || !screenSlug) {
|
||||
return res.status(400).json({ error: 'All pairing fields are required.' });
|
||||
}
|
||||
|
||||
try {
|
||||
let targetBaseUrl = playerInternalBaseUrl;
|
||||
let deviceId = '';
|
||||
let resolvedClientId = clientId;
|
||||
{
|
||||
const resolvePath = '/api/onboarding/resolve?pairingCode=' + encodeURIComponent(pairingCode);
|
||||
const resolveAuthHeaders = createRequestAuthHeaders({ method: 'GET', pathname: '/api/onboarding/resolve' });
|
||||
const resolveResponse = await fetch(`${targetBaseUrl}${resolvePath}`, {
|
||||
method: 'GET',
|
||||
headers: Object.assign({ Accept: 'application/json' }, resolveAuthHeaders)
|
||||
});
|
||||
const resolveBody = await resolveResponse.text();
|
||||
let resolved = null;
|
||||
try { resolved = JSON.parse(resolveBody); } catch (_error) {}
|
||||
if (!resolveResponse.ok || !resolved || !resolved.deviceId) {
|
||||
const registrations = await fetchPlayerRegistrations(pool);
|
||||
const remoteRegistrations = registrations.filter(function (player) {
|
||||
const internalUrl = String(player && player.internal_base_url || '').replace(/\/$/, '');
|
||||
return internalUrl && internalUrl !== playerInternalBaseUrl;
|
||||
});
|
||||
if (!remoteRegistrations.length) {
|
||||
return res.status(resolveResponse.status || 401).type(resolveResponse.headers.get('content-type') || 'application/json').send(resolveBody || JSON.stringify({ error: 'Unable to resolve kiosk pairing code.' }));
|
||||
}
|
||||
for (const remoteRegistration of remoteRegistrations) {
|
||||
targetBaseUrl = String(remoteRegistration.internal_base_url).replace(/\/$/, '');
|
||||
const bridgeResolveHeaders = createRequestAuthHeaders({ method: 'GET', pathname: '/api/onboarding/resolve' });
|
||||
const bridgeResolveResponse = await fetch(`${targetBaseUrl}/api/onboarding/resolve?pairingCode=${encodeURIComponent(pairingCode)}`, {
|
||||
method: 'GET',
|
||||
headers: Object.assign({ Accept: 'application/json' }, bridgeResolveHeaders)
|
||||
});
|
||||
const bridgeResolveBody = await bridgeResolveResponse.text();
|
||||
try { resolved = JSON.parse(bridgeResolveBody); } catch (_error) { resolved = null; }
|
||||
if (bridgeResolveResponse.ok && resolved && resolved.deviceId) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!resolved || !resolved.deviceId) {
|
||||
return res.status(401).json({ error: 'A valid kiosk pairing code is required.' });
|
||||
}
|
||||
}
|
||||
if (resolved && resolved.deviceId) {
|
||||
deviceId = String(resolved.deviceId).trim();
|
||||
resolvedClientId = String(resolved.clientId || resolvedClientId || '').trim();
|
||||
}
|
||||
}
|
||||
|
||||
if (!resolvedClientId) {
|
||||
return res.status(400).json({ error: 'Client ID is required.' });
|
||||
}
|
||||
const payload = { clientId: resolvedClientId, pairingCode: pairingCode, clientName: clientName, screenSlug: screenSlug };
|
||||
const authHeaders = createRequestAuthHeaders({ method: 'POST', pathname: '/api/onboarding', body: payload });
|
||||
const response = await fetch(`${targetBaseUrl}/api/onboarding`, {
|
||||
method: 'POST',
|
||||
headers: Object.assign({ 'Content-Type': 'application/json', Accept: 'application/json' }, authHeaders),
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
const responseBody = await response.text();
|
||||
res.status(response.status).type(response.headers.get('content-type') || 'application/json').send(responseBody);
|
||||
} catch (error) {
|
||||
res.status(502).json({ error: error && error.message ? error.message : 'Player unavailable.' });
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -21,11 +21,17 @@ const registerAuditLogRoutes = require('./settings/audit-log');
|
||||
const registerAboutRoutes = require('./settings/about/routes');
|
||||
const registerInternalSyncRoutes = require('./internal/sync');
|
||||
const registerScreensRoutes = require('./signage/screens/routes');
|
||||
const registerOnboardingRoutes = require('./onboarding');
|
||||
const { PERMISSIONS, normalizePermissionKeys } = require('#src/rbac');
|
||||
|
||||
function registerRoutes(app, deps) {
|
||||
// Group routes by the dependency bundle they need.
|
||||
registerAuthAndAccountRoutes(app, deps);
|
||||
registerOnboardingRoutes(app, {
|
||||
pool: deps.pool,
|
||||
playerInternalBaseUrl: deps.playerInternalBaseUrl,
|
||||
requirePermission: deps.requirePermission
|
||||
});
|
||||
registerSignageRoutes(app, deps);
|
||||
registerSettingsAndContentRoutes(app, deps);
|
||||
registerSettingsPageRoutes(app, {
|
||||
|
||||
@@ -32,6 +32,21 @@ module.exports = function registerClientsRoutes(app, deps) {
|
||||
return compareSortValues(getComparableSortValue(leftValue), getComparableSortValue(rightValue));
|
||||
}
|
||||
|
||||
function compareClientNames(leftValue, rightValue) {
|
||||
const leftName = String(leftValue || '').trim();
|
||||
const rightName = String(rightValue || '').trim();
|
||||
if (!leftName && !rightName) {
|
||||
return 0;
|
||||
}
|
||||
if (!leftName) {
|
||||
return 1;
|
||||
}
|
||||
if (!rightName) {
|
||||
return -1;
|
||||
}
|
||||
return leftName.localeCompare(rightName, undefined, { sensitivity: 'base', numeric: true });
|
||||
}
|
||||
|
||||
const sortKeys = accessors[normalizedSortKey]
|
||||
? [normalizedSortKey]
|
||||
: ['client'];
|
||||
@@ -39,7 +54,9 @@ module.exports = function registerClientsRoutes(app, deps) {
|
||||
return (Array.isArray(clients) ? clients.slice() : []).sort(function (leftClient, rightClient) {
|
||||
for (let index = 0; index < sortKeys.length; index += 1) {
|
||||
const sortKeyName = sortKeys[index];
|
||||
const comparison = compareValues(accessors[sortKeyName](leftClient), accessors[sortKeyName](rightClient));
|
||||
const comparison = sortKeyName === 'client'
|
||||
? compareClientNames(accessors[sortKeyName](leftClient), accessors[sortKeyName](rightClient))
|
||||
: compareValues(accessors[sortKeyName](leftClient), accessors[sortKeyName](rightClient));
|
||||
|
||||
if (comparison !== 0) {
|
||||
return index === 0 && normalizedDirection === 'desc' ? comparison * -1 : comparison;
|
||||
|
||||
@@ -2,17 +2,7 @@
|
||||
|
||||
const { renderView } = require('../../../view');
|
||||
|
||||
function normalizeBaseUrl(value) {
|
||||
return String(value || '').trim().replace(/\/$/, '');
|
||||
}
|
||||
|
||||
module.exports = function renderDashboardPage(data, message, currentUser) {
|
||||
const primaryPlayerUrl = Array.isArray(data.screens)
|
||||
? String((data.screens.find(function (screen) {
|
||||
return screen && String(screen.public_base_url || '').trim();
|
||||
}) || {}).public_base_url || '').trim()
|
||||
: '';
|
||||
|
||||
return renderView('dashboard/index', {
|
||||
title: 'Dashboard',
|
||||
active: 'dashboard',
|
||||
@@ -25,7 +15,6 @@ module.exports = function renderDashboardPage(data, message, currentUser) {
|
||||
slides: data.slides || [],
|
||||
connectedClientsCount: Number(data.connectedClientsCount || 0),
|
||||
connectedPlayersCount: Number(data.connectedPlayersCount || data.connectedClientsCount || 0),
|
||||
primaryPlayerUrl: normalizeBaseUrl(primaryPlayerUrl) || null,
|
||||
scripts: ['js/dashboard/dashboard-page.js']
|
||||
});
|
||||
};
|
||||
|
||||
@@ -26,6 +26,7 @@ function buildDuplicateTemplate(template, duplicateName) {
|
||||
canvas_size_height: source.canvas_size_height,
|
||||
background_color: source.background_color,
|
||||
background_image_path: source.background_image_path,
|
||||
background_gradient: source.background_gradient,
|
||||
region_usage: [],
|
||||
regions: Array.isArray(source.regions) ? source.regions.map(cloneRegion) : []
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user