This commit is contained in:
@@ -0,0 +1,653 @@
|
||||
/**
|
||||
* Settings Page Controller
|
||||
*/
|
||||
|
||||
class SettingsPage {
|
||||
constructor(app) {
|
||||
this.app = app;
|
||||
this.tabs = document.querySelectorAll('.tabs .tab');
|
||||
this.tabContents = document.querySelectorAll('.tab-content');
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
// Tab switching
|
||||
this.tabs.forEach(tab => {
|
||||
tab.addEventListener('click', () => this.switchTab(tab.dataset.tab));
|
||||
});
|
||||
|
||||
// Player settings
|
||||
this.initPlayerSettings();
|
||||
|
||||
// Transcoding settings
|
||||
this.initTranscodingSettings();
|
||||
|
||||
// User management (admin only)
|
||||
this.initUserManagement();
|
||||
}
|
||||
|
||||
initPlayerSettings() {
|
||||
const arrowKeysToggle = document.getElementById('setting-arrow-keys');
|
||||
const overlayDurationInput = document.getElementById('setting-overlay-duration');
|
||||
const defaultVolumeSlider = document.getElementById('setting-default-volume');
|
||||
const volumeValueDisplay = document.getElementById('volume-value');
|
||||
const rememberVolumeToggle = document.getElementById('setting-remember-volume');
|
||||
const autoPlayNextToggle = document.getElementById('setting-autoplay-next');
|
||||
|
||||
// Load current settings
|
||||
if (this.app.player?.settings) {
|
||||
arrowKeysToggle.checked = this.app.player.settings.arrowKeysChangeChannel;
|
||||
overlayDurationInput.value = this.app.player.settings.overlayDuration;
|
||||
defaultVolumeSlider.value = this.app.player.settings.defaultVolume;
|
||||
volumeValueDisplay.textContent = this.app.player.settings.defaultVolume + '%';
|
||||
rememberVolumeToggle.checked = this.app.player.settings.rememberVolume;
|
||||
autoPlayNextToggle.checked = this.app.player.settings.autoPlayNextEpisode;
|
||||
}
|
||||
|
||||
// Arrow keys toggle
|
||||
arrowKeysToggle.addEventListener('change', () => {
|
||||
this.app.player.settings.arrowKeysChangeChannel = arrowKeysToggle.checked;
|
||||
this.app.player.saveSettings();
|
||||
});
|
||||
|
||||
// Overlay duration
|
||||
overlayDurationInput.addEventListener('change', () => {
|
||||
this.app.player.settings.overlayDuration = parseInt(overlayDurationInput.value) || 5;
|
||||
this.app.player.saveSettings();
|
||||
});
|
||||
|
||||
// Default volume slider
|
||||
defaultVolumeSlider.addEventListener('input', () => {
|
||||
const value = defaultVolumeSlider.value;
|
||||
volumeValueDisplay.textContent = value + '%';
|
||||
this.app.player.settings.defaultVolume = parseInt(value);
|
||||
this.app.player.saveSettings();
|
||||
});
|
||||
|
||||
// Remember volume toggle
|
||||
rememberVolumeToggle.addEventListener('change', () => {
|
||||
this.app.player.settings.rememberVolume = rememberVolumeToggle.checked;
|
||||
this.app.player.saveSettings();
|
||||
});
|
||||
|
||||
// Auto-play next episode toggle
|
||||
autoPlayNextToggle.addEventListener('change', () => {
|
||||
this.app.player.settings.autoPlayNextEpisode = autoPlayNextToggle.checked;
|
||||
this.app.player.saveSettings();
|
||||
});
|
||||
|
||||
// EPG refresh interval
|
||||
const epgRefreshSelect = document.getElementById('epg-refresh-interval');
|
||||
if (epgRefreshSelect && this.app.player?.settings) {
|
||||
// Load saved value from player settings
|
||||
epgRefreshSelect.value = this.app.player.settings.epgRefreshInterval || '24';
|
||||
|
||||
// Save on change - server will restart its sync timer via PUT /api/settings
|
||||
epgRefreshSelect.addEventListener('change', () => {
|
||||
this.app.player.settings.epgRefreshInterval = epgRefreshSelect.value;
|
||||
this.app.player.saveSettings();
|
||||
});
|
||||
}
|
||||
|
||||
// Update last refreshed display
|
||||
this.updateEpgLastRefreshed();
|
||||
}
|
||||
|
||||
async initTranscodingSettings() {
|
||||
// Encoder settings
|
||||
const hwEncoderSelect = document.getElementById('setting-hw-encoder');
|
||||
const maxResolutionSelect = document.getElementById('setting-max-resolution');
|
||||
const qualitySelect = document.getElementById('setting-quality');
|
||||
|
||||
// Stream processing (use -tc suffix IDs from Transcoding tab)
|
||||
const forceProxyToggle = document.getElementById('setting-force-proxy-tc');
|
||||
const autoTranscodeToggle = document.getElementById('setting-auto-transcode-tc');
|
||||
const forceTranscodeToggle = document.getElementById('setting-force-transcode-tc');
|
||||
const forceVideoTranscodeToggle = document.getElementById('setting-force-video-transcode-tc');
|
||||
const forceRemuxToggle = document.getElementById('setting-force-remux-tc');
|
||||
const streamFormatSelect = document.getElementById('setting-stream-format-tc');
|
||||
|
||||
// User-Agent (Transcoding tab versions)
|
||||
const userAgentSelect = document.getElementById('setting-user-agent-tc');
|
||||
const userAgentCustomInput = document.getElementById('setting-user-agent-custom-tc');
|
||||
const customUaContainer = document.getElementById('custom-user-agent-container-tc');
|
||||
|
||||
// Fetch settings directly from API to avoid race condition with VideoPlayer
|
||||
let s;
|
||||
try {
|
||||
s = await API.settings.get();
|
||||
} catch (err) {
|
||||
console.warn('[Settings] Failed to load settings from API, using player defaults:', err);
|
||||
s = this.app.player?.settings || {};
|
||||
}
|
||||
|
||||
if (hwEncoderSelect) hwEncoderSelect.value = s.hwEncoder || 'auto';
|
||||
if (maxResolutionSelect) maxResolutionSelect.value = s.maxResolution || '1080p';
|
||||
if (qualitySelect) qualitySelect.value = s.quality || 'medium';
|
||||
if (forceProxyToggle) forceProxyToggle.checked = s.forceProxy === true;
|
||||
if (autoTranscodeToggle) autoTranscodeToggle.checked = s.autoTranscode !== false;
|
||||
if (forceTranscodeToggle) forceTranscodeToggle.checked = s.forceTranscode === true;
|
||||
if (forceVideoTranscodeToggle) forceVideoTranscodeToggle.checked = s.forceVideoTranscode === true;
|
||||
if (forceRemuxToggle) forceRemuxToggle.checked = s.forceRemux || false;
|
||||
if (streamFormatSelect) streamFormatSelect.value = s.streamFormat || 'ts';
|
||||
if (userAgentSelect) userAgentSelect.value = s.userAgentPreset || 'chrome';
|
||||
if (userAgentCustomInput) userAgentCustomInput.value = s.userAgentCustom || '';
|
||||
if (customUaContainer) {
|
||||
customUaContainer.style.display = userAgentSelect?.value === 'custom' ? 'flex' : 'none';
|
||||
}
|
||||
|
||||
// Event listeners for encoder settings
|
||||
hwEncoderSelect?.addEventListener('change', () => {
|
||||
this.app.player.settings.hwEncoder = hwEncoderSelect.value;
|
||||
this.app.player.saveSettings();
|
||||
});
|
||||
|
||||
maxResolutionSelect?.addEventListener('change', () => {
|
||||
this.app.player.settings.maxResolution = maxResolutionSelect.value;
|
||||
this.app.player.saveSettings();
|
||||
});
|
||||
|
||||
qualitySelect?.addEventListener('change', () => {
|
||||
this.app.player.settings.quality = qualitySelect.value;
|
||||
this.app.player.saveSettings();
|
||||
});
|
||||
|
||||
// Audio Mix Preset
|
||||
const audioMixSelect = document.getElementById('setting-audio-mix');
|
||||
if (audioMixSelect) {
|
||||
audioMixSelect.value = s.audioMixPreset || 'auto';
|
||||
audioMixSelect.addEventListener('change', () => {
|
||||
this.app.player.settings.audioMixPreset = audioMixSelect.value;
|
||||
this.app.player.saveSettings();
|
||||
});
|
||||
}
|
||||
|
||||
// Upscaling Settings
|
||||
const upscaleEnabledToggle = document.getElementById('setting-upscale-enabled');
|
||||
const upscaleMethodSelect = document.getElementById('setting-upscale-method');
|
||||
const upscaleTargetSelect = document.getElementById('setting-upscale-target');
|
||||
const upscaleMethodContainer = document.getElementById('upscale-method-container');
|
||||
const upscaleTargetContainer = document.getElementById('upscale-target-container');
|
||||
|
||||
// Helper to toggle upscale options visibility
|
||||
const toggleUpscaleOptions = (enabled) => {
|
||||
if (upscaleMethodContainer) upscaleMethodContainer.style.display = enabled ? 'flex' : 'none';
|
||||
if (upscaleTargetContainer) upscaleTargetContainer.style.display = enabled ? 'flex' : 'none';
|
||||
};
|
||||
|
||||
// Load upscaling settings
|
||||
if (upscaleEnabledToggle) {
|
||||
upscaleEnabledToggle.checked = s.upscaleEnabled || false;
|
||||
toggleUpscaleOptions(upscaleEnabledToggle.checked);
|
||||
}
|
||||
if (upscaleMethodSelect) upscaleMethodSelect.value = s.upscaleMethod || 'hardware';
|
||||
if (upscaleTargetSelect) upscaleTargetSelect.value = s.upscaleTarget || '1080p';
|
||||
|
||||
// Upscaling event handlers
|
||||
upscaleEnabledToggle?.addEventListener('change', () => {
|
||||
this.app.player.settings.upscaleEnabled = upscaleEnabledToggle.checked;
|
||||
this.app.player.saveSettings();
|
||||
toggleUpscaleOptions(upscaleEnabledToggle.checked);
|
||||
});
|
||||
|
||||
upscaleMethodSelect?.addEventListener('change', () => {
|
||||
this.app.player.settings.upscaleMethod = upscaleMethodSelect.value;
|
||||
this.app.player.saveSettings();
|
||||
});
|
||||
|
||||
upscaleTargetSelect?.addEventListener('change', () => {
|
||||
this.app.player.settings.upscaleTarget = upscaleTargetSelect.value;
|
||||
this.app.player.saveSettings();
|
||||
});
|
||||
|
||||
// Stream processing toggles
|
||||
forceProxyToggle?.addEventListener('change', () => {
|
||||
this.app.player.settings.forceProxy = forceProxyToggle.checked;
|
||||
this.app.player.saveSettings();
|
||||
});
|
||||
|
||||
autoTranscodeToggle?.addEventListener('change', () => {
|
||||
this.app.player.settings.autoTranscode = autoTranscodeToggle.checked;
|
||||
this.app.player.saveSettings();
|
||||
});
|
||||
|
||||
forceTranscodeToggle?.addEventListener('change', () => {
|
||||
this.app.player.settings.forceTranscode = forceTranscodeToggle.checked;
|
||||
this.app.player.saveSettings();
|
||||
});
|
||||
|
||||
forceVideoTranscodeToggle?.addEventListener('change', () => {
|
||||
this.app.player.settings.forceVideoTranscode = forceVideoTranscodeToggle.checked;
|
||||
this.app.player.saveSettings();
|
||||
});
|
||||
|
||||
forceRemuxToggle?.addEventListener('change', () => {
|
||||
this.app.player.settings.forceRemux = forceRemuxToggle.checked;
|
||||
this.app.player.saveSettings();
|
||||
});
|
||||
|
||||
streamFormatSelect?.addEventListener('change', () => {
|
||||
this.app.player.settings.streamFormat = streamFormatSelect.value;
|
||||
this.app.player.saveSettings();
|
||||
});
|
||||
|
||||
// User-Agent handlers
|
||||
const toggleCustomInput = () => {
|
||||
if (customUaContainer) {
|
||||
customUaContainer.style.display = userAgentSelect?.value === 'custom' ? 'flex' : 'none';
|
||||
}
|
||||
};
|
||||
|
||||
userAgentSelect?.addEventListener('change', () => {
|
||||
this.app.player.settings.userAgentPreset = userAgentSelect.value;
|
||||
this.app.player.saveSettings();
|
||||
toggleCustomInput();
|
||||
});
|
||||
|
||||
userAgentCustomInput?.addEventListener('change', () => {
|
||||
this.app.player.settings.userAgentCustom = userAgentCustomInput.value;
|
||||
this.app.player.saveSettings();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and display hardware info in Transcoding tab
|
||||
*/
|
||||
async loadHardwareInfo() {
|
||||
const container = document.getElementById('hw-info-container');
|
||||
if (!container) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/settings/hw-info');
|
||||
if (!response.ok) throw new Error('Failed to fetch hardware info');
|
||||
const hwInfo = await response.json();
|
||||
|
||||
const detected = [];
|
||||
|
||||
// Only show detected hardware
|
||||
if (hwInfo.nvidia?.available) {
|
||||
detected.push(`<div class="hw-info-item hw-available">
|
||||
<span class="hw-badge">✓ NVIDIA</span>
|
||||
<span class="hw-name">${hwInfo.nvidia.name}</span>
|
||||
</div>`);
|
||||
}
|
||||
|
||||
if (hwInfo.amf?.available) {
|
||||
detected.push(`<div class="hw-info-item hw-available">
|
||||
<span class="hw-badge">✓ AMD</span>
|
||||
<span class="hw-name">${hwInfo.amf.name || 'Available'}</span>
|
||||
</div>`);
|
||||
}
|
||||
|
||||
if (hwInfo.qsv?.available) {
|
||||
detected.push(`<div class="hw-info-item hw-available">
|
||||
<span class="hw-badge">✓ Intel QSV</span>
|
||||
<span class="hw-name">Available</span>
|
||||
</div>`);
|
||||
}
|
||||
|
||||
if (hwInfo.vaapi?.available) {
|
||||
detected.push(`<div class="hw-info-item hw-available">
|
||||
<span class="hw-badge">✓ VAAPI</span>
|
||||
<span class="hw-name">${hwInfo.vaapi.device || 'Available'}</span>
|
||||
</div>`);
|
||||
}
|
||||
|
||||
let html;
|
||||
if (detected.length > 0) {
|
||||
html = `<div class="hw-info-grid">${detected.join('')}</div>`;
|
||||
html += `<p class="hint" style="margin-top: var(--space-sm);">Recommended encoder: <strong>${hwInfo.recommended}</strong></p>`;
|
||||
} else {
|
||||
html = `<p class="hint">No GPU acceleration detected. Using software encoding.</p>`;
|
||||
}
|
||||
|
||||
container.innerHTML = html;
|
||||
} catch (err) {
|
||||
console.error('Error loading hardware info:', err);
|
||||
container.innerHTML = '<p class="hint error">Failed to load hardware info</p>';
|
||||
}
|
||||
}
|
||||
|
||||
initUserManagement() {
|
||||
// User tab visibility is handled in show() method
|
||||
// when currentUser is available
|
||||
|
||||
// Handle add user form
|
||||
const addUserForm = document.getElementById('add-user-form');
|
||||
if (addUserForm) {
|
||||
addUserForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const username = document.getElementById('new-username').value;
|
||||
const password = document.getElementById('new-password').value;
|
||||
const role = document.getElementById('new-role').value;
|
||||
|
||||
try {
|
||||
await API.users.create({ username, password, role });
|
||||
alert('Usuario creado correctamente.');
|
||||
addUserForm.reset();
|
||||
this.loadUsers();
|
||||
} catch (err) {
|
||||
alert('Error al crear usuario: ' + err.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async loadUsers() {
|
||||
const userList = document.getElementById('user-list');
|
||||
if (!userList) return;
|
||||
|
||||
try {
|
||||
const users = await API.users.getAll();
|
||||
// Store users in memory for easy access during edit
|
||||
this.users = users;
|
||||
|
||||
if (users.length === 0) {
|
||||
userList.innerHTML = '<tr><td colspan="5" class="hint">No users found</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
userList.innerHTML = users.map(user => {
|
||||
const isSSO = !!user.oidcId;
|
||||
const typeBadge = isSSO
|
||||
? '<span class="user-badge user-badge-sso">SSO</span>'
|
||||
: '<span class="user-badge user-badge-local">Local</span>';
|
||||
|
||||
const roleBadge = user.role === 'admin'
|
||||
? '<span class="user-badge user-badge-admin">Admin</span>'
|
||||
: '<span class="user-badge user-badge-viewer">Viewer</span>';
|
||||
|
||||
return `
|
||||
<tr>
|
||||
<td>
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<strong>${user.username}</strong>
|
||||
${typeBadge}
|
||||
</div>
|
||||
</td>
|
||||
<td>${user.email || '<span class="hint">-</span>'}</td>
|
||||
<td>${roleBadge}</td>
|
||||
<td>${user.createdAt ? new Date(user.createdAt).toLocaleDateString() : 'N/A'}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-secondary" onclick="window.app.pages.settings.openEditUserModal(${user.id})">Edit</button>
|
||||
<button class="btn btn-sm btn-error" onclick="window.app.pages.settings.deleteUser(${user.id}, '${user.username}')">Delete</button>
|
||||
</td>
|
||||
</tr>
|
||||
`}).join('');
|
||||
} catch (err) {
|
||||
console.error('Error loading users:', err);
|
||||
userList.innerHTML = '<tr><td colspan="5" class="hint">Error loading users</td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
openEditUserModal(userId) {
|
||||
console.log('openEditUserModal called with ID:', userId, 'Type:', typeof userId);
|
||||
console.log('Current users list:', this.users);
|
||||
|
||||
const user = this.users.find(u => u.id === userId);
|
||||
if (!user) {
|
||||
console.error('User not found in this.users cache!');
|
||||
console.log('Available IDs:', this.users.map(u => u.id));
|
||||
return;
|
||||
}
|
||||
console.log('User found:', user);
|
||||
|
||||
const modal = document.getElementById('edit-user-modal');
|
||||
console.log('Modal element:', modal);
|
||||
if (!modal) {
|
||||
console.error('CRITICAL: Modal element #edit-user-modal not found in DOM!');
|
||||
alert('Error: Modal not found. Please refresh the page.');
|
||||
return;
|
||||
}
|
||||
|
||||
const isSSO = !!user.oidcId;
|
||||
console.log('Is SSO user:', isSSO);
|
||||
|
||||
// Populate form with null checks
|
||||
try {
|
||||
const editId = document.getElementById('edit-user-id');
|
||||
const editUsername = document.getElementById('edit-username');
|
||||
const editEmail = document.getElementById('edit-email');
|
||||
const editRole = document.getElementById('edit-role');
|
||||
const editPassword = document.getElementById('edit-password');
|
||||
|
||||
console.log('Form elements found:', { editId, editUsername, editEmail, editRole, editPassword });
|
||||
|
||||
if (editId) editId.value = user.id;
|
||||
if (editUsername) editUsername.value = user.username;
|
||||
if (editEmail) editEmail.value = user.email || '';
|
||||
if (editRole) editRole.value = user.role;
|
||||
if (editPassword) editPassword.value = '';
|
||||
|
||||
// Handle SSO specific UI
|
||||
const passwordHint = document.getElementById('edit-password-hint');
|
||||
const oidcGroup = document.getElementById('oidc-info-group');
|
||||
const oidcIdDisplay = document.getElementById('edit-oidc-id');
|
||||
|
||||
if (isSSO) {
|
||||
if (editPassword) {
|
||||
editPassword.disabled = true;
|
||||
editPassword.placeholder = "Managed by SSO Provider";
|
||||
}
|
||||
if (passwordHint) passwordHint.textContent = "Password cannot be changed for SSO users.";
|
||||
if (oidcGroup) oidcGroup.classList.remove('hidden');
|
||||
if (oidcIdDisplay) oidcIdDisplay.textContent = user.oidcId;
|
||||
} else {
|
||||
if (editPassword) {
|
||||
editPassword.disabled = false;
|
||||
editPassword.placeholder = "Leave blank to keep current";
|
||||
}
|
||||
if (passwordHint) passwordHint.textContent = "Optional. Leave blank to keep unchanged.";
|
||||
if (oidcGroup) oidcGroup.classList.add('hidden');
|
||||
}
|
||||
|
||||
// Show modal
|
||||
console.log('Adding active class to modal...');
|
||||
modal.classList.add('active');
|
||||
console.log('Modal classes after add:', modal.classList.toString());
|
||||
|
||||
// Setup Close/Cancel handlers (once)
|
||||
this.setupModalHandlers(modal);
|
||||
console.log('Modal should now be visible!');
|
||||
} catch (err) {
|
||||
console.error('Error populating modal:', err);
|
||||
alert('Error al abrir edición: ' + err.message);
|
||||
}
|
||||
}
|
||||
|
||||
setupModalHandlers(modal) {
|
||||
if (this.modalHandlersSetup) return;
|
||||
|
||||
const closeBtn = document.getElementById('edit-user-close');
|
||||
const cancelBtn = document.getElementById('edit-user-cancel');
|
||||
const saveBtn = document.getElementById('edit-user-save');
|
||||
|
||||
const closeModal = () => modal.classList.remove('active');
|
||||
|
||||
closeBtn.onclick = closeModal;
|
||||
cancelBtn.onclick = closeModal;
|
||||
|
||||
// Click outside to close
|
||||
modal.onclick = (e) => {
|
||||
if (e.target === modal) closeModal();
|
||||
};
|
||||
|
||||
// Save Handler
|
||||
saveBtn.onclick = async () => {
|
||||
const userId = document.getElementById('edit-user-id').value;
|
||||
const updates = {
|
||||
username: document.getElementById('edit-username').value,
|
||||
role: document.getElementById('edit-role').value
|
||||
};
|
||||
|
||||
const newPassword = document.getElementById('edit-password').value;
|
||||
if (newPassword && !document.getElementById('edit-password').disabled) {
|
||||
updates.password = newPassword;
|
||||
}
|
||||
|
||||
try {
|
||||
await API.users.update(userId, updates);
|
||||
// alert('User updated successfully!'); // Optional: Replace with toast?
|
||||
closeModal();
|
||||
this.loadUsers();
|
||||
} catch (err) {
|
||||
alert('Error al actualizar usuario: ' + err.message);
|
||||
}
|
||||
};
|
||||
|
||||
this.modalHandlersSetup = true;
|
||||
}
|
||||
|
||||
|
||||
async deleteUser(userId, username) {
|
||||
if (!confirm(`Are you sure you want to delete user "${username}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await API.users.delete(userId);
|
||||
this.loadUsers();
|
||||
} catch (err) {
|
||||
alert('Error al eliminar usuario: ' + err.message);
|
||||
}
|
||||
}
|
||||
|
||||
switchTab(tabName) {
|
||||
this.tabs.forEach(t => t.classList.toggle('active', t.dataset.tab === tabName));
|
||||
this.tabContents.forEach(c => c.classList.toggle('active', c.id === `tab-${tabName}`));
|
||||
|
||||
// Load content browser when switching to that tab
|
||||
if (tabName === 'content') {
|
||||
this.app.sourceManager.loadContentSources();
|
||||
}
|
||||
|
||||
// Refrescar listas al volver a Fuentes (evita lista vacía tras crear/asignar)
|
||||
if (tabName === 'sources') {
|
||||
this.app.sourceManager.loadSources();
|
||||
}
|
||||
|
||||
// Load users when switching to users tab
|
||||
if (tabName === 'users') {
|
||||
this.loadUsers();
|
||||
}
|
||||
|
||||
// Load hardware info when switching to transcode tab
|
||||
if (tabName === 'transcode') {
|
||||
this.loadHardwareInfo();
|
||||
}
|
||||
}
|
||||
|
||||
async show() {
|
||||
// Show users tab for admin
|
||||
if (this.app.currentUser && this.app.currentUser.role === 'admin') {
|
||||
const usersTab = document.getElementById('users-tab');
|
||||
if (usersTab) {
|
||||
usersTab.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
// Load sources when page is shown
|
||||
await this.app.sourceManager.loadSources();
|
||||
|
||||
// Refresh ALL player settings from server
|
||||
if (this.app.player?.settings) {
|
||||
const s = this.app.player.settings;
|
||||
|
||||
// Player settings
|
||||
const arrowKeysToggle = document.getElementById('setting-arrow-keys');
|
||||
const overlayDurationInput = document.getElementById('setting-overlay-duration');
|
||||
const defaultVolumeSlider = document.getElementById('setting-default-volume');
|
||||
const volumeValueDisplay = document.getElementById('volume-value');
|
||||
const rememberVolumeToggle = document.getElementById('setting-remember-volume');
|
||||
const autoPlayNextToggle = document.getElementById('setting-autoplay-next');
|
||||
const forceProxyToggle = document.getElementById('setting-force-proxy');
|
||||
const forceTranscodeToggle = document.getElementById('setting-force-transcode');
|
||||
const forceRemuxToggle = document.getElementById('setting-force-remux');
|
||||
const autoTranscodeToggle = document.getElementById('setting-auto-transcode');
|
||||
const epgRefreshSelect = document.getElementById('epg-refresh-interval');
|
||||
const streamFormatSelect = document.getElementById('setting-stream-format');
|
||||
|
||||
if (arrowKeysToggle) arrowKeysToggle.checked = s.arrowKeysChangeChannel;
|
||||
if (overlayDurationInput) overlayDurationInput.value = s.overlayDuration;
|
||||
if (defaultVolumeSlider) defaultVolumeSlider.value = s.defaultVolume;
|
||||
if (volumeValueDisplay) volumeValueDisplay.textContent = s.defaultVolume + '%';
|
||||
if (rememberVolumeToggle) rememberVolumeToggle.checked = s.rememberVolume;
|
||||
if (autoPlayNextToggle) autoPlayNextToggle.checked = s.autoPlayNextEpisode;
|
||||
if (forceProxyToggle) forceProxyToggle.checked = s.forceProxy || false;
|
||||
if (forceTranscodeToggle) forceTranscodeToggle.checked = s.forceTranscode || false;
|
||||
if (forceRemuxToggle) forceRemuxToggle.checked = s.forceRemux || false;
|
||||
if (autoTranscodeToggle) autoTranscodeToggle.checked = s.autoTranscode || false;
|
||||
if (epgRefreshSelect) epgRefreshSelect.value = s.epgRefreshInterval || '24';
|
||||
if (streamFormatSelect) streamFormatSelect.value = s.streamFormat || 'ts';
|
||||
|
||||
// User-Agent settings
|
||||
const userAgentSelect = document.getElementById('setting-user-agent');
|
||||
const userAgentCustomInput = document.getElementById('setting-user-agent-custom');
|
||||
const customUaContainer = document.getElementById('custom-user-agent-container');
|
||||
if (userAgentSelect) {
|
||||
userAgentSelect.value = s.userAgentPreset || 'chrome';
|
||||
if (customUaContainer) {
|
||||
customUaContainer.style.display = userAgentSelect.value === 'custom' ? 'flex' : 'none';
|
||||
}
|
||||
}
|
||||
if (userAgentCustomInput) userAgentCustomInput.value = s.userAgentCustom || '';
|
||||
}
|
||||
|
||||
// Update EPG last refreshed display
|
||||
this.updateEpgLastRefreshed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the EPG last refreshed display
|
||||
*/
|
||||
async updateEpgLastRefreshed() {
|
||||
const display = document.getElementById('epg-last-refreshed');
|
||||
if (!display) return;
|
||||
|
||||
try {
|
||||
// Fetch last sync time from server
|
||||
const response = await fetch('/api/settings/sync-status');
|
||||
if (!response.ok) throw new Error('Failed to fetch sync status');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.lastSyncTime) {
|
||||
const lastRefreshTime = new Date(data.lastSyncTime);
|
||||
|
||||
// Format as relative time or absolute
|
||||
const now = new Date();
|
||||
const diffMs = now - lastRefreshTime;
|
||||
const diffMins = Math.floor(diffMs / 60000);
|
||||
const diffHours = Math.floor(diffMins / 60);
|
||||
|
||||
let text;
|
||||
if (diffMins < 1) {
|
||||
text = 'Hace un momento';
|
||||
} else if (diffMins < 60) {
|
||||
text = `Hace ${diffMins} min`;
|
||||
} else if (diffHours < 24) {
|
||||
text = `Hace ${diffHours} h`;
|
||||
} else {
|
||||
text = lastRefreshTime.toLocaleString('es-ES');
|
||||
}
|
||||
|
||||
display.textContent = text;
|
||||
display.title = lastRefreshTime.toLocaleString('es-ES');
|
||||
} else {
|
||||
display.textContent = 'Nunca';
|
||||
display.title = 'La sincronización aún no se ha ejecutado';
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching sync status:', err);
|
||||
display.textContent = '—';
|
||||
display.title = 'No se pudo obtener el estado de sincronización';
|
||||
}
|
||||
}
|
||||
|
||||
hide() {
|
||||
// Page is hidden
|
||||
}
|
||||
}
|
||||
|
||||
window.SettingsPage = SettingsPage;
|
||||
Reference in New Issue
Block a user