(function () { function escapeHtml(value) { return String(value ?? '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function formatDashboardDate(value) { if (!value) { return ''; } var date = new Date(value); if (Number.isNaN(date.getTime())) { return ''; } return new Intl.DateTimeFormat('en-US', { month: 'short', day: '2-digit', year: 'numeric', hour: 'numeric', minute: '2-digit', second: '2-digit' }).format(date); } function getClientRowKey(client) { return String(client && client.id ? client.id : client && client.clientId ? client.clientId : '').trim(); } function getClientDisplayName(client) { if (client && client.client_name) { return String(client.client_name).trim(); } var clientId = String(client && client.clientId ? client.clientId : '').trim(); if (clientId) { return clientId; } return ''; } function setButtonVariant(button, classesToRemove, classToAdd) { if (!button) { return; } if (button.classList) { classesToRemove.forEach(function (className) { button.classList.remove(className); }); if (classToAdd) { button.classList.add(classToAdd); } return; } var className = String(button.className || ''); classesToRemove.forEach(function (removeClass) { className = className.replace(new RegExp('(^|\\s)' + removeClass + '(?=\\s|$)', 'g'), ' '); }); if (classToAdd) { className += ' ' + classToAdd; } button.className = className.replace(/\s+/g, ' ').trim(); } function normalizeDisplayIp(value) { var ip = String(value || '').trim(); if (!ip) { return ''; } if (ip.toLowerCase().indexOf('::ffff:') === 0) { return ip.slice(7).trim(); } return ip; } function initConfirmForms() { document.addEventListener('submit', function (event) { var form = event.target; if (!form || !form.getAttribute) { return; } if (form.hasAttribute && form.hasAttribute('data-async-command')) { return; } var message = form.getAttribute('data-confirm-message'); if (message && !window.confirm(message)) { event.preventDefault(); } }); } function markFormDirty(form) { if (!form || !form.hasAttribute || form.hasAttribute('data-clean-on-load')) { return; } form.dataset.dirty = 'true'; } function clearFormDirty(form) { if (!form) { return; } form.dataset.dirty = 'false'; } function isFormDirty(form) { return Boolean(form && form.dataset && form.dataset.dirty === 'true'); } function initDirtyTracking() { document.addEventListener('input', function (event) { var target = event.target; if (!target || !target.form) { return; } markFormDirty(target.form); }, true); document.addEventListener('change', function (event) { var target = event.target; if (!target || !target.form) { return; } markFormDirty(target.form); }, true); } function initCancelConfirm() { document.addEventListener('click', function (event) { var cancelTarget = event.target.closest('[data-confirm-unsaved]'); if (!cancelTarget) { return; } var form = cancelTarget.form || cancelTarget.closest('form') || document.querySelector('form[data-dirty="true"]'); if (!isFormDirty(form)) { return; } var message = cancelTarget.getAttribute('data-confirm-unsaved') || 'You have unsaved changes. Leave this page?'; if (!window.confirm(message)) { event.preventDefault(); event.stopPropagation(); } }, true); } function initAsyncCommandForms() { document.addEventListener('submit', function (event) { var form = event.target; if (!form || !form.hasAttribute || !form.hasAttribute('data-async-command')) { return; } if (form.dataset && form.dataset.busy === 'true') { event.preventDefault(); return; } var message = form.getAttribute('data-confirm-message'); if (message && !window.confirm(message)) { event.preventDefault(); return; } event.preventDefault(); form.dataset.busy = 'true'; var formData = new FormData(form); var body = new URLSearchParams(); formData.forEach(function (value, key) { body.append(key, value); }); fetch(form.action, { method: (form.method || 'POST').toUpperCase(), 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' }).finally(function () { delete form.dataset.busy; }); }, true); } function initAsyncSaveForms() { var refreshSequence = 0; function setSaveActionValue(form, value) { if (!form) { return; } var hiddenInput = form.querySelector('input[type="hidden"][name="save_action"]'); if (!hiddenInput) { hiddenInput = document.createElement('input'); hiddenInput.type = 'hidden'; hiddenInput.name = 'save_action'; form.appendChild(hiddenInput); } hiddenInput.value = String(value || '').trim().toLowerCase(); } function getResponseQueryValue(responseUrl, key) { try { var url = new URL(responseUrl, window.location.href); return String(url.searchParams.get(key) || '').trim(); } catch (_error) { return ''; } } function rebindRefreshTarget(targetElement) { if (!targetElement) { return; } if (typeof window.initJsonTogglePanels === 'function') { window.initJsonTogglePanels(targetElement); } if (typeof window.initLocalDateTimes === 'function') { window.initLocalDateTimes(targetElement); } if (typeof window.initTableSearches === 'function') { window.initTableSearches(targetElement); } if (typeof window.initTablePaginations === 'function') { window.initTablePaginations(targetElement); } } function replaceRefreshTargetFromPage(refreshTargetSelector, sequenceId) { if (sequenceId !== refreshSequence) { return; } fetch(window.location.href, { method: 'GET', headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'text/html, application/xhtml+xml' }, credentials: 'same-origin', cache: 'no-store' }).then(function (response) { if (!response.ok) { throw new Error('Unable to refresh saved data.'); } return response.text(); }).then(function (text) { if (sequenceId !== refreshSequence) { return; } var responseDocument = new DOMParser().parseFromString(text || '', 'text/html'); var currentTarget = document.querySelector(refreshTargetSelector); var nextTarget = responseDocument.querySelector(refreshTargetSelector); if (!currentTarget || !nextTarget) { return; } currentTarget.outerHTML = nextTarget.outerHTML; rebindRefreshTarget(document.querySelector(refreshTargetSelector)); }).catch(function (_error) { // Ignore refresh replacement failures and leave the existing content in place. }); } function watchRefreshTask(refreshStateUrl, refreshTaskId, refreshTargetSelector, sequenceId) { var pollDelayMs = 1000; var maxAttempts = 60; function poll(attempt) { if (sequenceId !== refreshSequence) { return; } var stateUrl; try { stateUrl = new URL(refreshStateUrl, window.location.href); } catch (_error) { return; } stateUrl.searchParams.set('refresh_task_id', refreshTaskId); fetch(stateUrl.toString(), { method: 'GET', headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json, text/plain, */*' }, credentials: 'same-origin', cache: 'no-store' }).then(function (response) { if (!response.ok) { throw new Error('Unable to check refresh status.'); } return response.json(); }).then(function (payload) { if (sequenceId !== refreshSequence) { return; } var status = String(payload && payload.status || '').trim().toLowerCase(); if (status === 'queued' || status === 'running') { if (attempt < maxAttempts) { window.setTimeout(function () { poll(attempt + 1); }, pollDelayMs); } return; } replaceRefreshTargetFromPage(refreshTargetSelector, sequenceId); }).catch(function () { if (attempt < maxAttempts) { window.setTimeout(function () { poll(attempt + 1); }, pollDelayMs); } }); } poll(0); } document.addEventListener('click', function (event) { var target = event.target; if (!target || !target.closest) { return; } var button = target.closest('button[name="save_action"]'); if (!button) { return; } var form = button.form || button.closest('form'); if (!form || !form.hasAttribute || !form.hasAttribute('data-async-save')) { return; } setSaveActionValue(form, button.value || ''); form.dataset.submitterValue = String(button.value || '').trim().toLowerCase(); }, true); document.addEventListener('submit', function (event) { var form = event.target; if (!form || !form.hasAttribute || !form.hasAttribute('data-async-save')) { return; } if (form.dataset && form.dataset.busy === 'true') { event.preventDefault(); return; } var message = form.getAttribute('data-confirm-message'); if (message && !window.confirm(message)) { event.preventDefault(); return; } event.preventDefault(); form.dataset.busy = 'true'; var hiddenSaveAction = form.querySelector('input[type="hidden"][name="save_action"]'); var formData = new FormData(form); var submitterValue = String((hiddenSaveAction && hiddenSaveAction.value) || form.dataset.submitterValue || '').trim().toLowerCase(); if (event.submitter && event.submitter.name) { submitterValue = String(event.submitter.value || '').trim().toLowerCase(); formData.set(event.submitter.name, event.submitter.value || ''); } var hasFileValue = false; formData.forEach(function (value) { if (value && typeof value === 'object' && typeof value.name === 'string') { hasFileValue = true; } }); var isMultipart = hasFileValue || String(form.enctype || '').toLowerCase() === 'multipart/form-data'; var body = isMultipart ? formData : new URLSearchParams(); if (!isMultipart) { formData.forEach(function (value, key) { body.append(key, value); }); } fetch(form.action, { method: (form.method || 'POST').toUpperCase(), headers: Object.assign({ 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'text/html, application/json, text/plain, */*' }, isMultipart ? {} : { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' }), body: isMultipart ? body : body.toString(), credentials: 'same-origin' }).then(function (response) { if (!response.ok) { return response.text().then(function (text) { var error = new Error(text || 'Unable to save changes.'); error.status = response.status; throw error; }); } var actionUrl = ''; try { actionUrl = new URL(form.action, window.location.href).pathname; } catch (_error) { actionUrl = String(form.action || ''); } var refreshTargetSelector = String(form.getAttribute('data-async-save-refresh-target') || '').trim(); var refreshStateUrl = String(form.getAttribute('data-async-save-refresh-state-url') || '').trim(); var refreshTaskId = getResponseQueryValue(response.url || '', 'refresh_task_id'); var shouldFollowRedirect = Boolean(form.hasAttribute('data-async-save-new-url')) && !/\/\d+(?:\/|$)/.test(actionUrl); if (submitterValue === 'close' || submitterValue === 'new') { var redirectUrl = submitterValue === 'close' ? String(form.dataset.asyncSaveCloseUrl || response.url || window.location.href) : String(form.dataset.asyncSaveNewUrl || response.url || window.location.href); window.location.replace(redirectUrl); return; } if (shouldFollowRedirect && response.url) { clearFormDirty(form); window.location.replace(response.url); return; } if (form.hasAttribute('data-async-save-reload-on-success')) { clearFormDirty(form); window.location.replace(response.url || window.location.href); return; } clearFormDirty(form); return response.text().then(function (text) { var savedMessage = ''; var responseDocument = null; try { responseDocument = new DOMParser().parseFromString(text || '', 'text/html'); var toastBody = responseDocument.querySelector('.toast-body'); if (toastBody && toastBody.textContent) { savedMessage = toastBody.textContent.trim(); } } catch (_error) { savedMessage = ''; } if (refreshTargetSelector && refreshStateUrl && refreshTaskId) { refreshSequence += 1; watchRefreshTask(refreshStateUrl, refreshTaskId, refreshTargetSelector, refreshSequence); } else if (refreshTargetSelector && responseDocument) { var currentTarget = document.querySelector(refreshTargetSelector); var nextTarget = responseDocument.querySelector(refreshTargetSelector); if (currentTarget && nextTarget) { currentTarget.outerHTML = nextTarget.outerHTML; rebindRefreshTarget(document.querySelector(refreshTargetSelector)); } } showToast(savedMessage || 'Saved.', 'success'); }); }).catch(function (error) { if (typeof showToast === 'function') { var variant = Number(error && error.status) >= 400 && Number(error && error.status) < 500 ? 'warning' : 'danger'; showToast(error.message || 'Unable to save changes.', variant); return; } window.alert(error.message || 'Unable to save changes.'); }).finally(function () { delete form.dataset.busy; delete form.dataset.submitterValue; if (hiddenSaveAction) { hiddenSaveAction.value = ''; } }); }, true); } function initSubmitOnChange() { var fields = document.querySelectorAll('[data-submit-on-change]'); Array.prototype.forEach.call(fields, function (field) { field.addEventListener('change', function () { var form = field.form || field.closest('form'); if (form) { form.submit(); } }); }); } function initJsonTogglePanels(root) { var scope = root && root.querySelectorAll ? root : document; var panels = scope.querySelectorAll('[data-json-toggle-panel]'); Array.prototype.forEach.call(panels, function (panel) { var output = panel.querySelector('[data-json-toggle-output]'); if (!output) { return; } var card = panel.closest ? panel.closest('.card') : null; var button = card ? card.querySelector('[data-json-toggle]') : null; var label = button ? button.querySelector('[data-json-toggle-label]') : null; var sourceNode = panel.querySelector('[data-json-toggle-source]'); var rawJson = ''; try { rawJson = JSON.parse(String(sourceNode ? sourceNode.textContent : '""')); } catch (_error) { rawJson = ''; } if (typeof rawJson !== 'string' || !rawJson.trim()) { if (button) { button.classList.add('d-none'); } return; } var parsedJson; try { parsedJson = JSON.parse(rawJson); } catch (_error) { if (button) { button.classList.add('d-none'); } return; } var compactJson = JSON.stringify(parsedJson); var formattedJson = JSON.stringify(parsedJson, null, 2); var isFormatted = true; function syncButtonLabel() { if (!button || !label) { return; } label.textContent = isFormatted ? String(button.getAttribute('data-json-toggle-label-compact') || 'Unformat JSON') : String(button.getAttribute('data-json-toggle-label-formatted') || 'Format JSON'); button.setAttribute('aria-pressed', isFormatted ? 'true' : 'false'); } function syncOutput() { output.textContent = isFormatted ? formattedJson : compactJson; syncButtonLabel(); } output.textContent = formattedJson; syncButtonLabel(); if (button) { button.addEventListener('click', function () { isFormatted = !isFormatted; syncOutput(); }); } }); } function initLocalDateTimes(root) { var scope = root && root.querySelectorAll ? root : document; var elements = scope.querySelectorAll('[data-local-datetime]'); Array.prototype.forEach.call(elements, function (element) { var rawValue = String(element.getAttribute('datetime') || element.getAttribute('data-local-datetime') || element.textContent || '').trim(); if (!rawValue) { return; } var date = new Date(rawValue); if (Number.isNaN(date.getTime())) { return; } element.textContent = formatDashboardDate(date); }); } function initTablePaginations(root) { var scope = root && root.querySelectorAll ? root : document; Array.prototype.forEach.call(scope.querySelectorAll('[data-table-pagination="auto"]'), function (container) { if (container.getAttribute('data-table-pagination-bound') === 'true') { return; } var table = container.querySelector('[data-table-searchable]'); var cardHeader = container.querySelector('.card-header'); var cardBody = container.querySelector('.card-body.table-responsive') || container.querySelector('.card-body'); var footer = container.querySelector('[data-table-pagination-controls]'); var footerSummary = null; var footerList = null; var syncFrame = 0; var state = { currentPage: 1, pageSize: 1, totalPages: 1 }; if (!table || !cardBody || !table.tBodies.length) { return; } container.setAttribute('data-table-pagination-bound', 'true'); function getRows() { return Array.prototype.slice.call(table.querySelectorAll('tbody tr[data-table-search-row]')); } function isSearchMatched(row) { return String(row.getAttribute('data-table-search-match') || 'true') !== 'false'; } function ensureFooter() { if (footer) { footerSummary = footer.querySelector('[data-table-pagination-summary]'); footerList = footer.querySelector('[data-table-pagination-list]'); return footer; } footer = document.createElement('div'); footer.className = 'card-footer d-none'; footer.setAttribute('data-table-pagination-controls', 'true'); footer.innerHTML = '' + '
' + '
' + '' + '
'; cardBody.parentNode.insertBefore(footer, cardBody.nextSibling); footerSummary = footer.querySelector('[data-table-pagination-summary]'); footerList = footer.querySelector('[data-table-pagination-list]'); return footer; } function setRowVisible(row, visible) { row.hidden = !visible; row.classList.toggle('d-none', !visible); } function getAvailableHeight() { var containerRect = container.getBoundingClientRect(); var headerHeight = cardHeader ? cardHeader.getBoundingClientRect().height : 0; var footerEstimate = footer && !footer.classList.contains('d-none') ? footer.getBoundingClientRect().height : 56; return Math.max(0, window.innerHeight - Math.max(0, containerRect.top) - headerHeight - footerEstimate - 24); } function renderFooter(totalItems) { var pageButtons = []; ensureFooter(); if (state.totalPages <= 1 || totalItems <= 0) { footer.classList.add('d-none'); footerSummary.textContent = ''; footerList.innerHTML = ''; return; } footer.classList.remove('d-none'); footerSummary.textContent = 'Showing ' + (((state.currentPage - 1) * state.pageSize) + 1) + '-' + Math.min(totalItems, state.currentPage * state.pageSize) + ' of ' + totalItems; pageButtons.push('
  • Previous
  • '); for (var pageNumber = 1; pageNumber <= state.totalPages; pageNumber += 1) { pageButtons.push('
  • ' + pageNumber + '
  • '); } pageButtons.push('
  • Next
  • '); footerList.innerHTML = pageButtons.join(''); } function applyPagination(resetPage) { var rows = getRows(); var matchedRows = rows.filter(isSearchMatched); var totalItems = matchedRows.length; var rowHeight = 48; var pageSize; var startIndex; var endIndex; if (resetPage) { state.currentPage = 1; } rows.forEach(function (row) { if (!isSearchMatched(row)) { setRowVisible(row, false); } }); if (!totalItems) { state.pageSize = 1; state.totalPages = 1; state.currentPage = 1; renderFooter(0); return; } matchedRows.forEach(function (row) { setRowVisible(row, true); }); if (matchedRows[0]) { rowHeight = Math.max(24, matchedRows[0].getBoundingClientRect().height || matchedRows[0].offsetHeight || 48); } pageSize = Math.max(1, Math.floor(getAvailableHeight() / rowHeight)); if (pageSize >= totalItems) { state.pageSize = totalItems; state.totalPages = 1; state.currentPage = 1; matchedRows.forEach(function (row) { setRowVisible(row, true); }); renderFooter(totalItems); return; } state.pageSize = pageSize; state.totalPages = Math.max(1, Math.ceil(totalItems / pageSize)); state.currentPage = Math.min(Math.max(1, state.currentPage), state.totalPages); startIndex = (state.currentPage - 1) * pageSize; endIndex = startIndex + pageSize; matchedRows.forEach(function (row, index) { setRowVisible(row, index >= startIndex && index < endIndex); }); renderFooter(totalItems); } function scheduleSync(options) { if (syncFrame) { window.cancelAnimationFrame(syncFrame); } syncFrame = window.requestAnimationFrame(function () { syncFrame = 0; applyPagination(Boolean(options && options.resetPage)); }); } ensureFooter(); if (footer) { footer.addEventListener('click', function (event) { var target = event.target && event.target.closest ? event.target.closest('[data-table-pagination-page]') : null; var pageValue; if (!target || target.closest('.disabled')) { return; } event.preventDefault(); pageValue = Math.max(1, Math.floor(Number(target.getAttribute('data-table-pagination-page')) || 1)); state.currentPage = pageValue; scheduleSync(); }); } window.addEventListener('resize', function () { scheduleSync(); }); if (typeof ResizeObserver === 'function') { try { new ResizeObserver(function () { scheduleSync(); }).observe(container); } catch (_error) { // Ignore observer setup failures and rely on window resize. } } container.syncTablePagination = scheduleSync; scheduleSync({ resetPage: true }); }); } function initTableSearches(root) { var scope = root && root.querySelectorAll ? root : document; Array.prototype.forEach.call(scope.querySelectorAll('[data-table-search]'), function (input) { if (input.getAttribute('data-table-search-bound') === 'true') { return; } var container = input.closest('[data-table-search-container]') || input.closest('.card') || document; var table = container.querySelector('[data-table-searchable]'); var emptyRow = null; if (!table) { return; } input.setAttribute('data-table-search-bound', 'true'); function getSearchableRows() { return Array.prototype.slice.call(table.querySelectorAll('tbody tr[data-table-search-row]')); } function getDefaultEmptyRow() { return table.querySelector('tbody tr[data-table-search-empty-default]'); } function removeGeneratedEmptyRow() { if (emptyRow && emptyRow.parentNode) { emptyRow.parentNode.removeChild(emptyRow); } emptyRow = null; } function ensureGeneratedEmptyRow(message) { var tbody = table.tBodies[0] || table.querySelector('tbody'); var columnCount = 1; if (!tbody) { return; } if (!emptyRow) { emptyRow = document.createElement('tr'); emptyRow.setAttribute('data-table-search-empty-row', 'true'); var cell = document.createElement('td'); cell.className = 'empty'; cell.setAttribute('data-table-search-empty-cell', 'true'); emptyRow.appendChild(cell); } columnCount = table.tHead && table.tHead.rows && table.tHead.rows[0] ? table.tHead.rows[0].cells.length : (getSearchableRows()[0] ? getSearchableRows()[0].cells.length : 1); emptyRow.firstElementChild.colSpan = Math.max(1, columnCount); emptyRow.firstElementChild.textContent = message; if (!emptyRow.parentNode) { tbody.appendChild(emptyRow); } } function syncSearch() { var query = String(input.value || '').trim().toLowerCase(); var rows = getSearchableRows(); var visibleCount = 0; var defaultEmptyRow = getDefaultEmptyRow(); removeGeneratedEmptyRow(); rows.forEach(function (row) { var haystack = String(row.getAttribute('data-search-text') || row.textContent || '').toLowerCase(); var visible = !query || haystack.indexOf(query) !== -1; row.setAttribute('data-table-search-match', visible ? 'true' : 'false'); row.classList.toggle('d-none', !visible); row.hidden = !visible; if (visible) { visibleCount += 1; } }); if (defaultEmptyRow) { defaultEmptyRow.classList.toggle('d-none', Boolean(query)); defaultEmptyRow.hidden = Boolean(query); } if (query && visibleCount === 0) { ensureGeneratedEmptyRow('No results match your search.'); } if (typeof window.syncTablePagination === 'function') { window.syncTablePagination(container, { resetPage: true }); } } input.addEventListener('input', syncSearch); syncSearch(); }); } function createSlideThumbPlaceholder() { var placeholder = document.createElement('span'); var icon = document.createElement('i'); placeholder.className = 'playlist-slide-thumb-placeholder'; icon.className = 'bi bi-image'; icon.setAttribute('aria-hidden', 'true'); placeholder.appendChild(icon); return placeholder; } function attachSlideThumbFallbacks(root) { var scope = root && root.querySelectorAll ? root : document; Array.prototype.forEach.call(scope.querySelectorAll('.playlist-slide-thumb-image'), function (image) { if (image.getAttribute('data-slide-thumb-fallback-bound') === 'true') { return; } image.setAttribute('data-slide-thumb-fallback-bound', 'true'); image.addEventListener('error', function () { if (image.parentNode) { image.parentNode.replaceChild(createSlideThumbPlaceholder(), image); } }); }); } if (window.initSortableTables) { window.initSortableTables(); } initConfirmForms(); initDirtyTracking(); initCancelConfirm(); initAsyncCommandForms(); initAsyncSaveForms(); initSubmitOnChange(); initJsonTogglePanels(); initLocalDateTimes(); initTableSearches(); initTablePaginations(); attachSlideThumbFallbacks(document); window.initJsonTogglePanels = initJsonTogglePanels; window.initLocalDateTimes = initLocalDateTimes; window.initTableSearches = initTableSearches; window.initTablePaginations = initTablePaginations; window.syncTablePagination = function (container, options) { if (!container || !container.syncTablePagination) { return; } container.syncTablePagination(options); }; window.createSlideThumbPlaceholder = createSlideThumbPlaceholder; window.attachSlideThumbFallbacks = attachSlideThumbFallbacks; }());