(function () { var webUiHelpers = window.webUiHelpers || {}; var escapeHtml = webUiHelpers.escapeHtml; var formatDashboardDate = webUiHelpers.formatDashboardDate; var getClientRowKey = webUiHelpers.getClientRowKey; var getClientDisplayName = webUiHelpers.getClientDisplayName; var setButtonVariant = webUiHelpers.setButtonVariant; var normalizeDisplayIp = webUiHelpers.normalizeDisplayIp; function renderClientActionCell(client) { var paused = Boolean(client.paused); var pauseButtonClass = 'btn btn-sm btn-info'; var pauseButtonIcon = paused ? 'bi-play-fill' : 'bi-pause-fill'; var pauseButtonLabel = paused ? 'Resume' : 'Pause'; var blackout = Boolean(client.blackout); var blackoutButtonClass = 'btn btn-sm ' + (blackout ? 'btn-success' : 'btn-secondary'); var blackoutButtonIcon = blackout ? 'bi-eye' : 'bi-eye-slash'; var blackoutButtonLabel = blackout ? 'Restore' : 'Blackout'; var reloadConfirmMessage = 'Reloading will restart the player page. Continue?'; var blackoutCommandValue = blackout ? 'false' : 'true'; return '
'; } function updateClientActionCell(cell, client) { if (!cell) { return; } var pauseButton = cell.querySelector('button[data-action="pause"]'); if (!pauseButton) { cell.innerHTML = renderClientActionCell(client); return; } var paused = Boolean(client.paused); setButtonVariant(pauseButton, ['btn-secondary', 'btn-outline-primary'], 'btn-info'); pauseButton.innerHTML = '' + (paused ? 'Resume' : 'Pause'); var pauseForm = pauseButton.form; if (pauseForm) { var commandInput = pauseForm.querySelector('input[name="command"]'); if (commandInput) { commandInput.value = 'pause'; } var connectionInput = pauseForm.querySelector('input[name="connectionId"]'); if (connectionInput) { connectionInput.value = client.id || ''; } pauseForm.action = '/clients/' + encodeURIComponent(client.screen_slug) + '/commands'; } var reloadButton = cell.querySelector('button[data-action="reload"]'); if (reloadButton) { reloadButton.innerHTML = 'Reload'; setButtonVariant(reloadButton, ['btn-danger', 'btn-success', 'btn-outline-secondary', 'btn-outline-dark', 'btn-outline-primary', 'btn-secondary'], 'btn-danger'); var reloadForm = reloadButton.form; if (reloadForm) { var reloadInput = reloadForm.querySelector('input[name="connectionId"]'); if (reloadInput) { reloadInput.value = client.id || ''; } reloadForm.action = '/clients/' + encodeURIComponent(client.screen_slug) + '/commands'; reloadForm.setAttribute('data-confirm-message', 'Reloading will restart the player page. Continue?'); } } var blackoutButton = cell.querySelector('button[data-action="blackout"]'); if (!blackoutButton) { cell.innerHTML = renderClientActionCell(client); return; } var blackout = Boolean(client.blackout); var blackoutButtonIcon = blackout ? 'bi-eye' : 'bi-eye-slash'; setButtonVariant(blackoutButton, ['btn-success', 'btn-secondary'], blackout ? 'btn-success' : 'btn-secondary'); blackoutButton.innerHTML = '' + (blackout ? 'Restore' : 'Blackout'); var blackoutForm = blackoutButton.form; if (blackoutForm) { var blackoutCommandInput = blackoutForm.querySelector('input[name="command"]'); if (blackoutCommandInput) { blackoutCommandInput.value = 'blackout'; } var blackoutStateInput = blackoutForm.querySelector('input[name="blackout"]'); if (blackoutStateInput) { blackoutStateInput.value = blackout ? 'false' : 'true'; } var blackoutConnectionInput = blackoutForm.querySelector('input[name="connectionId"]'); if (blackoutConnectionInput) { blackoutConnectionInput.value = client.id || ''; } blackoutForm.action = '/clients/' + encodeURIComponent(client.screen_slug) + '/commands'; } var previousButton = cell.querySelector('button[data-action="previous"]'); if (!previousButton) { cell.innerHTML = renderClientActionCell(client); return; } setButtonVariant(previousButton, ['btn-outline-secondary', 'btn-success', 'btn-danger', 'btn-primary', 'btn-secondary', 'btn-warning'], 'btn-warning'); previousButton.setAttribute('aria-label', 'Previous slide'); var previousForm = previousButton.form; if (previousForm) { var previousCommandInput = previousForm.querySelector('input[name="command"]'); if (previousCommandInput) { previousCommandInput.value = 'previous'; } var previousConnectionInput = previousForm.querySelector('input[name="connectionId"]'); if (previousConnectionInput) { previousConnectionInput.value = client.id || ''; } previousForm.action = '/clients/' + encodeURIComponent(client.screen_slug) + '/commands'; } var nextButton = cell.querySelector('button[data-action="next"]'); if (!nextButton) { cell.innerHTML = renderClientActionCell(client); return; } setButtonVariant(nextButton, ['btn-outline-secondary', 'btn-success', 'btn-danger', 'btn-primary', 'btn-secondary', 'btn-warning'], 'btn-warning'); nextButton.setAttribute('aria-label', 'Next slide'); var nextForm = nextButton.form; if (nextForm) { var nextCommandInput = nextForm.querySelector('input[name="command"]'); if (nextCommandInput) { nextCommandInput.value = 'next'; } var nextConnectionInput = nextForm.querySelector('input[name="connectionId"]'); if (nextConnectionInput) { nextConnectionInput.value = client.id || ''; } nextForm.action = '/clients/' + encodeURIComponent(client.screen_slug) + '/commands'; } } function syncClientActionCell(row, client, hasActionsColumn) { if (!row || !row.cells) { return; } if (!hasActionsColumn) { if (row.cells.length > 6) { row.deleteCell(row.cells.length - 1); } return; } var actionCell = row.cells.length > 6 ? row.cells[6] : null; if (!actionCell) { actionCell = row.insertCell(-1); actionCell.setAttribute('data-label', 'Actions'); actionCell.className = 'text-end'; } updateClientActionCell(actionCell, client); } function renderClientRow(client, hasActionsColumn) { var connectedAt = client.connectedAt ? '
' + escapeHtml(client.connectedAtLabel || formatDashboardDate(client.connectedAt) || client.connectedAt) + '
' + (client.lastSeenAt ? '
' + escapeHtml(client.lastSeenAtLabel || formatDashboardDate(client.lastSeenAt) || client.lastSeenAt) + '
' : '') : 'Unknown'; var clientIpValue = normalizeDisplayIp(client.clientIp); var clientIp = clientIpValue ? escapeHtml(clientIpValue) : 'Unknown'; var viewport = client.viewport && client.viewport.width && client.viewport.height ? escapeHtml(client.viewport.width + 'x' + client.viewport.height) : 'Unknown'; var clientNameValue = getClientDisplayName(client); var clientName = clientNameValue ? escapeHtml(clientNameValue) : 'Unknown'; var screenName = client.screen_name ? escapeHtml(client.screen_name) : 'Unknown'; var currentSlide = client.currentSlideTitle ? escapeHtml(client.currentSlideTitle) : 'No slide currently showing'; var actionCell = hasActionsColumn ? '' + renderClientActionCell(client) + '' : ''; return [ '', '
' + clientName + '
', '
' + screenName + '
', '' + currentSlide + '', '' + clientIp + '', '' + viewport + '', '' + connectedAt + '', actionCell, '' ].join(''); } function renderScreenRow(screen) { var clientCount = Number(screen.player_connection_count || 0); return [ '', '' + escapeHtml(screen.name) + '', '' + escapeHtml(screen.player_url || '') + '', '' + escapeHtml(screen.playlist_name || '') + '', '' + (clientCount ? '
' + clientCount + ' connected
' : 'No clients connected.') + '', '' ].join(''); } function updateStats(state) { var clientCount = document.getElementById('dashboard-client-count'); var screenCount = document.getElementById('dashboard-screen-count'); var slideCount = document.getElementById('dashboard-slide-count'); var playlistCount = document.getElementById('dashboard-playlist-count'); if (playlistCount && Array.isArray(state.playlists)) { playlistCount.textContent = String(state.playlists.length); } if (slideCount && Array.isArray(state.slides)) { slideCount.textContent = String(state.slides.length); } if (screenCount && Array.isArray(state.screens)) { screenCount.textContent = String(state.screens.length); } if (clientCount) { clientCount.textContent = String(Number(state.connectedClientsCount || 0)); } } function updateClientTable(state) { var tbody = document.getElementById('dashboard-clients-table-body'); if (!tbody || !Array.isArray(state.clients)) { return; } var table = document.getElementById('dashboard-clients-table'); var hasActionsColumn = Boolean(table && String(table.getAttribute('data-has-actions-column') || '').toLowerCase() === 'true'); if (!state.clients.length) { tbody.innerHTML = 'No connected clients yet.'; return; } var existingRows = {}; Array.prototype.slice.call(tbody.querySelectorAll('tr[data-client-key]')).forEach(function (row) { existingRows[row.getAttribute('data-client-key')] = row; }); Array.prototype.slice.call(tbody.querySelectorAll('tr')).forEach(function (row) { if (!row.hasAttribute('data-client-key')) { row.parentNode.removeChild(row); } }); state.clients.forEach(function (client, index) { var rowKey = getClientRowKey(client); var row = existingRows[rowKey]; if (!row) { var tempBody = document.createElement('tbody'); tempBody.innerHTML = renderClientRow(client, hasActionsColumn); row = tempBody.firstElementChild; } if (!row) { return; } row.setAttribute('data-client-key', rowKey); row.setAttribute('data-client-id', client.clientId || ''); row.setAttribute('data-client-device-id', client.deviceId || ''); row.setAttribute('data-client-screen-slug', client.screen_slug || ''); if (row.cells && row.cells.length >= 6) { var connectedAt = client.connectedAt ? '
' + escapeHtml(client.connectedAtLabel || formatDashboardDate(client.connectedAt) || client.connectedAt) + '
' + (client.lastSeenAt ? '
' + escapeHtml(client.lastSeenAtLabel || formatDashboardDate(client.lastSeenAt) || client.lastSeenAt) + '
' : '') : 'Unknown'; var clientIpValue = normalizeDisplayIp(client.clientIp); var clientIp = clientIpValue ? escapeHtml(clientIpValue) : 'Unknown'; var viewport = client.viewport && client.viewport.width && client.viewport.height ? escapeHtml(client.viewport.width + 'x' + client.viewport.height) : 'Unknown'; var clientNameValue = getClientDisplayName(client); var clientName = clientNameValue ? escapeHtml(clientNameValue) : 'Unknown'; var screenName = client.screen_name ? escapeHtml(client.screen_name) : 'Unknown'; var currentSlide = client.currentSlideTitle ? escapeHtml(client.currentSlideTitle) : 'No slide currently showing'; row.cells[0].innerHTML = '
' + clientName + '
'; row.cells[1].innerHTML = '
' + screenName + '
'; row.cells[2].innerHTML = currentSlide; row.cells[3].innerHTML = clientIp; row.cells[4].innerHTML = viewport; row.cells[5].innerHTML = connectedAt; syncClientActionCell(row, client, hasActionsColumn); } var referenceNode = tbody.children[index] || null; if (referenceNode !== row) { tbody.insertBefore(row, referenceNode); } }); while (tbody.children.length > state.clients.length) { tbody.removeChild(tbody.lastElementChild); } window.applyTableSort(document.getElementById('dashboard-clients-table')); } function updateScreenTable(state) { var table = document.getElementById('dashboard-screens-table'); if (!table || !Array.isArray(state.screens)) { return; } var tbody = table.tBodies && table.tBodies[0] ? table.tBodies[0] : null; if (!tbody) { return; } if (!state.screens.length) { tbody.innerHTML = 'No screens yet.'; return; } tbody.innerHTML = state.screens.map(renderScreenRow).join(''); window.applyTableSort(table); } function updateDashboardQuickActions(state) { var blackoutButton = document.getElementById('dashboard-blackout-all-button'); if (!blackoutButton || !state || !Array.isArray(state.clients)) { return; } var hasClients = state.clients.length > 0; var allBlackout = hasClients && state.clients.every(function (client) { return Boolean(client && client.blackout); }); var label = allBlackout ? 'Restore all clients' : 'Blackout all clients'; var blackoutForm = blackoutButton.form; var blackoutInput = blackoutForm ? blackoutForm.querySelector('input[name="blackout"]') : null; var blackoutButtonIcon = allBlackout ? 'bi-eye' : 'bi-eye-slash'; blackoutButton.innerHTML = '' + escapeHtml(label); setButtonVariant(blackoutButton, ['btn-success', 'btn-danger', 'btn-secondary', 'btn-outline-secondary', 'btn-outline-dark'], 'btn-secondary'); if (blackoutInput) { blackoutInput.value = allBlackout ? 'false' : 'true'; } if (blackoutForm) { blackoutForm.setAttribute('data-confirm-message', allBlackout ? 'Restore all connected clients?' : 'Blackout all connected clients?'); } blackoutButton.setAttribute('aria-label', label); } function handleDashboardState(state) { if (!state) { return; } updateStats(state); updateScreenTable(state); updateClientTable(state); updateDashboardQuickActions(state); } function sendClientRename(screenSlug, connectionId, clientId, deviceId, clientName) { var body = new URLSearchParams(); body.append('command', 'setClientName'); body.append('connectionId', String(connectionId || '').trim()); body.append('clientId', String(clientId || '').trim()); body.append('deviceId', String(deviceId || '').trim()); body.append('clientName', String(clientName || '').trim()); return fetch('/clients/' + encodeURIComponent(String(screenSlug || '').trim()) + '/commands', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json, text/plain, */*' }, body: body.toString(), credentials: 'same-origin' }).then(function (response) { if (!response.ok) { return response.text().then(function (text) { var message = text || 'Unable to rename client.'; try { var payload = JSON.parse(text); message = payload && (payload.error || payload.message) ? String(payload.error || payload.message) : message; } catch (_error) { // fall back to the raw text body } throw new Error(message); }); } return response.json().catch(function () { return { ok: true }; }); }); } function initClientRenameHandler() { var table = document.getElementById('dashboard-clients-table'); var tbody = document.getElementById('dashboard-clients-table-body'); if (!table || !tbody) { return; } tbody.addEventListener('dblclick', function (event) { var cell = event.target && event.target.closest ? event.target.closest('td[data-label="Client"]') : null; if (!cell || !tbody.contains(cell)) { return; } var row = cell.parentElement; if (!row) { return; } var connectionId = String(row.getAttribute('data-client-key') || '').trim(); var clientId = String(row.getAttribute('data-client-id') || '').trim(); var deviceId = String(row.getAttribute('data-client-device-id') || '').trim(); var screenSlug = String(row.getAttribute('data-client-screen-slug') || '').trim(); var currentName = String(cell.textContent || '').trim(); var nextName = window.prompt('Rename connected client', currentName && currentName !== 'Unknown' ? currentName : ''); if (nextName === null) { return; } nextName = String(nextName || '').trim(); if (!nextName) { window.alert('Client name is required.'); return; } if (!connectionId || !screenSlug) { window.alert('Unable to rename this client right now.'); return; } sendClientRename(screenSlug, connectionId, clientId, deviceId, nextName).then(function () { if (row && row.cells && row.cells[0]) { row.cells[0].innerHTML = '
' + escapeHtml(nextName) + '
'; } }).catch(function (error) { window.alert(error && error.message ? error.message : 'Unable to rename client.'); }); }); } window.webHandleDashboardState = handleDashboardState; initClientRenameHandler(); }());