mirror of
https://github.com/plexguide/Huntarr.git
synced 2026-04-25 10:38:25 -05:00
updating
This commit is contained in:
@@ -1,353 +1,93 @@
|
||||
// Lidarr-specific functionality
|
||||
|
||||
(function(app) {
|
||||
// Add Lidarr-specific initialization
|
||||
if (!app) {
|
||||
console.error("Huntarr App core is not loaded!");
|
||||
return;
|
||||
}
|
||||
|
||||
const lidarrModule = {
|
||||
// DOM elements specific to Lidarr
|
||||
elements: {
|
||||
apiUrlInput: document.getElementById('lidarr_api_url'),
|
||||
apiKeyInput: document.getElementById('lidarr_api_key'),
|
||||
|
||||
// Settings form elements
|
||||
huntMissingAlbumsInput: document.getElementById('hunt_missing_albums'),
|
||||
huntUpgradeTracksInput: document.getElementById('hunt_upgrade_tracks'),
|
||||
sleepDurationInput: document.getElementById('lidarr_sleep_duration'),
|
||||
sleepDurationHoursSpan: document.getElementById('lidarr_sleep_duration_hours'),
|
||||
stateResetIntervalInput: document.getElementById('lidarr_state_reset_interval_hours'),
|
||||
monitoredOnlyInput: document.getElementById('lidarr_monitored_only'),
|
||||
randomMissingInput: document.getElementById('lidarr_random_missing'),
|
||||
randomUpgradesInput: document.getElementById('lidarr_random_upgrades'),
|
||||
skipFutureReleasesInput: document.getElementById('lidarr_skip_future_releases'),
|
||||
skipArtistRefreshInput: document.getElementById('skip_artist_refresh'),
|
||||
|
||||
// Advanced settings
|
||||
apiTimeoutInput: document.getElementById('lidarr_api_timeout'),
|
||||
debugModeInput: document.getElementById('lidarr_debug_mode'),
|
||||
commandWaitDelayInput: document.getElementById('lidarr_command_wait_delay'),
|
||||
commandWaitAttemptsInput: document.getElementById('lidarr_command_wait_attempts'),
|
||||
minimumDownloadQueueSizeInput: document.getElementById('lidarr_minimum_download_queue_size')
|
||||
},
|
||||
|
||||
elements: {},
|
||||
|
||||
init: function() {
|
||||
console.log('[Lidarr Module] Initializing...');
|
||||
this.cacheElements();
|
||||
this.setupEventListeners();
|
||||
|
||||
// Extend the core app with Lidarr-specific implementations
|
||||
app.loadSettingsLidarr = this.loadSettings.bind(this);
|
||||
|
||||
// Override the load settings with Lidarr implementation when Lidarr is active
|
||||
const originalLoadSettings = app.loadSettings;
|
||||
app.loadSettings = function(appType) {
|
||||
if (appType === 'lidarr') {
|
||||
this.loadSettingsLidarr();
|
||||
} else if (originalLoadSettings) {
|
||||
// Only call the original if we're not handling Lidarr
|
||||
originalLoadSettings.call(this, appType);
|
||||
}
|
||||
};
|
||||
// Settings are now loaded centrally by huntarrUI.loadAllSettings
|
||||
// this.loadSettings(); // REMOVED
|
||||
},
|
||||
|
||||
|
||||
cacheElements: function() {
|
||||
// Cache elements specific to the Lidarr settings form
|
||||
this.elements.apiUrlInput = document.getElementById('lidarr_api_url');
|
||||
this.elements.apiKeyInput = document.getElementById('lidarr_api_key');
|
||||
this.elements.huntMissingAlbumsInput = document.getElementById('hunt_missing_albums');
|
||||
this.elements.huntUpgradeTracksInput = document.getElementById('hunt_upgrade_tracks');
|
||||
this.elements.sleepDurationInput = document.getElementById('lidarr_sleep_duration');
|
||||
this.elements.sleepDurationHoursSpan = document.getElementById('lidarr_sleep_duration_hours');
|
||||
this.elements.stateResetIntervalInput = document.getElementById('lidarr_state_reset_interval_hours');
|
||||
this.elements.monitoredOnlyInput = document.getElementById('lidarr_monitored_only');
|
||||
this.elements.skipFutureReleasesInput = document.getElementById('lidarr_skip_future_releases');
|
||||
this.elements.skipArtistRefreshInput = document.getElementById('skip_artist_refresh');
|
||||
this.elements.randomMissingInput = document.getElementById('lidarr_random_missing');
|
||||
this.elements.randomUpgradesInput = document.getElementById('lidarr_random_upgrades');
|
||||
this.elements.debugModeInput = document.getElementById('lidarr_debug_mode');
|
||||
this.elements.apiTimeoutInput = document.getElementById('lidarr_api_timeout');
|
||||
this.elements.commandWaitDelayInput = document.getElementById('lidarr_command_wait_delay');
|
||||
this.elements.commandWaitAttemptsInput = document.getElementById('lidarr_command_wait_attempts');
|
||||
this.elements.minimumDownloadQueueSizeInput = document.getElementById('lidarr_minimum_download_queue_size');
|
||||
// Add any other Lidarr-specific elements
|
||||
},
|
||||
|
||||
setupEventListeners: function() {
|
||||
// Add input event listeners for Lidarr-specific settings
|
||||
const inputs = [
|
||||
this.elements.apiUrlInput,
|
||||
this.elements.apiKeyInput,
|
||||
this.elements.huntMissingAlbumsInput,
|
||||
this.elements.huntUpgradeTracksInput,
|
||||
this.elements.sleepDurationInput,
|
||||
this.elements.stateResetIntervalInput,
|
||||
this.elements.apiTimeoutInput,
|
||||
this.elements.commandWaitDelayInput,
|
||||
this.elements.commandWaitAttemptsInput,
|
||||
this.elements.minimumDownloadQueueSizeInput
|
||||
];
|
||||
|
||||
inputs.forEach(input => {
|
||||
if (input) {
|
||||
input.addEventListener('input', this.checkForChanges.bind(this));
|
||||
}
|
||||
});
|
||||
|
||||
const checkboxes = [
|
||||
this.elements.monitoredOnlyInput,
|
||||
this.elements.randomMissingInput,
|
||||
this.elements.randomUpgradesInput,
|
||||
this.elements.skipFutureReleasesInput,
|
||||
this.elements.skipArtistRefreshInput,
|
||||
this.elements.debugModeInput
|
||||
];
|
||||
|
||||
checkboxes.forEach(checkbox => {
|
||||
if (checkbox) {
|
||||
checkbox.addEventListener('change', this.checkForChanges.bind(this));
|
||||
}
|
||||
});
|
||||
|
||||
// Add special handler for sleep duration to update display
|
||||
// Keep listeners ONLY for elements with specific UI updates beyond simple value changes
|
||||
if (this.elements.sleepDurationInput) {
|
||||
this.elements.sleepDurationInput.addEventListener('input', () => {
|
||||
this.updateSleepDurationDisplay();
|
||||
this.checkForChanges();
|
||||
// No need to call checkForChanges here, handled by delegation
|
||||
});
|
||||
}
|
||||
// Remove other input listeners previously used for checkForChanges
|
||||
},
|
||||
|
||||
|
||||
updateSleepDurationDisplay: function() {
|
||||
// This function remains as it updates a specific UI element
|
||||
if (this.elements.sleepDurationInput && this.elements.sleepDurationHoursSpan) {
|
||||
const seconds = parseInt(this.elements.sleepDurationInput.value) || 900;
|
||||
app.updateDurationDisplay(seconds, this.elements.sleepDurationHoursSpan);
|
||||
}
|
||||
},
|
||||
|
||||
loadSettings: function() {
|
||||
fetch('/api/settings')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Store original settings for comparison
|
||||
app.originalSettings = JSON.parse(JSON.stringify(data));
|
||||
|
||||
// Get app-specific settings directly from lidarr section instead of huntarr/advanced
|
||||
const lidarrSettings = data.lidarr || {};
|
||||
|
||||
// For Lidarr, load from app-settings endpoint
|
||||
fetch(`/api/app-settings?app=lidarr`)
|
||||
.then(response => response.json())
|
||||
.then(appData => {
|
||||
if (appData.success) {
|
||||
this.elements.apiUrlInput.value = appData.api_url || '';
|
||||
this.elements.apiKeyInput.value = appData.api_key || '';
|
||||
|
||||
// Store original values in data attributes for comparison
|
||||
this.elements.apiUrlInput.dataset.originalValue = appData.api_url || '';
|
||||
this.elements.apiKeyInput.dataset.originalValue = appData.api_key || '';
|
||||
|
||||
// Update configured status
|
||||
app.configuredApps.lidarr = !!(appData.api_url && appData.api_key);
|
||||
}
|
||||
|
||||
// Lidarr-specific settings - all from lidarrSettings directly
|
||||
if (this.elements.huntMissingAlbumsInput) {
|
||||
this.elements.huntMissingAlbumsInput.value = lidarrSettings.hunt_missing_albums !== undefined ? lidarrSettings.hunt_missing_albums : 1;
|
||||
}
|
||||
if (this.elements.huntUpgradeTracksInput) {
|
||||
this.elements.huntUpgradeTracksInput.value = lidarrSettings.hunt_upgrade_tracks !== undefined ? lidarrSettings.hunt_upgrade_tracks : 0;
|
||||
}
|
||||
if (this.elements.sleepDurationInput) {
|
||||
this.elements.sleepDurationInput.value = lidarrSettings.sleep_duration || 900;
|
||||
this.updateSleepDurationDisplay();
|
||||
}
|
||||
if (this.elements.stateResetIntervalInput) {
|
||||
this.elements.stateResetIntervalInput.value = lidarrSettings.state_reset_interval_hours || 168;
|
||||
}
|
||||
if (this.elements.monitoredOnlyInput) {
|
||||
this.elements.monitoredOnlyInput.checked = lidarrSettings.monitored_only !== false;
|
||||
}
|
||||
if (this.elements.skipFutureReleasesInput) {
|
||||
this.elements.skipFutureReleasesInput.checked = lidarrSettings.skip_future_releases !== false;
|
||||
}
|
||||
if (this.elements.skipArtistRefreshInput) {
|
||||
this.elements.skipArtistRefreshInput.checked = lidarrSettings.skip_artist_refresh === true;
|
||||
}
|
||||
|
||||
// Advanced settings - from the same lidarrSettings object
|
||||
if (this.elements.apiTimeoutInput) {
|
||||
this.elements.apiTimeoutInput.value = lidarrSettings.api_timeout || 60;
|
||||
}
|
||||
if (this.elements.debugModeInput) {
|
||||
this.elements.debugModeInput.checked = lidarrSettings.debug_mode === true;
|
||||
}
|
||||
if (this.elements.commandWaitDelayInput) {
|
||||
this.elements.commandWaitDelayInput.value = lidarrSettings.command_wait_delay || 1;
|
||||
}
|
||||
if (this.elements.commandWaitAttemptsInput) {
|
||||
this.elements.commandWaitAttemptsInput.value = lidarrSettings.command_wait_attempts || 600;
|
||||
}
|
||||
if (this.elements.minimumDownloadQueueSizeInput) {
|
||||
this.elements.minimumDownloadQueueSizeInput.value = lidarrSettings.minimum_download_queue_size || -1;
|
||||
}
|
||||
if (this.elements.randomMissingInput) {
|
||||
this.elements.randomMissingInput.checked = lidarrSettings.random_missing !== false;
|
||||
}
|
||||
if (this.elements.randomUpgradesInput) {
|
||||
this.elements.randomUpgradesInput.checked = lidarrSettings.random_upgrades !== false;
|
||||
}
|
||||
|
||||
// Update home page connection status
|
||||
app.updateHomeConnectionStatus();
|
||||
|
||||
// Update log connection status if on logs page
|
||||
if (app.elements.logsContainer && app.elements.logsContainer.style.display !== 'none') {
|
||||
app.updateLogsConnectionStatus();
|
||||
}
|
||||
|
||||
// Initialize save buttons state
|
||||
this.updateSaveButtonState(false);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading Lidarr settings:', error);
|
||||
|
||||
// Default values
|
||||
this.elements.apiUrlInput.value = '';
|
||||
this.elements.apiKeyInput.value = '';
|
||||
this.elements.apiUrlInput.dataset.originalValue = '';
|
||||
this.elements.apiKeyInput.dataset.originalValue = '';
|
||||
app.configuredApps.lidarr = false;
|
||||
|
||||
// Update home page connection status
|
||||
app.updateHomeConnectionStatus();
|
||||
|
||||
// Update log connection status if on logs page
|
||||
if (app.elements.logsContainer && app.elements.logsContainer.style.display !== 'none') {
|
||||
app.updateLogsConnectionStatus();
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(error => console.error('Error loading settings:', error));
|
||||
},
|
||||
|
||||
checkForChanges: function() {
|
||||
if (!app.originalSettings.lidarr) return false; // Don't check if original settings not loaded
|
||||
|
||||
let hasChanges = false;
|
||||
const lidarrSettings = app.originalSettings.lidarr || {};
|
||||
|
||||
// API connection settings
|
||||
if (this.elements.apiUrlInput && this.elements.apiUrlInput.dataset.originalValue !== undefined &&
|
||||
this.elements.apiUrlInput.value !== this.elements.apiUrlInput.dataset.originalValue) hasChanges = true;
|
||||
if (this.elements.apiKeyInput && this.elements.apiKeyInput.dataset.originalValue !== undefined &&
|
||||
this.elements.apiKeyInput.value !== this.elements.apiKeyInput.dataset.originalValue) hasChanges = true;
|
||||
|
||||
// Check all settings directly from the lidarr object
|
||||
if (this.elements.huntMissingAlbumsInput && parseInt(this.elements.huntMissingAlbumsInput.value) !== lidarrSettings.hunt_missing_albums) hasChanges = true;
|
||||
if (this.elements.huntUpgradeTracksInput && parseInt(this.elements.huntUpgradeTracksInput.value) !== lidarrSettings.hunt_upgrade_tracks) hasChanges = true;
|
||||
if (this.elements.sleepDurationInput && parseInt(this.elements.sleepDurationInput.value) !== lidarrSettings.sleep_duration) hasChanges = true;
|
||||
if (this.elements.stateResetIntervalInput && parseInt(this.elements.stateResetIntervalInput.value) !== lidarrSettings.state_reset_interval_hours) hasChanges = true;
|
||||
if (this.elements.monitoredOnlyInput && this.elements.monitoredOnlyInput.checked !== lidarrSettings.monitored_only) hasChanges = true;
|
||||
if (this.elements.skipFutureReleasesInput && this.elements.skipFutureReleasesInput.checked !== lidarrSettings.skip_future_releases) hasChanges = true;
|
||||
if (this.elements.skipArtistRefreshInput && this.elements.skipArtistRefreshInput.checked !== lidarrSettings.skip_artist_refresh) hasChanges = true;
|
||||
|
||||
// Check Advanced Settings directly from the lidarr object as well
|
||||
if (this.elements.apiTimeoutInput && parseInt(this.elements.apiTimeoutInput.value) !== lidarrSettings.api_timeout) hasChanges = true;
|
||||
if (this.elements.debugModeInput && this.elements.debugModeInput.checked !== lidarrSettings.debug_mode) hasChanges = true;
|
||||
if (this.elements.commandWaitDelayInput && parseInt(this.elements.commandWaitDelayInput.value) !== lidarrSettings.command_wait_delay) hasChanges = true;
|
||||
if (this.elements.commandWaitAttemptsInput && parseInt(this.elements.commandWaitAttemptsInput.value) !== lidarrSettings.command_wait_attempts) hasChanges = true;
|
||||
if (this.elements.minimumDownloadQueueSizeInput && parseInt(this.elements.minimumDownloadQueueSizeInput.value) !== lidarrSettings.minimum_download_queue_size) hasChanges = true;
|
||||
if (this.elements.randomMissingInput && this.elements.randomMissingInput.checked !== lidarrSettings.random_missing) hasChanges = true;
|
||||
if (this.elements.randomUpgradesInput && this.elements.randomUpgradesInput.checked !== lidarrSettings.random_upgrades) hasChanges = true;
|
||||
|
||||
// Update save buttons state
|
||||
this.updateSaveButtonState(hasChanges);
|
||||
|
||||
return hasChanges;
|
||||
},
|
||||
|
||||
updateSaveButtonState: function(hasChanges) {
|
||||
// Use the huntarrUI instance to access elements
|
||||
const saveButton = window.huntarrUI?.elements?.saveSettingsButton;
|
||||
if (saveButton) {
|
||||
saveButton.disabled = !hasChanges;
|
||||
if (hasChanges) {
|
||||
saveButton.classList.remove('disabled-button'); // Assuming 'disabled-button' class handles visual state
|
||||
// Assuming app.updateDurationDisplay exists and is accessible
|
||||
if (app && typeof app.updateDurationDisplay === 'function') {
|
||||
app.updateDurationDisplay(seconds, this.elements.sleepDurationHoursSpan);
|
||||
} else {
|
||||
saveButton.classList.add('disabled-button');
|
||||
console.warn("app.updateDurationDisplay not found, sleep duration text might not update.");
|
||||
}
|
||||
}
|
||||
// Remove references to non-existent bottom button
|
||||
},
|
||||
|
||||
getSettingsPayload: function() {
|
||||
return {
|
||||
app_type: 'lidarr',
|
||||
api_url: this.elements.apiUrlInput ? this.elements.apiUrlInput.value || '' : '',
|
||||
api_key: this.elements.apiKeyInput ? this.elements.apiKeyInput.value || '' : '',
|
||||
// Combined settings - all at top level, no nesting
|
||||
hunt_missing_albums: this.elements.huntMissingAlbumsInput ? parseInt(this.elements.huntMissingAlbumsInput.value) || 0 : 0,
|
||||
hunt_upgrade_tracks: this.elements.huntUpgradeTracksInput ? parseInt(this.elements.huntUpgradeTracksInput.value) || 0 : 0,
|
||||
sleep_duration: this.elements.sleepDurationInput ? parseInt(this.elements.sleepDurationInput.value) || 900 : 900,
|
||||
state_reset_interval_hours: this.elements.stateResetIntervalInput ? parseInt(this.elements.stateResetIntervalInput.value) || 168 : 168,
|
||||
monitored_only: this.elements.monitoredOnlyInput ? this.elements.monitoredOnlyInput.checked : true,
|
||||
skip_future_releases: this.elements.skipFutureReleasesInput ? this.elements.skipFutureReleasesInput.checked : true,
|
||||
skip_artist_refresh: this.elements.skipArtistRefreshInput ? this.elements.skipArtistRefreshInput.checked : false,
|
||||
|
||||
// Include advanced settings at the same level
|
||||
debug_mode: this.elements.debugModeInput ? this.elements.debugModeInput.checked : false,
|
||||
command_wait_delay: this.elements.commandWaitDelayInput ? parseInt(this.elements.commandWaitDelayInput.value) || 1 : 1,
|
||||
command_wait_attempts: this.elements.commandWaitAttemptsInput ? parseInt(this.elements.commandWaitAttemptsInput.value) || 600 : 600,
|
||||
minimum_download_queue_size: this.elements.minimumDownloadQueueSizeInput ? parseInt(this.elements.minimumDownloadQueueSizeInput.value) || -1 : -1,
|
||||
random_missing: this.elements.randomMissingInput ? this.elements.randomMissingInput.checked : true,
|
||||
random_upgrades: this.elements.randomUpgradesInput ? this.elements.randomUpgradesInput.checked : true,
|
||||
api_timeout: this.elements.apiTimeoutInput ? parseInt(this.elements.apiTimeoutInput.value) || 60 : 60,
|
||||
log_refresh_interval_seconds: 30 // Default value
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize Lidarr module
|
||||
lidarrModule.init();
|
||||
|
||||
// Override app.saveSettings to handle Lidarr-specific logic when Lidarr is active
|
||||
const originalSaveSettings = app.saveSettings;
|
||||
app.saveSettings = function() {
|
||||
if (app.currentApp === 'lidarr') {
|
||||
if (!lidarrModule.checkForChanges()) {
|
||||
// If no changes, don't do anything
|
||||
return;
|
||||
}
|
||||
|
||||
const settings = lidarrModule.getSettingsPayload();
|
||||
|
||||
fetch('/api/settings', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(settings)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Store the original values in data attributes for comparison
|
||||
if (lidarrModule.elements.apiUrlInput) lidarrModule.elements.apiUrlInput.dataset.originalValue = settings.api_url;
|
||||
if (lidarrModule.elements.apiKeyInput) lidarrModule.elements.apiKeyInput.dataset.originalValue = settings.api_key;
|
||||
|
||||
// Update the rest of originalSettings
|
||||
if (settings.lidarr) app.originalSettings.lidarr = {...settings.lidarr};
|
||||
|
||||
// Update configuration status
|
||||
app.configuredApps.lidarr = !!(settings.api_url && settings.api_key);
|
||||
|
||||
// Update connection status
|
||||
app.updateConnectionStatus();
|
||||
|
||||
// Update home page connection status
|
||||
app.updateHomeConnectionStatus();
|
||||
|
||||
// Update logs connection status
|
||||
app.updateLogsConnectionStatus();
|
||||
|
||||
// Disable save buttons
|
||||
lidarrModule.updateSaveButtonState(false);
|
||||
|
||||
// Show success message
|
||||
if (data.changes_made) {
|
||||
alert('Settings saved successfully and cycle restarted to apply changes!');
|
||||
} else {
|
||||
alert('No changes detected.');
|
||||
}
|
||||
} else {
|
||||
alert('Error saving settings: ' + (data.message || 'Unknown error'));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error saving settings:', error);
|
||||
alert('Error saving settings: ' + error.message);
|
||||
});
|
||||
} else if (originalSaveSettings) {
|
||||
// Call the original if we're not handling Lidarr
|
||||
originalSaveSettings.call(app);
|
||||
}
|
||||
};
|
||||
|
||||
// Add the Lidarr module to the app for reference
|
||||
app.lidarrModule = lidarrModule;
|
||||
|
||||
})(window.huntarrApp);
|
||||
// REMOVED loadSettings - Handled by huntarrUI.loadAllSettings
|
||||
// loadSettings: function() { ... },
|
||||
|
||||
// REMOVED checkForChanges - Handled by huntarrUI.handleSettingChange and updateSaveResetButtonState
|
||||
// checkForChanges: function() { ... },
|
||||
|
||||
// REMOVED updateSaveButtonState - Handled by huntarrUI.updateSaveResetButtonState
|
||||
// updateSaveButtonState: function(hasChanges) { ... },
|
||||
|
||||
// REMOVED getSettingsPayload - Handled by huntarrUI.collectSettingsFromForm
|
||||
// getSettingsPayload: function() { ... },
|
||||
|
||||
// REMOVED saveSettings override - Handled by huntarrUI.saveSettings
|
||||
// const originalSaveSettings = app.saveSettings;
|
||||
// app.saveSettings = function() { ... };
|
||||
};
|
||||
|
||||
// Initialize Lidarr module
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (document.getElementById('lidarrSettings')) {
|
||||
lidarrModule.init();
|
||||
if (app) {
|
||||
app.lidarrModule = lidarrModule;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
})(window.huntarrUI); // Pass the global UI object
|
||||
|
||||
@@ -1,353 +1,75 @@
|
||||
// Radarr-specific functionality
|
||||
|
||||
(function(app) {
|
||||
// Add Radarr-specific initialization
|
||||
if (!app) {
|
||||
console.error("Huntarr App core is not loaded!");
|
||||
return;
|
||||
}
|
||||
|
||||
const radarrModule = {
|
||||
// DOM elements specific to Radarr
|
||||
elements: {
|
||||
apiUrlInput: document.getElementById('radarr_api_url'),
|
||||
apiKeyInput: document.getElementById('radarr_api_key'),
|
||||
|
||||
// Settings form elements
|
||||
huntMissingMoviesInput: document.getElementById('hunt_missing_movies'),
|
||||
huntUpgradeMoviesInput: document.getElementById('hunt_upgrade_movies'),
|
||||
sleepDurationInput: document.getElementById('radarr_sleep_duration'),
|
||||
sleepDurationHoursSpan: document.getElementById('radarr_sleep_duration_hours'),
|
||||
stateResetIntervalInput: document.getElementById('radarr_state_reset_interval_hours'),
|
||||
monitoredOnlyInput: document.getElementById('radarr_monitored_only'),
|
||||
randomMissingInput: document.getElementById('radarr_random_missing'),
|
||||
randomUpgradesInput: document.getElementById('radarr_random_upgrades'),
|
||||
skipFutureReleasesInput: document.getElementById('skip_future_releases'),
|
||||
skipMovieRefreshInput: document.getElementById('skip_movie_refresh'),
|
||||
|
||||
// Advanced settings
|
||||
apiTimeoutInput: document.getElementById('radarr_api_timeout'),
|
||||
debugModeInput: document.getElementById('radarr_debug_mode'),
|
||||
commandWaitDelayInput: document.getElementById('radarr_command_wait_delay'),
|
||||
commandWaitAttemptsInput: document.getElementById('radarr_command_wait_attempts'),
|
||||
minimumDownloadQueueSizeInput: document.getElementById('radarr_minimum_download_queue_size')
|
||||
},
|
||||
|
||||
elements: {},
|
||||
|
||||
init: function() {
|
||||
console.log('[Radarr Module] Initializing...');
|
||||
this.cacheElements();
|
||||
this.setupEventListeners();
|
||||
|
||||
// Extend the core app with Radarr-specific implementations
|
||||
app.loadSettingsRadarr = this.loadSettings.bind(this);
|
||||
|
||||
// Override the load settings with Radarr implementation when Radarr is active
|
||||
const originalLoadSettings = app.loadSettings;
|
||||
app.loadSettings = function(appType) {
|
||||
if (appType === 'radarr') {
|
||||
this.loadSettingsRadarr();
|
||||
} else if (originalLoadSettings) {
|
||||
// Only call the original if we're not handling Radarr
|
||||
originalLoadSettings.call(this, appType);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
cacheElements: function() {
|
||||
// Cache elements specific to the Radarr settings form
|
||||
this.elements.apiUrlInput = document.getElementById('radarr_api_url');
|
||||
this.elements.apiKeyInput = document.getElementById('radarr_api_key');
|
||||
this.elements.huntMissingMoviesInput = document.getElementById('hunt_missing_movies');
|
||||
this.elements.huntUpgradeMoviesInput = document.getElementById('hunt_upgrade_movies');
|
||||
this.elements.sleepDurationInput = document.getElementById('radarr_sleep_duration');
|
||||
this.elements.sleepDurationHoursSpan = document.getElementById('radarr_sleep_duration_hours');
|
||||
this.elements.stateResetIntervalInput = document.getElementById('radarr_state_reset_interval_hours');
|
||||
this.elements.monitoredOnlyInput = document.getElementById('radarr_monitored_only');
|
||||
this.elements.skipFutureReleasesInput = document.getElementById('skip_future_releases'); // Note: ID might be shared
|
||||
this.elements.skipMovieRefreshInput = document.getElementById('skip_movie_refresh');
|
||||
this.elements.randomMissingInput = document.getElementById('radarr_random_missing');
|
||||
this.elements.randomUpgradesInput = document.getElementById('radarr_random_upgrades');
|
||||
this.elements.debugModeInput = document.getElementById('radarr_debug_mode');
|
||||
this.elements.apiTimeoutInput = document.getElementById('radarr_api_timeout');
|
||||
this.elements.commandWaitDelayInput = document.getElementById('radarr_command_wait_delay');
|
||||
this.elements.commandWaitAttemptsInput = document.getElementById('radarr_command_wait_attempts');
|
||||
this.elements.minimumDownloadQueueSizeInput = document.getElementById('radarr_minimum_download_queue_size');
|
||||
// Add any other Radarr-specific elements
|
||||
},
|
||||
|
||||
setupEventListeners: function() {
|
||||
// Add input event listeners for Radarr-specific settings
|
||||
const inputs = [
|
||||
this.elements.apiUrlInput,
|
||||
this.elements.apiKeyInput,
|
||||
this.elements.huntMissingMoviesInput,
|
||||
this.elements.huntUpgradeMoviesInput,
|
||||
this.elements.sleepDurationInput,
|
||||
this.elements.stateResetIntervalInput,
|
||||
this.elements.apiTimeoutInput,
|
||||
this.elements.commandWaitDelayInput,
|
||||
this.elements.commandWaitAttemptsInput,
|
||||
this.elements.minimumDownloadQueueSizeInput
|
||||
];
|
||||
|
||||
inputs.forEach(input => {
|
||||
if (input) {
|
||||
input.addEventListener('input', this.checkForChanges.bind(this));
|
||||
}
|
||||
});
|
||||
|
||||
const checkboxes = [
|
||||
this.elements.monitoredOnlyInput,
|
||||
this.elements.randomMissingInput,
|
||||
this.elements.randomUpgradesInput,
|
||||
this.elements.skipFutureReleasesInput,
|
||||
this.elements.skipMovieRefreshInput,
|
||||
this.elements.debugModeInput
|
||||
];
|
||||
|
||||
checkboxes.forEach(checkbox => {
|
||||
if (checkbox) {
|
||||
checkbox.addEventListener('change', this.checkForChanges.bind(this));
|
||||
}
|
||||
});
|
||||
|
||||
// Add special handler for sleep duration to update display
|
||||
// Keep listeners ONLY for elements with specific UI updates beyond simple value changes
|
||||
if (this.elements.sleepDurationInput) {
|
||||
this.elements.sleepDurationInput.addEventListener('input', () => {
|
||||
this.updateSleepDurationDisplay();
|
||||
this.checkForChanges();
|
||||
// No need to call checkForChanges here, handled by delegation
|
||||
});
|
||||
}
|
||||
// Remove other input listeners previously used for checkForChanges
|
||||
},
|
||||
|
||||
|
||||
updateSleepDurationDisplay: function() {
|
||||
// This function remains as it updates a specific UI element
|
||||
if (this.elements.sleepDurationInput && this.elements.sleepDurationHoursSpan) {
|
||||
const seconds = parseInt(this.elements.sleepDurationInput.value) || 900;
|
||||
app.updateDurationDisplay(seconds, this.elements.sleepDurationHoursSpan);
|
||||
}
|
||||
},
|
||||
|
||||
loadSettings: function() {
|
||||
fetch('/api/settings')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Store original settings for comparison
|
||||
app.originalSettings = JSON.parse(JSON.stringify(data));
|
||||
|
||||
// Get app-specific settings directly from radarr section instead of huntarr/advanced
|
||||
const radarrSettings = data.radarr || {};
|
||||
|
||||
// For Radarr, load from app-settings endpoint
|
||||
fetch(`/api/app-settings?app=radarr`)
|
||||
.then(response => response.json())
|
||||
.then(appData => {
|
||||
if (appData.success) {
|
||||
this.elements.apiUrlInput.value = appData.api_url || '';
|
||||
this.elements.apiKeyInput.value = appData.api_key || '';
|
||||
|
||||
// Store original values in data attributes for comparison
|
||||
this.elements.apiUrlInput.dataset.originalValue = appData.api_url || '';
|
||||
this.elements.apiKeyInput.dataset.originalValue = appData.api_key || '';
|
||||
|
||||
// Update configured status
|
||||
app.configuredApps.radarr = !!(appData.api_url && appData.api_key);
|
||||
}
|
||||
|
||||
// Radarr-specific settings - all from radarrSettings directly
|
||||
if (this.elements.huntMissingMoviesInput) {
|
||||
this.elements.huntMissingMoviesInput.value = radarrSettings.hunt_missing_movies !== undefined ? radarrSettings.hunt_missing_movies : 1;
|
||||
}
|
||||
if (this.elements.huntUpgradeMoviesInput) {
|
||||
this.elements.huntUpgradeMoviesInput.value = radarrSettings.hunt_upgrade_movies !== undefined ? radarrSettings.hunt_upgrade_movies : 0;
|
||||
}
|
||||
if (this.elements.sleepDurationInput) {
|
||||
this.elements.sleepDurationInput.value = radarrSettings.sleep_duration || 900;
|
||||
this.updateSleepDurationDisplay();
|
||||
}
|
||||
if (this.elements.stateResetIntervalInput) {
|
||||
this.elements.stateResetIntervalInput.value = radarrSettings.state_reset_interval_hours || 168;
|
||||
}
|
||||
if (this.elements.monitoredOnlyInput) {
|
||||
this.elements.monitoredOnlyInput.checked = radarrSettings.monitored_only !== false;
|
||||
}
|
||||
if (this.elements.skipFutureReleasesInput) {
|
||||
this.elements.skipFutureReleasesInput.checked = radarrSettings.skip_future_releases !== false;
|
||||
}
|
||||
if (this.elements.skipMovieRefreshInput) {
|
||||
this.elements.skipMovieRefreshInput.checked = radarrSettings.skip_movie_refresh === true;
|
||||
}
|
||||
|
||||
// Advanced settings - from the same radarrSettings object
|
||||
if (this.elements.apiTimeoutInput) {
|
||||
this.elements.apiTimeoutInput.value = radarrSettings.api_timeout || 60;
|
||||
}
|
||||
if (this.elements.debugModeInput) {
|
||||
this.elements.debugModeInput.checked = radarrSettings.debug_mode === true;
|
||||
}
|
||||
if (this.elements.commandWaitDelayInput) {
|
||||
this.elements.commandWaitDelayInput.value = radarrSettings.command_wait_delay || 1;
|
||||
}
|
||||
if (this.elements.commandWaitAttemptsInput) {
|
||||
this.elements.commandWaitAttemptsInput.value = radarrSettings.command_wait_attempts || 600;
|
||||
}
|
||||
if (this.elements.minimumDownloadQueueSizeInput) {
|
||||
this.elements.minimumDownloadQueueSizeInput.value = radarrSettings.minimum_download_queue_size || -1;
|
||||
}
|
||||
if (this.elements.randomMissingInput) {
|
||||
this.elements.randomMissingInput.checked = radarrSettings.random_missing !== false;
|
||||
}
|
||||
if (this.elements.randomUpgradesInput) {
|
||||
this.elements.randomUpgradesInput.checked = radarrSettings.random_upgrades !== false;
|
||||
}
|
||||
|
||||
// Update home page connection status
|
||||
app.updateHomeConnectionStatus();
|
||||
|
||||
// Update log connection status if on logs page
|
||||
if (app.elements.logsContainer && app.elements.logsContainer.style.display !== 'none') {
|
||||
app.updateLogsConnectionStatus();
|
||||
}
|
||||
|
||||
// Initialize save buttons state
|
||||
this.updateSaveButtonState(false);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading Radarr settings:', error);
|
||||
|
||||
// Default values
|
||||
this.elements.apiUrlInput.value = '';
|
||||
this.elements.apiKeyInput.value = '';
|
||||
this.elements.apiUrlInput.dataset.originalValue = '';
|
||||
this.elements.apiKeyInput.dataset.originalValue = '';
|
||||
app.configuredApps.radarr = false;
|
||||
|
||||
// Update home page connection status
|
||||
app.updateHomeConnectionStatus();
|
||||
|
||||
// Update log connection status if on logs page
|
||||
if (app.elements.logsContainer && app.elements.logsContainer.style.display !== 'none') {
|
||||
app.updateLogsConnectionStatus();
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(error => console.error('Error loading settings:', error));
|
||||
},
|
||||
|
||||
checkForChanges: function() {
|
||||
if (!app.originalSettings.radarr) return false; // Don't check if original settings not loaded
|
||||
|
||||
let hasChanges = false;
|
||||
const radarrSettings = app.originalSettings.radarr || {};
|
||||
|
||||
// API connection settings
|
||||
if (this.elements.apiUrlInput && this.elements.apiUrlInput.dataset.originalValue !== undefined &&
|
||||
this.elements.apiUrlInput.value !== this.elements.apiUrlInput.dataset.originalValue) hasChanges = true;
|
||||
if (this.elements.apiKeyInput && this.elements.apiKeyInput.dataset.originalValue !== undefined &&
|
||||
this.elements.apiKeyInput.value !== this.elements.apiKeyInput.dataset.originalValue) hasChanges = true;
|
||||
|
||||
// Check all settings directly from the radarr object
|
||||
if (this.elements.huntMissingMoviesInput && parseInt(this.elements.huntMissingMoviesInput.value) !== radarrSettings.hunt_missing_movies) hasChanges = true;
|
||||
if (this.elements.huntUpgradeMoviesInput && parseInt(this.elements.huntUpgradeMoviesInput.value) !== radarrSettings.hunt_upgrade_movies) hasChanges = true;
|
||||
if (this.elements.sleepDurationInput && parseInt(this.elements.sleepDurationInput.value) !== radarrSettings.sleep_duration) hasChanges = true;
|
||||
if (this.elements.stateResetIntervalInput && parseInt(this.elements.stateResetIntervalInput.value) !== radarrSettings.state_reset_interval_hours) hasChanges = true;
|
||||
if (this.elements.monitoredOnlyInput && this.elements.monitoredOnlyInput.checked !== radarrSettings.monitored_only) hasChanges = true;
|
||||
if (this.elements.skipFutureReleasesInput && this.elements.skipFutureReleasesInput.checked !== radarrSettings.skip_future_releases) hasChanges = true;
|
||||
if (this.elements.skipMovieRefreshInput && this.elements.skipMovieRefreshInput.checked !== radarrSettings.skip_movie_refresh) hasChanges = true;
|
||||
|
||||
// Check Advanced Settings directly from the radarr object as well
|
||||
if (this.elements.apiTimeoutInput && parseInt(this.elements.apiTimeoutInput.value) !== radarrSettings.api_timeout) hasChanges = true;
|
||||
if (this.elements.debugModeInput && this.elements.debugModeInput.checked !== radarrSettings.debug_mode) hasChanges = true;
|
||||
if (this.elements.commandWaitDelayInput && parseInt(this.elements.commandWaitDelayInput.value) !== radarrSettings.command_wait_delay) hasChanges = true;
|
||||
if (this.elements.commandWaitAttemptsInput && parseInt(this.elements.commandWaitAttemptsInput.value) !== radarrSettings.command_wait_attempts) hasChanges = true;
|
||||
if (this.elements.minimumDownloadQueueSizeInput && parseInt(this.elements.minimumDownloadQueueSizeInput.value) !== radarrSettings.minimum_download_queue_size) hasChanges = true;
|
||||
if (this.elements.randomMissingInput && this.elements.randomMissingInput.checked !== radarrSettings.random_missing) hasChanges = true;
|
||||
if (this.elements.randomUpgradesInput && this.elements.randomUpgradesInput.checked !== radarrSettings.random_upgrades) hasChanges = true;
|
||||
|
||||
// Update save buttons state
|
||||
this.updateSaveButtonState(hasChanges);
|
||||
|
||||
return hasChanges;
|
||||
},
|
||||
|
||||
updateSaveButtonState: function(hasChanges) {
|
||||
// Use the huntarrUI instance to access elements
|
||||
const saveButton = window.huntarrUI?.elements?.saveSettingsButton;
|
||||
if (saveButton) {
|
||||
saveButton.disabled = !hasChanges;
|
||||
if (hasChanges) {
|
||||
saveButton.classList.remove('disabled-button'); // Assuming 'disabled-button' class handles visual state
|
||||
// Assuming app.updateDurationDisplay exists and is accessible
|
||||
if (app && typeof app.updateDurationDisplay === 'function') {
|
||||
app.updateDurationDisplay(seconds, this.elements.sleepDurationHoursSpan);
|
||||
} else {
|
||||
saveButton.classList.add('disabled-button');
|
||||
console.warn("app.updateDurationDisplay not found, sleep duration text might not update.");
|
||||
}
|
||||
}
|
||||
// Remove references to non-existent bottom button
|
||||
},
|
||||
|
||||
getSettingsPayload: function() {
|
||||
return {
|
||||
app_type: 'radarr',
|
||||
api_url: this.elements.apiUrlInput ? this.elements.apiUrlInput.value || '' : '',
|
||||
api_key: this.elements.apiKeyInput ? this.elements.apiKeyInput.value || '' : '',
|
||||
// Combined settings - all at top level, no nesting
|
||||
hunt_missing_movies: this.elements.huntMissingMoviesInput ? parseInt(this.elements.huntMissingMoviesInput.value) || 0 : 0,
|
||||
hunt_upgrade_movies: this.elements.huntUpgradeMoviesInput ? parseInt(this.elements.huntUpgradeMoviesInput.value) || 0 : 0,
|
||||
sleep_duration: this.elements.sleepDurationInput ? parseInt(this.elements.sleepDurationInput.value) || 900 : 900,
|
||||
state_reset_interval_hours: this.elements.stateResetIntervalInput ? parseInt(this.elements.stateResetIntervalInput.value) || 168 : 168,
|
||||
monitored_only: this.elements.monitoredOnlyInput ? this.elements.monitoredOnlyInput.checked : true,
|
||||
skip_future_releases: this.elements.skipFutureReleasesInput ? this.elements.skipFutureReleasesInput.checked : true,
|
||||
skip_movie_refresh: this.elements.skipMovieRefreshInput ? this.elements.skipMovieRefreshInput.checked : false,
|
||||
|
||||
// Include advanced settings at the same level
|
||||
debug_mode: this.elements.debugModeInput ? this.elements.debugModeInput.checked : false,
|
||||
command_wait_delay: this.elements.commandWaitDelayInput ? parseInt(this.elements.commandWaitDelayInput.value) || 1 : 1,
|
||||
command_wait_attempts: this.elements.commandWaitAttemptsInput ? parseInt(this.elements.commandWaitAttemptsInput.value) || 600 : 600,
|
||||
minimum_download_queue_size: this.elements.minimumDownloadQueueSizeInput ? parseInt(this.elements.minimumDownloadQueueSizeInput.value) || -1 : -1,
|
||||
random_missing: this.elements.randomMissingInput ? this.elements.randomMissingInput.checked : true,
|
||||
random_upgrades: this.elements.randomUpgradesInput ? this.elements.randomUpgradesInput.checked : true,
|
||||
api_timeout: this.elements.apiTimeoutInput ? parseInt(this.elements.apiTimeoutInput.value) || 60 : 60,
|
||||
log_refresh_interval_seconds: 30 // Default value
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize Radarr module
|
||||
radarrModule.init();
|
||||
|
||||
// Override app.saveSettings to handle Radarr-specific logic when Radarr is active
|
||||
const originalSaveSettings = app.saveSettings;
|
||||
app.saveSettings = function() {
|
||||
if (app.currentApp === 'radarr') {
|
||||
if (!radarrModule.checkForChanges()) {
|
||||
// If no changes, don't do anything
|
||||
return;
|
||||
}
|
||||
|
||||
const settings = radarrModule.getSettingsPayload();
|
||||
|
||||
fetch('/api/settings', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(settings)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Store the original values in data attributes for comparison
|
||||
if (radarrModule.elements.apiUrlInput) radarrModule.elements.apiUrlInput.dataset.originalValue = settings.api_url;
|
||||
if (radarrModule.elements.apiKeyInput) radarrModule.elements.apiKeyInput.dataset.originalValue = settings.api_key;
|
||||
|
||||
// Update the rest of originalSettings
|
||||
if (settings.radarr) app.originalSettings.radarr = {...settings.radarr};
|
||||
|
||||
// Update configuration status
|
||||
app.configuredApps.radarr = !!(settings.api_url && settings.api_key);
|
||||
|
||||
// Update connection status
|
||||
app.updateConnectionStatus();
|
||||
|
||||
// Update home page connection status
|
||||
app.updateHomeConnectionStatus();
|
||||
|
||||
// Update logs connection status
|
||||
app.updateLogsConnectionStatus();
|
||||
|
||||
// Disable save buttons
|
||||
radarrModule.updateSaveButtonState(false);
|
||||
|
||||
// Show success message
|
||||
if (data.changes_made) {
|
||||
alert('Settings saved successfully and cycle restarted to apply changes!');
|
||||
} else {
|
||||
alert('No changes detected.');
|
||||
}
|
||||
} else {
|
||||
alert('Error saving settings: ' + (data.message || 'Unknown error'));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error saving settings:', error);
|
||||
alert('Error saving settings: ' + error.message);
|
||||
});
|
||||
} else if (originalSaveSettings) {
|
||||
// Call the original if we're not handling Radarr
|
||||
originalSaveSettings.call(app);
|
||||
}
|
||||
};
|
||||
|
||||
// Add the Radarr module to the app for reference
|
||||
app.radarrModule = radarrModule;
|
||||
|
||||
})(window.huntarrApp);
|
||||
// Initialize Radarr module
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (document.getElementById('radarrSettings')) {
|
||||
radarrModule.init();
|
||||
if (app) {
|
||||
app.radarrModule = radarrModule;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
})(window.huntarrUI); // Pass the global UI object
|
||||
|
||||
@@ -1,460 +1,75 @@
|
||||
// Readarr-specific functionality
|
||||
|
||||
(function(app) {
|
||||
// Add Readarr-specific initialization
|
||||
if (!app) {
|
||||
console.error("Huntarr App core is not loaded!");
|
||||
return;
|
||||
}
|
||||
|
||||
const readarrModule = {
|
||||
// DOM elements specific to Readarr
|
||||
elements: {
|
||||
apiUrlInput: document.getElementById('readarr_api_url'),
|
||||
apiKeyInput: document.getElementById('readarr_api_key'),
|
||||
|
||||
// Settings form elements
|
||||
huntMissingBooksInput: document.getElementById('hunt_missing_books'),
|
||||
huntUpgradeBooksInput: document.getElementById('hunt_upgrade_books'),
|
||||
sleepDurationInput: document.getElementById('readarr_sleep_duration'),
|
||||
sleepDurationHoursSpan: document.getElementById('readarr_sleep_duration_hours'),
|
||||
stateResetIntervalInput: document.getElementById('readarr_state_reset_interval_hours'),
|
||||
monitoredOnlyInput: document.getElementById('readarr_monitored_only'),
|
||||
randomMissingInput: document.getElementById('readarr_random_missing'),
|
||||
randomUpgradesInput: document.getElementById('readarr_random_upgrades'),
|
||||
skipFutureReleasesInput: document.getElementById('readarr_skip_future_releases'),
|
||||
skipAuthorRefreshInput: document.getElementById('skip_author_refresh'),
|
||||
|
||||
// Advanced settings
|
||||
apiTimeoutInput: document.getElementById('readarr_api_timeout'),
|
||||
debugModeInput: document.getElementById('readarr_debug_mode'),
|
||||
commandWaitDelayInput: document.getElementById('readarr_command_wait_delay'),
|
||||
commandWaitAttemptsInput: document.getElementById('readarr_command_wait_attempts'),
|
||||
minimumDownloadQueueSizeInput: document.getElementById('readarr_minimum_download_queue_size')
|
||||
},
|
||||
|
||||
elements: {},
|
||||
|
||||
init: function() {
|
||||
console.log('[Readarr Module] Initializing...');
|
||||
this.cacheElements();
|
||||
this.setupEventListeners();
|
||||
|
||||
// Extend the core app with Readarr-specific implementations
|
||||
app.loadSettingsReadarr = this.loadSettings.bind(this);
|
||||
|
||||
// Override the load settings with Readarr implementation when Readarr is active
|
||||
const originalLoadSettings = app.loadSettings;
|
||||
app.loadSettings = function(appType) {
|
||||
if (appType === 'readarr') {
|
||||
this.loadSettingsReadarr();
|
||||
} else if (originalLoadSettings) {
|
||||
// Only call the original if we're not handling Readarr
|
||||
originalLoadSettings.call(this, appType);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
cacheElements: function() {
|
||||
// Cache elements specific to the Readarr settings form
|
||||
this.elements.apiUrlInput = document.getElementById('readarr_api_url');
|
||||
this.elements.apiKeyInput = document.getElementById('readarr_api_key');
|
||||
this.elements.huntMissingBooksInput = document.getElementById('hunt_missing_books');
|
||||
this.elements.huntUpgradeBooksInput = document.getElementById('hunt_upgrade_books');
|
||||
this.elements.sleepDurationInput = document.getElementById('readarr_sleep_duration');
|
||||
this.elements.sleepDurationHoursSpan = document.getElementById('readarr_sleep_duration_hours');
|
||||
this.elements.stateResetIntervalInput = document.getElementById('readarr_state_reset_interval_hours');
|
||||
this.elements.monitoredOnlyInput = document.getElementById('readarr_monitored_only');
|
||||
this.elements.skipFutureReleasesInput = document.getElementById('readarr_skip_future_releases');
|
||||
this.elements.skipAuthorRefreshInput = document.getElementById('skip_author_refresh');
|
||||
this.elements.randomMissingInput = document.getElementById('readarr_random_missing');
|
||||
this.elements.randomUpgradesInput = document.getElementById('readarr_random_upgrades');
|
||||
this.elements.debugModeInput = document.getElementById('readarr_debug_mode');
|
||||
this.elements.apiTimeoutInput = document.getElementById('readarr_api_timeout');
|
||||
this.elements.commandWaitDelayInput = document.getElementById('readarr_command_wait_delay');
|
||||
this.elements.commandWaitAttemptsInput = document.getElementById('readarr_command_wait_attempts');
|
||||
this.elements.minimumDownloadQueueSizeInput = document.getElementById('readarr_minimum_download_queue_size');
|
||||
// Add any other Readarr-specific elements
|
||||
},
|
||||
|
||||
setupEventListeners: function() {
|
||||
// Add input event listeners for Readarr-specific settings
|
||||
const inputs = [
|
||||
this.elements.apiUrlInput,
|
||||
this.elements.apiKeyInput,
|
||||
this.elements.huntMissingBooksInput,
|
||||
this.elements.huntUpgradeBooksInput,
|
||||
this.elements.sleepDurationInput,
|
||||
this.elements.stateResetIntervalInput,
|
||||
this.elements.apiTimeoutInput,
|
||||
this.elements.commandWaitDelayInput,
|
||||
this.elements.commandWaitAttemptsInput,
|
||||
this.elements.minimumDownloadQueueSizeInput
|
||||
];
|
||||
|
||||
inputs.forEach(input => {
|
||||
if (input) {
|
||||
input.addEventListener('input', this.checkForChanges.bind(this));
|
||||
}
|
||||
});
|
||||
|
||||
const checkboxes = [
|
||||
this.elements.monitoredOnlyInput,
|
||||
this.elements.randomMissingInput,
|
||||
this.elements.randomUpgradesInput,
|
||||
this.elements.skipFutureReleasesInput,
|
||||
this.elements.skipAuthorRefreshInput,
|
||||
this.elements.debugModeInput
|
||||
];
|
||||
|
||||
checkboxes.forEach(checkbox => {
|
||||
if (checkbox) {
|
||||
checkbox.addEventListener('change', this.checkForChanges.bind(this));
|
||||
}
|
||||
});
|
||||
|
||||
// Add special handler for sleep duration to update display
|
||||
// Keep listeners ONLY for elements with specific UI updates beyond simple value changes
|
||||
if (this.elements.sleepDurationInput) {
|
||||
this.elements.sleepDurationInput.addEventListener('input', () => {
|
||||
this.updateSleepDurationDisplay();
|
||||
this.checkForChanges();
|
||||
// No need to call checkForChanges here, handled by delegation
|
||||
});
|
||||
}
|
||||
// Remove other input listeners previously used for checkForChanges
|
||||
},
|
||||
|
||||
|
||||
updateSleepDurationDisplay: function() {
|
||||
// This function remains as it updates a specific UI element
|
||||
if (this.elements.sleepDurationInput && this.elements.sleepDurationHoursSpan) {
|
||||
const seconds = parseInt(this.elements.sleepDurationInput.value) || 900;
|
||||
app.updateDurationDisplay(seconds, this.elements.sleepDurationHoursSpan);
|
||||
}
|
||||
},
|
||||
|
||||
loadSettings: function() {
|
||||
fetch('/api/settings')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Store original settings for comparison
|
||||
app.originalSettings = JSON.parse(JSON.stringify(data));
|
||||
|
||||
// Get app-specific settings directly from readarr section instead of huntarr/advanced
|
||||
const readarrSettings = data.readarr || {};
|
||||
|
||||
// For Readarr, load from app-settings endpoint
|
||||
fetch(`/api/app-settings?app=readarr`)
|
||||
.then(response => response.json())
|
||||
.then(appData => {
|
||||
if (appData.success) {
|
||||
this.elements.apiUrlInput.value = appData.api_url || '';
|
||||
this.elements.apiKeyInput.value = appData.api_key || '';
|
||||
|
||||
// Store original values in data attributes for comparison
|
||||
this.elements.apiUrlInput.dataset.originalValue = appData.api_url || '';
|
||||
this.elements.apiKeyInput.dataset.originalValue = appData.api_key || '';
|
||||
|
||||
// Update configured status
|
||||
app.configuredApps.readarr = !!(appData.api_url && appData.api_key);
|
||||
}
|
||||
|
||||
// Readarr-specific settings - all from readarrSettings directly
|
||||
if (this.elements.huntMissingBooksInput) {
|
||||
this.elements.huntMissingBooksInput.value = readarrSettings.hunt_missing_books !== undefined ? readarrSettings.hunt_missing_books : 1;
|
||||
}
|
||||
if (this.elements.huntUpgradeBooksInput) {
|
||||
this.elements.huntUpgradeBooksInput.value = readarrSettings.hunt_upgrade_books !== undefined ? readarrSettings.hunt_upgrade_books : 0;
|
||||
}
|
||||
if (this.elements.sleepDurationInput) {
|
||||
this.elements.sleepDurationInput.value = readarrSettings.sleep_duration || 900;
|
||||
this.updateSleepDurationDisplay();
|
||||
}
|
||||
if (this.elements.stateResetIntervalInput) {
|
||||
this.elements.stateResetIntervalInput.value = readarrSettings.state_reset_interval_hours || 168;
|
||||
}
|
||||
if (this.elements.monitoredOnlyInput) {
|
||||
this.elements.monitoredOnlyInput.checked = readarrSettings.monitored_only !== false;
|
||||
}
|
||||
if (this.elements.skipFutureReleasesInput) {
|
||||
this.elements.skipFutureReleasesInput.checked = readarrSettings.skip_future_releases !== false;
|
||||
}
|
||||
if (this.elements.skipAuthorRefreshInput) {
|
||||
this.elements.skipAuthorRefreshInput.checked = readarrSettings.skip_author_refresh === true;
|
||||
}
|
||||
|
||||
// Advanced settings - from the same readarrSettings object
|
||||
if (this.elements.apiTimeoutInput) {
|
||||
this.elements.apiTimeoutInput.value = readarrSettings.api_timeout || 60;
|
||||
}
|
||||
if (this.elements.debugModeInput) {
|
||||
this.elements.debugModeInput.checked = readarrSettings.debug_mode === true;
|
||||
}
|
||||
if (this.elements.commandWaitDelayInput) {
|
||||
this.elements.commandWaitDelayInput.value = readarrSettings.command_wait_delay || 1;
|
||||
}
|
||||
if (this.elements.commandWaitAttemptsInput) {
|
||||
this.elements.commandWaitAttemptsInput.value = readarrSettings.command_wait_attempts || 600;
|
||||
}
|
||||
if (this.elements.minimumDownloadQueueSizeInput) {
|
||||
this.elements.minimumDownloadQueueSizeInput.value = readarrSettings.minimum_download_queue_size || -1;
|
||||
}
|
||||
if (this.elements.randomMissingInput) {
|
||||
this.elements.randomMissingInput.checked = readarrSettings.random_missing !== false;
|
||||
}
|
||||
if (this.elements.randomUpgradesInput) {
|
||||
this.elements.randomUpgradesInput.checked = readarrSettings.random_upgrades !== false;
|
||||
}
|
||||
|
||||
// Update home page connection status
|
||||
app.updateHomeConnectionStatus();
|
||||
|
||||
// Update log connection status if on logs page
|
||||
if (app.elements.logsContainer && app.elements.logsContainer.style.display !== 'none') {
|
||||
app.updateLogsConnectionStatus();
|
||||
}
|
||||
|
||||
// Initialize save buttons state
|
||||
this.updateSaveButtonState(false);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading Readarr settings:', error);
|
||||
|
||||
// Default values
|
||||
this.elements.apiUrlInput.value = '';
|
||||
this.elements.apiKeyInput.value = '';
|
||||
this.elements.apiUrlInput.dataset.originalValue = '';
|
||||
this.elements.apiKeyInput.dataset.originalValue = '';
|
||||
app.configuredApps.readarr = false;
|
||||
|
||||
// Update home page connection status
|
||||
app.updateHomeConnectionStatus();
|
||||
|
||||
// Update log connection status if on logs page
|
||||
if (app.elements.logsContainer && app.elements.logsContainer.style.display !== 'none') {
|
||||
app.updateLogsConnectionStatus();
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(error => console.error('Error loading settings:', error));
|
||||
},
|
||||
|
||||
checkForChanges: function() {
|
||||
if (!app.originalSettings.readarr) return false; // Don't check if original settings not loaded
|
||||
|
||||
let hasChanges = false;
|
||||
const readarrSettings = app.originalSettings.readarr || {};
|
||||
|
||||
// API connection settings
|
||||
if (this.elements.apiUrlInput && this.elements.apiUrlInput.dataset.originalValue !== undefined &&
|
||||
this.elements.apiUrlInput.value !== this.elements.apiUrlInput.dataset.originalValue) hasChanges = true;
|
||||
if (this.elements.apiKeyInput && this.elements.apiKeyInput.dataset.originalValue !== undefined &&
|
||||
this.elements.apiKeyInput.value !== this.elements.apiKeyInput.dataset.originalValue) hasChanges = true;
|
||||
|
||||
// Check all settings directly from the readarr object
|
||||
if (this.elements.huntMissingBooksInput && parseInt(this.elements.huntMissingBooksInput.value) !== readarrSettings.hunt_missing_books) hasChanges = true;
|
||||
if (this.elements.huntUpgradeBooksInput && parseInt(this.elements.huntUpgradeBooksInput.value) !== readarrSettings.hunt_upgrade_books) hasChanges = true;
|
||||
if (this.elements.sleepDurationInput && parseInt(this.elements.sleepDurationInput.value) !== readarrSettings.sleep_duration) hasChanges = true;
|
||||
if (this.elements.stateResetIntervalInput && parseInt(this.elements.stateResetIntervalInput.value) !== readarrSettings.state_reset_interval_hours) hasChanges = true;
|
||||
if (this.elements.monitoredOnlyInput && this.elements.monitoredOnlyInput.checked !== readarrSettings.monitored_only) hasChanges = true;
|
||||
if (this.elements.skipFutureReleasesInput && this.elements.skipFutureReleasesInput.checked !== readarrSettings.skip_future_releases) hasChanges = true;
|
||||
if (this.elements.skipAuthorRefreshInput && this.elements.skipAuthorRefreshInput.checked !== readarrSettings.skip_author_refresh) hasChanges = true;
|
||||
|
||||
// Check Advanced Settings directly from the readarr object as well
|
||||
if (this.elements.apiTimeoutInput && parseInt(this.elements.apiTimeoutInput.value) !== readarrSettings.api_timeout) hasChanges = true;
|
||||
if (this.elements.debugModeInput && this.elements.debugModeInput.checked !== readarrSettings.debug_mode) hasChanges = true;
|
||||
if (this.elements.commandWaitDelayInput && parseInt(this.elements.commandWaitDelayInput.value) !== readarrSettings.command_wait_delay) hasChanges = true;
|
||||
if (this.elements.commandWaitAttemptsInput && parseInt(this.elements.commandWaitAttemptsInput.value) !== readarrSettings.command_wait_attempts) hasChanges = true;
|
||||
if (this.elements.minimumDownloadQueueSizeInput && parseInt(this.elements.minimumDownloadQueueSizeInput.value) !== readarrSettings.minimum_download_queue_size) hasChanges = true;
|
||||
if (this.elements.randomMissingInput && this.elements.randomMissingInput.checked !== readarrSettings.random_missing) hasChanges = true;
|
||||
if (this.elements.randomUpgradesInput && this.elements.randomUpgradesInput.checked !== readarrSettings.random_upgrades) hasChanges = true;
|
||||
|
||||
// Update save buttons state
|
||||
this.updateSaveButtonState(hasChanges);
|
||||
|
||||
return hasChanges;
|
||||
},
|
||||
|
||||
updateSaveButtonState: function(hasChanges) {
|
||||
// Use the huntarrUI instance to access elements
|
||||
const saveButton = window.huntarrUI?.elements?.saveSettingsButton;
|
||||
if (saveButton) {
|
||||
saveButton.disabled = !hasChanges;
|
||||
if (hasChanges) {
|
||||
saveButton.classList.remove('disabled-button'); // Assuming 'disabled-button' class handles visual state
|
||||
// Assuming app.updateDurationDisplay exists and is accessible
|
||||
if (app && typeof app.updateDurationDisplay === 'function') {
|
||||
app.updateDurationDisplay(seconds, this.elements.sleepDurationHoursSpan);
|
||||
} else {
|
||||
saveButton.classList.add('disabled-button');
|
||||
console.warn("app.updateDurationDisplay not found, sleep duration text might not update.");
|
||||
}
|
||||
}
|
||||
// Remove references to non-existent bottom button
|
||||
},
|
||||
|
||||
getSettingsPayload: function() {
|
||||
return {
|
||||
app_type: 'readarr',
|
||||
api_url: this.elements.apiUrlInput ? this.elements.apiUrlInput.value || '' : '',
|
||||
api_key: this.elements.apiKeyInput ? this.elements.apiKeyInput.value || '' : '',
|
||||
// Combined settings - all at top level, no nesting
|
||||
hunt_missing_books: this.elements.huntMissingBooksInput ? parseInt(this.elements.huntMissingBooksInput.value) || 0 : 0,
|
||||
hunt_upgrade_books: this.elements.huntUpgradeBooksInput ? parseInt(this.elements.huntUpgradeBooksInput.value) || 0 : 0,
|
||||
sleep_duration: this.elements.sleepDurationInput ? parseInt(this.elements.sleepDurationInput.value) || 900 : 900,
|
||||
state_reset_interval_hours: this.elements.stateResetIntervalInput ? parseInt(this.elements.stateResetIntervalInput.value) || 168 : 168,
|
||||
monitored_only: this.elements.monitoredOnlyInput ? this.elements.monitoredOnlyInput.checked : true,
|
||||
skip_future_releases: this.elements.skipFutureReleasesInput ? this.elements.skipFutureReleasesInput.checked : true,
|
||||
skip_author_refresh: this.elements.skipAuthorRefreshInput ? this.elements.skipAuthorRefreshInput.checked : false,
|
||||
|
||||
// Include advanced settings at the same level
|
||||
debug_mode: this.elements.debugModeInput ? this.elements.debugModeInput.checked : false,
|
||||
command_wait_delay: this.elements.commandWaitDelayInput ? parseInt(this.elements.commandWaitDelayInput.value) || 1 : 1,
|
||||
command_wait_attempts: this.elements.commandWaitAttemptsInput ? parseInt(this.elements.commandWaitAttemptsInput.value) || 600 : 600,
|
||||
minimum_download_queue_size: this.elements.minimumDownloadQueueSizeInput ? parseInt(this.elements.minimumDownloadQueueSizeInput.value) || -1 : -1,
|
||||
random_missing: this.elements.randomMissingInput ? this.elements.randomMissingInput.checked : true,
|
||||
random_upgrades: this.elements.randomUpgradesInput ? this.elements.randomUpgradesInput.checked : true,
|
||||
api_timeout: this.elements.apiTimeoutInput ? parseInt(this.elements.apiTimeoutInput.value) || 60 : 60,
|
||||
log_refresh_interval_seconds: 30 // Default value
|
||||
};
|
||||
},
|
||||
|
||||
saveSettings: function() {
|
||||
const payload = this.getSettingsPayload();
|
||||
|
||||
// Use POST /api/settings endpoint
|
||||
fetch('/api/settings', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Update original settings and API key data attributes after successful save
|
||||
const settings = data.settings || {}; // Assuming backend returns the saved settings
|
||||
if (readarrModule.elements.apiUrlInput) readarrModule.elements.apiUrlInput.dataset.originalValue = settings.api_url;
|
||||
if (readarrModule.elements.apiKeyInput) readarrModule.elements.apiKeyInput.dataset.originalValue = settings.api_key;
|
||||
|
||||
// Update the rest of originalSettings
|
||||
if (settings.readarr) app.originalSettings.readarr = {...settings.readarr};
|
||||
|
||||
// Update configuration status
|
||||
app.configuredApps.readarr = !!(settings.api_url && settings.api_key);
|
||||
|
||||
// Update connection status
|
||||
app.updateConnectionStatus();
|
||||
|
||||
// Update home page connection status
|
||||
app.updateHomeConnectionStatus();
|
||||
|
||||
// Update logs connection status
|
||||
app.updateLogsConnectionStatus();
|
||||
|
||||
// Disable save buttons
|
||||
readarrModule.updateSaveButtonState(false);
|
||||
|
||||
// Show success message
|
||||
if (data.changes_made) {
|
||||
alert('Settings saved successfully and cycle restarted to apply changes!');
|
||||
} else {
|
||||
// Even if no changes were made according to backend, confirm save
|
||||
alert('Settings saved successfully.');
|
||||
}
|
||||
} else {
|
||||
alert('Error saving settings: ' + (data.message || 'Unknown error'));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error saving settings:', error);
|
||||
alert('Error saving settings: ' + error.message);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize Readarr module
|
||||
readarrModule.init();
|
||||
|
||||
// Override app.saveSettings to handle Readarr-specific logic when Readarr is active
|
||||
const originalSaveSettings = app.saveSettings;
|
||||
app.saveSettings = function() {
|
||||
if (app.currentApp === 'readarr') {
|
||||
if (!readarrModule.checkForChanges()) {
|
||||
// If no changes, don't do anything
|
||||
return;
|
||||
}
|
||||
|
||||
const settings = readarrModule.getSettingsPayload();
|
||||
|
||||
fetch('/api/settings', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(settings)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Store the original values in data attributes for comparison
|
||||
if (readarrModule.elements.apiUrlInput) readarrModule.elements.apiUrlInput.dataset.originalValue = settings.api_url;
|
||||
if (readarrModule.elements.apiKeyInput) readarrModule.elements.apiKeyInput.dataset.originalValue = settings.api_key;
|
||||
|
||||
// Update the rest of originalSettings
|
||||
if (settings.readarr) app.originalSettings.readarr = {...settings.readarr};
|
||||
|
||||
// Update configuration status
|
||||
app.configuredApps.readarr = !!(settings.api_url && settings.api_key);
|
||||
|
||||
// Update connection status
|
||||
app.updateConnectionStatus();
|
||||
|
||||
// Update home page connection status
|
||||
app.updateHomeConnectionStatus();
|
||||
|
||||
// Update logs connection status
|
||||
app.updateLogsConnectionStatus();
|
||||
|
||||
// Disable save buttons
|
||||
readarrModule.updateSaveButtonState(false);
|
||||
|
||||
// Show success message
|
||||
if (data.changes_made) {
|
||||
alert('Settings saved successfully and cycle restarted to apply changes!');
|
||||
} else {
|
||||
alert('No changes detected.');
|
||||
}
|
||||
} else {
|
||||
alert('Error saving settings: ' + (data.message || 'Unknown error'));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error saving settings:', error);
|
||||
alert('Error saving settings: ' + error.message);
|
||||
});
|
||||
} else if (originalSaveSettings) {
|
||||
// Call the original if we're not handling Readarr
|
||||
originalSaveSettings.call(app);
|
||||
}
|
||||
};
|
||||
|
||||
// Attach Readarr-specific methods to the global app object
|
||||
app.readarrModule = readarrModule;
|
||||
|
||||
// Override the global saveSettings function for Readarr
|
||||
app.saveSettings = function() {
|
||||
const payload = readarrModule.getSettingsPayload();
|
||||
|
||||
// Use POST /api/settings endpoint
|
||||
fetch('/api/settings', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Update original values after successful save
|
||||
if (readarrModule.elements.apiUrlInput) readarrModule.elements.apiUrlInput.dataset.originalValue = settings.api_url;
|
||||
if (readarrModule.elements.apiKeyInput) readarrModule.elements.apiKeyInput.dataset.originalValue = settings.api_key;
|
||||
|
||||
// Update the rest of originalSettings
|
||||
if (settings.readarr) app.originalSettings.readarr = {...settings.readarr};
|
||||
|
||||
// Update configuration status
|
||||
app.configuredApps.readarr = !!(settings.api_url && settings.api_key);
|
||||
|
||||
// Update connection status
|
||||
app.updateConnectionStatus();
|
||||
|
||||
// Update home page connection status
|
||||
app.updateHomeConnectionStatus();
|
||||
|
||||
// Update logs connection status
|
||||
app.updateLogsConnectionStatus();
|
||||
|
||||
// Disable save buttons
|
||||
readarrModule.updateSaveButtonState(false);
|
||||
|
||||
// Show success message
|
||||
if (data.changes_made) {
|
||||
alert('Settings saved successfully and cycle restarted to apply changes!');
|
||||
} else {
|
||||
alert('No changes detected.');
|
||||
}
|
||||
} else {
|
||||
alert('Error saving settings: ' + (data.message || 'Unknown error'));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error saving settings:', error);
|
||||
alert('Error saving settings: ' + error.message);
|
||||
});
|
||||
};
|
||||
|
||||
})(window.huntarrApp); // Pass the global app object
|
||||
// Initialize Readarr module
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (document.getElementById('readarrSettings')) {
|
||||
readarrModule.init();
|
||||
if (app) {
|
||||
app.readarrModule = readarrModule;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
})(window.huntarrUI); // Pass the global UI object
|
||||
|
||||
@@ -1,354 +1,83 @@
|
||||
// Sonarr-specific functionality
|
||||
|
||||
(function(app) {
|
||||
// Add Sonarr-specific initialization
|
||||
if (!app) {
|
||||
console.error("Huntarr App core is not loaded!");
|
||||
return;
|
||||
}
|
||||
|
||||
const sonarrModule = {
|
||||
// DOM elements specific to Sonarr
|
||||
elements: {
|
||||
apiUrlInput: document.getElementById('sonarr_api_url'),
|
||||
apiKeyInput: document.getElementById('sonarr_api_key'),
|
||||
|
||||
// Settings form elements
|
||||
huntMissingShowsInput: document.getElementById('hunt_missing_shows'),
|
||||
huntUpgradeEpisodesInput: document.getElementById('hunt_upgrade_episodes'),
|
||||
sleepDurationInput: document.getElementById('sleep_duration'),
|
||||
sleepDurationHoursSpan: document.getElementById('sleep_duration_hours'),
|
||||
stateResetIntervalInput: document.getElementById('state_reset_interval_hours'),
|
||||
monitoredOnlyInput: document.getElementById('monitored_only'),
|
||||
randomMissingInput: document.getElementById('random_missing'),
|
||||
randomUpgradesInput: document.getElementById('random_upgrades'),
|
||||
skipFutureEpisodesInput: document.getElementById('skip_future_episodes'),
|
||||
skipSeriesRefreshInput: document.getElementById('skip_series_refresh'),
|
||||
|
||||
// Advanced settings
|
||||
apiTimeoutInput: document.getElementById('api_timeout'),
|
||||
debugModeInput: document.getElementById('debug_mode'),
|
||||
commandWaitDelayInput: document.getElementById('command_wait_delay'),
|
||||
commandWaitAttemptsInput: document.getElementById('command_wait_attempts'),
|
||||
minimumDownloadQueueSizeInput: document.getElementById('minimum_download_queue_size')
|
||||
},
|
||||
|
||||
elements: {},
|
||||
|
||||
init: function() {
|
||||
// Cache elements specific to Sonarr settings
|
||||
this.cacheElements();
|
||||
// Setup event listeners specific to Sonarr settings
|
||||
this.setupEventListeners();
|
||||
|
||||
// Extend the core app with Sonarr-specific implementations
|
||||
app.loadSettingsSonarr = this.loadSettings.bind(this);
|
||||
|
||||
// Override the load settings with Sonarr implementation when Sonarr is active
|
||||
const originalLoadSettings = app.loadSettings;
|
||||
app.loadSettings = function(appType) {
|
||||
if (appType === 'sonarr') {
|
||||
this.loadSettingsSonarr();
|
||||
} else if (originalLoadSettings) {
|
||||
// Only call the original if we're not handling Sonarr
|
||||
originalLoadSettings.call(this, appType);
|
||||
}
|
||||
};
|
||||
// Initial population of the form is handled by new-main.js
|
||||
},
|
||||
|
||||
|
||||
cacheElements: function() {
|
||||
// Cache elements used by Sonarr settings form
|
||||
this.elements.apiUrlInput = document.getElementById('sonarr_api_url');
|
||||
this.elements.apiKeyInput = document.getElementById('sonarr_api_key');
|
||||
this.elements.huntMissingShowsInput = document.getElementById('hunt_missing_shows');
|
||||
this.elements.huntUpgradeEpisodesInput = document.getElementById('hunt_upgrade_episodes');
|
||||
this.elements.sleepDurationInput = document.getElementById('sleep_duration'); // Note: ID might be generic now
|
||||
this.elements.sleepDurationHoursSpan = document.getElementById('sleep_duration_hours'); // Note: ID might be generic now
|
||||
this.elements.stateResetIntervalInput = document.getElementById('state_reset_interval_hours'); // Note: ID might be generic now
|
||||
this.elements.monitoredOnlyInput = document.getElementById('monitored_only'); // Note: ID might be generic now
|
||||
this.elements.skipFutureEpisodesInput = document.getElementById('skip_future_episodes');
|
||||
this.elements.skipSeriesRefreshInput = document.getElementById('skip_series_refresh');
|
||||
this.elements.randomMissingInput = document.getElementById('random_missing'); // Note: ID might be generic now
|
||||
this.elements.randomUpgradesInput = document.getElementById('random_upgrades'); // Note: ID might be generic now
|
||||
this.elements.debugModeInput = document.getElementById('debug_mode'); // Note: ID might be generic now
|
||||
this.elements.apiTimeoutInput = document.getElementById('api_timeout'); // Note: ID might be generic now
|
||||
this.elements.commandWaitDelayInput = document.getElementById('command_wait_delay'); // Note: ID might be generic now
|
||||
this.elements.commandWaitAttemptsInput = document.getElementById('command_wait_attempts'); // Note: ID might be generic now
|
||||
this.elements.minimumDownloadQueueSizeInput = document.getElementById('minimum_download_queue_size'); // Note: ID might be generic now
|
||||
// Add other Sonarr-specific elements if any
|
||||
},
|
||||
|
||||
setupEventListeners: function() {
|
||||
// Add input event listeners for Sonarr-specific settings
|
||||
const inputs = [
|
||||
this.elements.apiUrlInput,
|
||||
this.elements.apiKeyInput,
|
||||
this.elements.huntMissingShowsInput,
|
||||
this.elements.huntUpgradeEpisodesInput,
|
||||
this.elements.sleepDurationInput,
|
||||
this.elements.stateResetIntervalInput,
|
||||
this.elements.apiTimeoutInput,
|
||||
this.elements.commandWaitDelayInput,
|
||||
this.elements.commandWaitAttemptsInput,
|
||||
this.elements.minimumDownloadQueueSizeInput
|
||||
];
|
||||
|
||||
inputs.forEach(input => {
|
||||
if (input) {
|
||||
input.addEventListener('input', this.checkForChanges.bind(this));
|
||||
}
|
||||
});
|
||||
|
||||
const checkboxes = [
|
||||
this.elements.monitoredOnlyInput,
|
||||
this.elements.randomMissingInput,
|
||||
this.elements.randomUpgradesInput,
|
||||
this.elements.skipFutureEpisodesInput,
|
||||
this.elements.skipSeriesRefreshInput,
|
||||
this.elements.debugModeInput
|
||||
];
|
||||
|
||||
checkboxes.forEach(checkbox => {
|
||||
if (checkbox) {
|
||||
checkbox.addEventListener('change', this.checkForChanges.bind(this));
|
||||
}
|
||||
});
|
||||
|
||||
// Add special handler for sleep duration to update display
|
||||
// Add event listeners for Sonarr-specific controls if needed
|
||||
// Example: If there were unique interactions for Sonarr settings
|
||||
// Most change detection is now handled centrally by new-main.js
|
||||
|
||||
// Update sleep duration display on input change
|
||||
if (this.elements.sleepDurationInput) {
|
||||
this.elements.sleepDurationInput.addEventListener('input', () => {
|
||||
this.updateSleepDurationDisplay();
|
||||
this.checkForChanges();
|
||||
// Central change detection handles the rest
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
updateSleepDurationDisplay: function() {
|
||||
// Use the central utility function for updating duration display
|
||||
if (this.elements.sleepDurationInput && this.elements.sleepDurationHoursSpan) {
|
||||
const seconds = parseInt(this.elements.sleepDurationInput.value) || 900;
|
||||
app.updateDurationDisplay(seconds, this.elements.sleepDurationHoursSpan);
|
||||
}
|
||||
},
|
||||
|
||||
loadSettings: function() {
|
||||
fetch('/api/settings')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Store original settings for comparison
|
||||
app.originalSettings = JSON.parse(JSON.stringify(data));
|
||||
|
||||
// Get app-specific settings directly from sonarr section instead of huntarr/advanced
|
||||
const sonarrSettings = data.sonarr || {};
|
||||
|
||||
// For Sonarr, load from app-settings endpoint
|
||||
fetch(`/api/app-settings?app=sonarr`)
|
||||
.then(response => response.json())
|
||||
.then(appData => {
|
||||
if (appData.success) {
|
||||
this.elements.apiUrlInput.value = appData.api_url || '';
|
||||
this.elements.apiKeyInput.value = appData.api_key || '';
|
||||
|
||||
// Store original values in data attributes for comparison
|
||||
this.elements.apiUrlInput.dataset.originalValue = appData.api_url || '';
|
||||
this.elements.apiKeyInput.dataset.originalValue = appData.api_key || '';
|
||||
|
||||
// Update configured status
|
||||
app.configuredApps.sonarr = !!(appData.api_url && appData.api_key);
|
||||
}
|
||||
|
||||
// Sonarr-specific settings - all from sonarrSettings directly
|
||||
if (this.elements.huntMissingShowsInput) {
|
||||
this.elements.huntMissingShowsInput.value = sonarrSettings.hunt_missing_shows !== undefined ? sonarrSettings.hunt_missing_shows : 1;
|
||||
}
|
||||
if (this.elements.huntUpgradeEpisodesInput) {
|
||||
this.elements.huntUpgradeEpisodesInput.value = sonarrSettings.hunt_upgrade_episodes !== undefined ? sonarrSettings.hunt_upgrade_episodes : 0;
|
||||
}
|
||||
if (this.elements.sleepDurationInput) {
|
||||
this.elements.sleepDurationInput.value = sonarrSettings.sleep_duration || 900;
|
||||
this.updateSleepDurationDisplay();
|
||||
}
|
||||
if (this.elements.stateResetIntervalInput) {
|
||||
this.elements.stateResetIntervalInput.value = sonarrSettings.state_reset_interval_hours || 168;
|
||||
}
|
||||
if (this.elements.monitoredOnlyInput) {
|
||||
this.elements.monitoredOnlyInput.checked = sonarrSettings.monitored_only !== false;
|
||||
}
|
||||
if (this.elements.skipFutureEpisodesInput) {
|
||||
this.elements.skipFutureEpisodesInput.checked = sonarrSettings.skip_future_episodes !== false;
|
||||
}
|
||||
if (this.elements.skipSeriesRefreshInput) {
|
||||
this.elements.skipSeriesRefreshInput.checked = sonarrSettings.skip_series_refresh === true;
|
||||
}
|
||||
|
||||
// Advanced settings - from the same sonarrSettings object
|
||||
if (this.elements.apiTimeoutInput) {
|
||||
this.elements.apiTimeoutInput.value = sonarrSettings.api_timeout || 60;
|
||||
}
|
||||
if (this.elements.debugModeInput) {
|
||||
this.elements.debugModeInput.checked = sonarrSettings.debug_mode === true;
|
||||
}
|
||||
if (this.elements.commandWaitDelayInput) {
|
||||
this.elements.commandWaitDelayInput.value = sonarrSettings.command_wait_delay || 1;
|
||||
}
|
||||
if (this.elements.commandWaitAttemptsInput) {
|
||||
this.elements.commandWaitAttemptsInput.value = sonarrSettings.command_wait_attempts || 600;
|
||||
}
|
||||
if (this.elements.minimumDownloadQueueSizeInput) {
|
||||
this.elements.minimumDownloadQueueSizeInput.value = sonarrSettings.minimum_download_queue_size || -1;
|
||||
}
|
||||
if (this.elements.randomMissingInput) {
|
||||
this.elements.randomMissingInput.checked = sonarrSettings.random_missing !== false;
|
||||
}
|
||||
if (this.elements.randomUpgradesInput) {
|
||||
this.elements.randomUpgradesInput.checked = sonarrSettings.random_upgrades !== false;
|
||||
}
|
||||
|
||||
// Update home page connection status
|
||||
app.updateHomeConnectionStatus();
|
||||
|
||||
// Update log connection status if on logs page
|
||||
if (app.elements.logsContainer && app.elements.logsContainer.style.display !== 'none') {
|
||||
app.updateLogsConnectionStatus();
|
||||
}
|
||||
|
||||
// Initialize save buttons state
|
||||
this.updateSaveButtonState(false);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading Sonarr settings:', error);
|
||||
|
||||
// Default values
|
||||
this.elements.apiUrlInput.value = '';
|
||||
this.elements.apiKeyInput.value = '';
|
||||
this.elements.apiUrlInput.dataset.originalValue = '';
|
||||
this.elements.apiKeyInput.dataset.originalValue = '';
|
||||
app.configuredApps.sonarr = false;
|
||||
|
||||
// Update home page connection status
|
||||
app.updateHomeConnectionStatus();
|
||||
|
||||
// Update log connection status if on logs page
|
||||
if (app.elements.logsContainer && app.elements.logsContainer.style.display !== 'none') {
|
||||
app.updateLogsConnectionStatus();
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(error => console.error('Error loading settings:', error));
|
||||
},
|
||||
|
||||
checkForChanges: function() {
|
||||
if (!app.originalSettings.sonarr) return false; // Don't check if original settings not loaded
|
||||
|
||||
let hasChanges = false;
|
||||
const sonarrSettings = app.originalSettings.sonarr || {};
|
||||
|
||||
// API connection settings
|
||||
if (this.elements.apiUrlInput && this.elements.apiUrlInput.dataset.originalValue !== undefined &&
|
||||
this.elements.apiUrlInput.value !== this.elements.apiUrlInput.dataset.originalValue) hasChanges = true;
|
||||
if (this.elements.apiKeyInput && this.elements.apiKeyInput.dataset.originalValue !== undefined &&
|
||||
this.elements.apiKeyInput.value !== this.elements.apiKeyInput.dataset.originalValue) hasChanges = true;
|
||||
|
||||
// Check all settings directly from the sonarr object
|
||||
if (this.elements.huntMissingShowsInput && parseInt(this.elements.huntMissingShowsInput.value) !== sonarrSettings.hunt_missing_shows) hasChanges = true;
|
||||
if (this.elements.huntUpgradeEpisodesInput && parseInt(this.elements.huntUpgradeEpisodesInput.value) !== sonarrSettings.hunt_upgrade_episodes) hasChanges = true;
|
||||
if (this.elements.sleepDurationInput && parseInt(this.elements.sleepDurationInput.value) !== sonarrSettings.sleep_duration) hasChanges = true;
|
||||
if (this.elements.stateResetIntervalInput && parseInt(this.elements.stateResetIntervalInput.value) !== sonarrSettings.state_reset_interval_hours) hasChanges = true;
|
||||
if (this.elements.monitoredOnlyInput && this.elements.monitoredOnlyInput.checked !== sonarrSettings.monitored_only) hasChanges = true;
|
||||
if (this.elements.skipFutureEpisodesInput && this.elements.skipFutureEpisodesInput.checked !== sonarrSettings.skip_future_episodes) hasChanges = true;
|
||||
if (this.elements.skipSeriesRefreshInput && this.elements.skipSeriesRefreshInput.checked !== sonarrSettings.skip_series_refresh) hasChanges = true;
|
||||
|
||||
// Check Advanced Settings directly from the sonarr object as well
|
||||
if (this.elements.apiTimeoutInput && parseInt(this.elements.apiTimeoutInput.value) !== sonarrSettings.api_timeout) hasChanges = true;
|
||||
if (this.elements.debugModeInput && this.elements.debugModeInput.checked !== sonarrSettings.debug_mode) hasChanges = true;
|
||||
if (this.elements.commandWaitDelayInput && parseInt(this.elements.commandWaitDelayInput.value) !== sonarrSettings.command_wait_delay) hasChanges = true;
|
||||
if (this.elements.commandWaitAttemptsInput && parseInt(this.elements.commandWaitAttemptsInput.value) !== sonarrSettings.command_wait_attempts) hasChanges = true;
|
||||
if (this.elements.minimumDownloadQueueSizeInput && parseInt(this.elements.minimumDownloadQueueSizeInput.value) !== sonarrSettings.minimum_download_queue_size) hasChanges = true;
|
||||
if (this.elements.randomMissingInput && this.elements.randomMissingInput.checked !== sonarrSettings.random_missing) hasChanges = true;
|
||||
if (this.elements.randomUpgradesInput && this.elements.randomUpgradesInput.checked !== sonarrSettings.random_upgrades) hasChanges = true;
|
||||
|
||||
// Update save buttons state
|
||||
this.updateSaveButtonState(hasChanges);
|
||||
|
||||
return hasChanges;
|
||||
},
|
||||
|
||||
updateSaveButtonState: function(hasChanges) {
|
||||
// Use the huntarrUI instance to access elements
|
||||
const saveButton = window.huntarrUI?.elements?.saveSettingsButton;
|
||||
if (saveButton) {
|
||||
saveButton.disabled = !hasChanges;
|
||||
if (hasChanges) {
|
||||
saveButton.classList.remove('disabled-button'); // Assuming 'disabled-button' class handles visual state
|
||||
} else {
|
||||
saveButton.classList.add('disabled-button');
|
||||
}
|
||||
}
|
||||
// Remove references to non-existent bottom button
|
||||
// if (app.elements.saveSettingsBottomButton) { ... }
|
||||
},
|
||||
|
||||
getSettingsPayload: function() {
|
||||
return {
|
||||
app_type: 'sonarr',
|
||||
api_url: this.elements.apiUrlInput ? this.elements.apiUrlInput.value || '' : '',
|
||||
api_key: this.elements.apiKeyInput ? this.elements.apiKeyInput.value || '' : '',
|
||||
// Combined settings - all at top level, no nesting
|
||||
hunt_missing_shows: this.elements.huntMissingShowsInput ? parseInt(this.elements.huntMissingShowsInput.value) || 0 : 0,
|
||||
hunt_upgrade_episodes: this.elements.huntUpgradeEpisodesInput ? parseInt(this.elements.huntUpgradeEpisodesInput.value) || 0 : 0,
|
||||
sleep_duration: this.elements.sleepDurationInput ? parseInt(this.elements.sleepDurationInput.value) || 900 : 900,
|
||||
state_reset_interval_hours: this.elements.stateResetIntervalInput ? parseInt(this.elements.stateResetIntervalInput.value) || 168 : 168,
|
||||
monitored_only: this.elements.monitoredOnlyInput ? this.elements.monitoredOnlyInput.checked : true,
|
||||
skip_future_episodes: this.elements.skipFutureEpisodesInput ? this.elements.skipFutureEpisodesInput.checked : true,
|
||||
skip_series_refresh: this.elements.skipSeriesRefreshInput ? this.elements.skipSeriesRefreshInput.checked : false,
|
||||
|
||||
// Include advanced settings at the same level
|
||||
debug_mode: this.elements.debugModeInput ? this.elements.debugModeInput.checked : false,
|
||||
command_wait_delay: this.elements.commandWaitDelayInput ? parseInt(this.elements.commandWaitDelayInput.value) || 1 : 1,
|
||||
command_wait_attempts: this.elements.commandWaitAttemptsInput ? parseInt(this.elements.commandWaitAttemptsInput.value) || 600 : 600,
|
||||
minimum_download_queue_size: this.elements.minimumDownloadQueueSizeInput ? parseInt(this.elements.minimumDownloadQueueSizeInput.value) || -1 : -1,
|
||||
random_missing: this.elements.randomMissingInput ? this.elements.randomMissingInput.checked : true,
|
||||
random_upgrades: this.elements.randomUpgradesInput ? this.elements.randomUpgradesInput.checked : true,
|
||||
api_timeout: this.elements.apiTimeoutInput ? parseInt(this.elements.apiTimeoutInput.value) || 60 : 60,
|
||||
log_refresh_interval_seconds: 30 // Default value
|
||||
};
|
||||
}
|
||||
|
||||
// REMOVED: loadSettings function (handled by new-main.js)
|
||||
|
||||
// REMOVED: checkForChanges function (handled by new-main.js)
|
||||
|
||||
// REMOVED: updateSaveButtonState function (handled by new-main.js)
|
||||
|
||||
// REMOVED: getSettingsPayload function (handled by new-main.js)
|
||||
|
||||
// REMOVED: saveSettings function (handled by new-main.js)
|
||||
|
||||
// REMOVED: Overriding of app.saveSettings
|
||||
};
|
||||
|
||||
|
||||
// Initialize Sonarr module
|
||||
sonarrModule.init();
|
||||
|
||||
// Override app.saveSettings to handle Sonarr-specific logic when Sonarr is active
|
||||
const originalSaveSettings = app.saveSettings;
|
||||
app.saveSettings = function() {
|
||||
if (app.currentApp === 'sonarr') {
|
||||
if (!sonarrModule.checkForChanges()) {
|
||||
// If no changes, don't do anything
|
||||
return;
|
||||
}
|
||||
|
||||
const settings = sonarrModule.getSettingsPayload();
|
||||
|
||||
fetch('/api/settings', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(settings)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Store the original values in data attributes for comparison
|
||||
if (sonarrModule.elements.apiUrlInput) sonarrModule.elements.apiUrlInput.dataset.originalValue = settings.api_url;
|
||||
if (sonarrModule.elements.apiKeyInput) sonarrModule.elements.apiKeyInput.dataset.originalValue = settings.api_key;
|
||||
|
||||
// Update the rest of originalSettings
|
||||
if (settings.sonarr) app.originalSettings.sonarr = {...settings.sonarr};
|
||||
|
||||
// Update configuration status
|
||||
app.configuredApps.sonarr = !!(settings.api_url && settings.api_key);
|
||||
|
||||
// Update connection status
|
||||
app.updateConnectionStatus();
|
||||
|
||||
// Update home page connection status
|
||||
app.updateHomeConnectionStatus();
|
||||
|
||||
// Update logs connection status
|
||||
app.updateLogsConnectionStatus();
|
||||
|
||||
// Disable save buttons
|
||||
sonarrModule.updateSaveButtonState(false);
|
||||
|
||||
// Show success message
|
||||
if (data.changes_made) {
|
||||
alert('Settings saved successfully and cycle restarted to apply changes!');
|
||||
} else {
|
||||
alert('No changes detected.');
|
||||
}
|
||||
} else {
|
||||
alert('Error saving settings: ' + (data.message || 'Unknown error'));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error saving settings:', error);
|
||||
alert('Error saving settings: ' + error.message);
|
||||
});
|
||||
} else if (originalSaveSettings) {
|
||||
// Call the original if we're not handling Sonarr
|
||||
originalSaveSettings.call(app);
|
||||
}
|
||||
};
|
||||
|
||||
// Add the Sonarr module to the app for reference
|
||||
|
||||
// Add the Sonarr module to the app for reference if needed elsewhere
|
||||
app.sonarrModule = sonarrModule;
|
||||
|
||||
})(window.huntarrApp);
|
||||
})(window.huntarrUI); // Use the new global object name
|
||||
|
||||
@@ -1,529 +0,0 @@
|
||||
// Core functionality used across all application modules
|
||||
|
||||
// Global state
|
||||
const huntarrApp = {
|
||||
// Current selected app
|
||||
currentApp: 'sonarr',
|
||||
|
||||
// Track which apps are configured
|
||||
configuredApps: {
|
||||
sonarr: false,
|
||||
radarr: false,
|
||||
lidarr: false,
|
||||
readarr: false
|
||||
},
|
||||
|
||||
// Store original settings values
|
||||
originalSettings: {},
|
||||
|
||||
// Event source for logs
|
||||
eventSource: null,
|
||||
|
||||
// DOM references to common elements
|
||||
elements: {},
|
||||
|
||||
// Initialize the application
|
||||
init: function() {
|
||||
// Cache DOM elements
|
||||
this.cacheElements();
|
||||
|
||||
// Set up event listeners
|
||||
this.setupEventListeners();
|
||||
|
||||
// Initialize theme
|
||||
this.loadTheme();
|
||||
|
||||
// Update sleep duration displays
|
||||
this.updateSleepDurationDisplay();
|
||||
|
||||
// Get user info for welcome page
|
||||
this.getUserInfo();
|
||||
|
||||
// Load settings for initial app
|
||||
this.loadSettings(this.currentApp);
|
||||
|
||||
// Navigate based on URL path
|
||||
this.handleNavigation();
|
||||
},
|
||||
|
||||
// Cache DOM elements
|
||||
cacheElements: function() {
|
||||
// Navigation elements
|
||||
this.elements.homeButton = document.getElementById('homeButton');
|
||||
this.elements.logsButton = document.getElementById('logsButton');
|
||||
this.elements.settingsButton = document.getElementById('settingsButton');
|
||||
this.elements.userButton = document.getElementById('userButton');
|
||||
|
||||
// Container elements
|
||||
this.elements.homeContainer = document.getElementById('homeContainer');
|
||||
this.elements.logsContainer = document.getElementById('logsContainer');
|
||||
this.elements.settingsContainer = document.getElementById('settingsContainer');
|
||||
|
||||
// Logs elements
|
||||
this.elements.logsElement = document.getElementById('logs');
|
||||
this.elements.statusElement = document.getElementById('status');
|
||||
this.elements.clearLogsButton = document.getElementById('clearLogs');
|
||||
this.elements.autoScrollCheckbox = document.getElementById('autoScroll');
|
||||
|
||||
// Theme elements
|
||||
this.elements.themeToggle = document.getElementById('themeToggle');
|
||||
this.elements.themeLabel = document.getElementById('themeLabel');
|
||||
|
||||
// App tabs
|
||||
this.elements.appTabs = document.querySelectorAll('.app-tab');
|
||||
this.elements.appSettings = document.querySelectorAll('.app-settings');
|
||||
|
||||
// Connection status elements
|
||||
this.elements.sonarrHomeStatus = document.getElementById('sonarrHomeStatus');
|
||||
this.elements.radarrHomeStatus = document.getElementById('radarrHomeStatus');
|
||||
this.elements.lidarrHomeStatus = document.getElementById('lidarrHomeStatus');
|
||||
this.elements.readarrHomeStatus = document.getElementById('readarrHomeStatus');
|
||||
|
||||
// Save and reset buttons
|
||||
this.elements.saveSettingsButton = document.getElementById('saveSettings');
|
||||
this.elements.resetSettingsButton = document.getElementById('resetSettings');
|
||||
this.elements.saveSettingsBottomButton = document.getElementById('saveSettingsBottom');
|
||||
this.elements.resetSettingsBottomButton = document.getElementById('resetSettingsBottom');
|
||||
},
|
||||
|
||||
// Set up event listeners
|
||||
setupEventListeners: function() {
|
||||
// App tab selection
|
||||
this.elements.appTabs.forEach(tab => {
|
||||
tab.addEventListener('click', this.handleAppTabClick.bind(this));
|
||||
});
|
||||
|
||||
// Navigation
|
||||
if (this.elements.homeButton && this.elements.logsButton && this.elements.settingsButton) {
|
||||
this.elements.homeButton.addEventListener('click', this.navigateToHome.bind(this));
|
||||
this.elements.logsButton.addEventListener('click', this.navigateToLogs.bind(this));
|
||||
this.elements.settingsButton.addEventListener('click', this.navigateToSettings.bind(this));
|
||||
this.elements.userButton.addEventListener('click', this.navigateToUser.bind(this));
|
||||
}
|
||||
|
||||
// Log management
|
||||
if (this.elements.clearLogsButton) {
|
||||
this.elements.clearLogsButton.addEventListener('click', this.clearLogs.bind(this));
|
||||
}
|
||||
|
||||
// Auto-scroll
|
||||
if (this.elements.logsElement) {
|
||||
this.elements.logsElement.addEventListener('scroll', this.handleLogsScroll.bind(this));
|
||||
}
|
||||
|
||||
if (this.elements.autoScrollCheckbox) {
|
||||
this.elements.autoScrollCheckbox.addEventListener('change', this.handleAutoScrollChange.bind(this));
|
||||
}
|
||||
|
||||
// Theme toggle
|
||||
if (this.elements.themeToggle) {
|
||||
this.elements.themeToggle.addEventListener('change', this.handleThemeToggle.bind(this));
|
||||
}
|
||||
|
||||
// Save and reset settings
|
||||
if (this.elements.saveSettingsButton && this.elements.resetSettingsButton) {
|
||||
this.elements.saveSettingsButton.addEventListener('click', this.saveSettings.bind(this));
|
||||
this.elements.resetSettingsButton.addEventListener('click', this.resetSettings.bind(this));
|
||||
this.elements.saveSettingsBottomButton.addEventListener('click', this.saveSettings.bind(this));
|
||||
this.elements.resetSettingsBottomButton.addEventListener('click', this.resetSettings.bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
// Handle app tab click
|
||||
handleAppTabClick: function(event) {
|
||||
const app = event.currentTarget.dataset.app;
|
||||
|
||||
// If it's already the active app, do nothing
|
||||
if (app === this.currentApp) return;
|
||||
|
||||
// Update active tab
|
||||
this.elements.appTabs.forEach(t => t.classList.remove('active'));
|
||||
event.currentTarget.classList.add('active');
|
||||
|
||||
// Update active settings panel if on settings page
|
||||
if (this.elements.settingsContainer && this.elements.settingsContainer.style.display !== 'none') {
|
||||
this.elements.appSettings.forEach(s => s.style.display = 'none');
|
||||
document.getElementById(`${app}Settings`).style.display = 'block';
|
||||
}
|
||||
|
||||
// Update current app
|
||||
this.currentApp = app;
|
||||
|
||||
// Load settings for this app
|
||||
this.loadSettings(app);
|
||||
|
||||
// For logs, refresh the log stream
|
||||
if (this.elements.logsElement && this.elements.logsContainer && this.elements.logsContainer.style.display !== 'none') {
|
||||
// Clear the logs first
|
||||
this.elements.logsElement.innerHTML = '';
|
||||
|
||||
// Update connection status based on configuration
|
||||
this.updateLogsConnectionStatus();
|
||||
|
||||
// Reconnect the event source only if app is configured
|
||||
if (this.configuredApps[app]) {
|
||||
this.connectEventSource(app);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Navigation functions
|
||||
navigateToHome: function() {
|
||||
this.elements.homeContainer.style.display = 'flex';
|
||||
this.elements.logsContainer.style.display = 'none';
|
||||
this.elements.settingsContainer.style.display = 'none';
|
||||
|
||||
this.elements.homeButton.classList.add('active');
|
||||
this.elements.logsButton.classList.remove('active');
|
||||
this.elements.settingsButton.classList.remove('active');
|
||||
this.elements.userButton.classList.remove('active');
|
||||
|
||||
// Update connection status on home page
|
||||
this.updateHomeConnectionStatus();
|
||||
},
|
||||
|
||||
navigateToLogs: function() {
|
||||
this.elements.homeContainer.style.display = 'none';
|
||||
this.elements.logsContainer.style.display = 'flex';
|
||||
this.elements.settingsContainer.style.display = 'none';
|
||||
|
||||
this.elements.homeButton.classList.remove('active');
|
||||
this.elements.logsButton.classList.add('active');
|
||||
this.elements.settingsButton.classList.remove('active');
|
||||
this.elements.userButton.classList.remove('active');
|
||||
|
||||
// Update the connection status based on configuration
|
||||
this.updateLogsConnectionStatus();
|
||||
|
||||
// Reconnect to logs for the current app if configured
|
||||
if (this.elements.logsElement && this.configuredApps[this.currentApp]) {
|
||||
this.connectEventSource(this.currentApp);
|
||||
}
|
||||
},
|
||||
|
||||
navigateToSettings: function() {
|
||||
this.elements.homeContainer.style.display = 'none';
|
||||
this.elements.logsContainer.style.display = 'none';
|
||||
this.elements.settingsContainer.style.display = 'flex';
|
||||
|
||||
this.elements.homeButton.classList.remove('active');
|
||||
this.elements.logsButton.classList.remove('active');
|
||||
this.elements.settingsButton.classList.add('active');
|
||||
this.elements.userButton.classList.remove('active');
|
||||
|
||||
// Show the settings for the current app
|
||||
this.elements.appSettings.forEach(s => s.style.display = 'none');
|
||||
document.getElementById(`${this.currentApp}Settings`).style.display = 'block';
|
||||
|
||||
// Make sure settings are loaded
|
||||
this.loadSettings(this.currentApp);
|
||||
},
|
||||
|
||||
navigateToUser: function() {
|
||||
window.location.href = '/user';
|
||||
},
|
||||
|
||||
// Handle navigation based on URL path
|
||||
handleNavigation: function() {
|
||||
const path = window.location.pathname;
|
||||
|
||||
if (path === '/settings') {
|
||||
this.navigateToSettings();
|
||||
} else if (path === '/') {
|
||||
this.navigateToHome();
|
||||
}
|
||||
|
||||
// Connect to logs if we're on the logs page and the current app is configured
|
||||
if (this.elements.logsElement && this.elements.logsContainer &&
|
||||
this.elements.logsContainer.style.display !== 'none' &&
|
||||
this.configuredApps[this.currentApp]) {
|
||||
this.connectEventSource(this.currentApp);
|
||||
}
|
||||
},
|
||||
|
||||
// Theme management
|
||||
loadTheme: function() {
|
||||
// Always set to dark mode
|
||||
this.setTheme(true);
|
||||
|
||||
// Update server setting to dark mode
|
||||
fetch('/api/settings/theme', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ dark_mode: true })
|
||||
})
|
||||
.catch(error => console.error('Error saving theme:', error));
|
||||
},
|
||||
|
||||
setTheme: function(isDark) {
|
||||
// Always force dark mode
|
||||
document.body.classList.add('dark-theme');
|
||||
document.body.classList.remove('light-theme');
|
||||
|
||||
if (this.elements.themeToggle) {
|
||||
this.elements.themeToggle.checked = true;
|
||||
}
|
||||
|
||||
if (this.elements.themeLabel) {
|
||||
this.elements.themeLabel.textContent = 'Dark Mode';
|
||||
}
|
||||
},
|
||||
|
||||
handleThemeToggle: function(e) {
|
||||
// Force dark mode regardless of toggle
|
||||
this.setTheme(true);
|
||||
|
||||
// Save to localStorage
|
||||
localStorage.setItem('huntarr-dark-mode', 'true');
|
||||
|
||||
// Send theme preference to server
|
||||
fetch('/api/settings/theme', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ dark_mode: true })
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error saving theme preference:', error);
|
||||
});
|
||||
},
|
||||
|
||||
// Log management
|
||||
clearLogs: function() {
|
||||
if (this.elements.logsElement) {
|
||||
this.elements.logsElement.innerHTML = '';
|
||||
}
|
||||
},
|
||||
|
||||
scrollToBottom: function() {
|
||||
if (this.elements.autoScrollCheckbox && this.elements.autoScrollCheckbox.checked && this.elements.logsElement) {
|
||||
this.elements.logsElement.scrollTop = this.elements.logsElement.scrollHeight;
|
||||
}
|
||||
},
|
||||
|
||||
handleLogsScroll: function() {
|
||||
// If we're at the bottom or near it (within 20px), ensure auto-scroll stays on
|
||||
const atBottom = (this.elements.logsElement.scrollHeight - this.elements.logsElement.scrollTop - this.elements.logsElement.clientHeight) < 20;
|
||||
if (!atBottom && this.elements.autoScrollCheckbox && this.elements.autoScrollCheckbox.checked) {
|
||||
// User manually scrolled up, disable auto-scroll
|
||||
this.elements.autoScrollCheckbox.checked = false;
|
||||
}
|
||||
},
|
||||
|
||||
handleAutoScrollChange: function(event) {
|
||||
if (event.target.checked) {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
},
|
||||
|
||||
// Status updates
|
||||
updateHomeConnectionStatus: function() {
|
||||
// Check current configured state
|
||||
fetch('/api/configured-apps')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Update the configuredApps object
|
||||
this.configuredApps.sonarr = data.sonarr || false;
|
||||
this.configuredApps.radarr = data.radarr || false;
|
||||
this.configuredApps.lidarr = data.lidarr || false;
|
||||
this.configuredApps.readarr = data.readarr || false;
|
||||
|
||||
// Update UI elements
|
||||
this.updateStatusElement(this.elements.sonarrHomeStatus, this.configuredApps.sonarr);
|
||||
this.updateStatusElement(this.elements.radarrHomeStatus, this.configuredApps.radarr);
|
||||
this.updateStatusElement(this.elements.lidarrHomeStatus, this.configuredApps.lidarr);
|
||||
this.updateStatusElement(this.elements.readarrHomeStatus, this.configuredApps.readarr);
|
||||
})
|
||||
.catch(error => console.error('Error checking configured apps:', error));
|
||||
},
|
||||
|
||||
updateStatusElement: function(element, isConfigured) {
|
||||
if (element) {
|
||||
if (isConfigured) {
|
||||
element.textContent = 'Configured';
|
||||
element.className = 'connection-badge connected';
|
||||
} else {
|
||||
element.textContent = 'Not Configured';
|
||||
element.className = 'connection-badge not-connected';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
updateLogsConnectionStatus: function() {
|
||||
if (this.elements.statusElement) {
|
||||
if (this.configuredApps[this.currentApp]) {
|
||||
this.elements.statusElement.textContent = 'Connected';
|
||||
this.elements.statusElement.className = 'status-connected';
|
||||
} else {
|
||||
this.elements.statusElement.textContent = 'Disconnected';
|
||||
this.elements.statusElement.className = 'status-disconnected';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
updateConnectionStatus: function() {
|
||||
const appConnectionElements = {
|
||||
'sonarr': document.getElementById('sonarrConnectionStatus'),
|
||||
'radarr': document.getElementById('radarrConnectionStatus'),
|
||||
'lidarr': document.getElementById('lidarrConnectionStatus'),
|
||||
'readarr': document.getElementById('readarrConnectionStatus')
|
||||
};
|
||||
|
||||
const connectionElement = appConnectionElements[this.currentApp];
|
||||
if (connectionElement) {
|
||||
this.updateStatusElement(connectionElement, this.configuredApps[this.currentApp]);
|
||||
}
|
||||
},
|
||||
|
||||
// User info
|
||||
getUserInfo: function() {
|
||||
const username = document.getElementById('username');
|
||||
if (username) {
|
||||
username.textContent = 'User'; // Default placeholder
|
||||
}
|
||||
},
|
||||
|
||||
// Settings functions - generic common operations (app-specific logic in app modules)
|
||||
loadSettings: function(app) {
|
||||
// This function will be overridden by app-specific modules
|
||||
// Each app module will attach its own implementation to huntarrApp
|
||||
},
|
||||
|
||||
saveSettings: function() {
|
||||
// This function will be overridden by app-specific modules
|
||||
// Each app module will attach its own implementation to huntarrApp
|
||||
},
|
||||
|
||||
resetSettings: function() {
|
||||
if (confirm('Are you sure you want to reset all settings to default values?')) {
|
||||
fetch('/api/settings/reset', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
app: this.currentApp
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert('Settings reset to defaults and cycle restarted.');
|
||||
this.loadSettings(this.currentApp);
|
||||
|
||||
// Update home page connection status
|
||||
this.updateHomeConnectionStatus();
|
||||
|
||||
// Update logs connection status
|
||||
this.updateLogsConnectionStatus();
|
||||
} else {
|
||||
alert('Error resetting settings: ' + (data.message || 'Unknown error'));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error resetting settings:', error);
|
||||
alert('Error resetting settings: ' + error.message);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Test connection function - works for all apps
|
||||
testConnection: function(app, urlInput, keyInput, statusElement) {
|
||||
const apiUrl = urlInput.value;
|
||||
const apiKey = keyInput.value;
|
||||
|
||||
if (!apiUrl || !apiKey) {
|
||||
alert(`Please enter both API URL and API Key for ${app.charAt(0).toUpperCase() + app.slice(1)} before testing the connection.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Test API connection
|
||||
if (statusElement) {
|
||||
statusElement.textContent = 'Testing...';
|
||||
statusElement.className = 'connection-badge';
|
||||
}
|
||||
|
||||
// Use the correct endpoint URL based on the app type
|
||||
fetch(`/${app}/test-connection`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
api_url: apiUrl,
|
||||
api_key: apiKey
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
if (statusElement) {
|
||||
statusElement.textContent = 'Connected';
|
||||
statusElement.className = 'connection-badge connected';
|
||||
}
|
||||
|
||||
// Update configuration status
|
||||
this.configuredApps[app] = true;
|
||||
|
||||
// Update home page status
|
||||
this.updateHomeConnectionStatus();
|
||||
} else {
|
||||
if (statusElement) {
|
||||
statusElement.textContent = 'Connection Failed';
|
||||
statusElement.className = 'connection-badge not-connected';
|
||||
}
|
||||
|
||||
// Update configuration status
|
||||
this.configuredApps[app] = false;
|
||||
|
||||
alert(`Connection failed: ${data.message}`);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(`Error testing ${app} connection:`, error);
|
||||
if (statusElement) {
|
||||
statusElement.textContent = 'Connection Error';
|
||||
statusElement.className = 'connection-badge not-connected';
|
||||
}
|
||||
|
||||
// Update configuration status
|
||||
this.configuredApps[app] = false;
|
||||
|
||||
alert(`Error testing ${app} connection: ` + error.message);
|
||||
});
|
||||
},
|
||||
|
||||
// Duration utility function
|
||||
updateSleepDurationDisplay: function() {
|
||||
// This will be called from app-specific modules
|
||||
},
|
||||
|
||||
updateDurationDisplay: function(seconds, spanElement) {
|
||||
if (!spanElement) return;
|
||||
|
||||
let displayText = '';
|
||||
|
||||
if (seconds < 60) {
|
||||
displayText = `${seconds} seconds`;
|
||||
} else if (seconds < 3600) {
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
displayText = `≈ ${minutes} minute${minutes !== 1 ? 's' : ''}`;
|
||||
} else {
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
if (minutes === 0) {
|
||||
displayText = `≈ ${hours} hour${hours !== 1 ? 's' : ''}`;
|
||||
} else {
|
||||
displayText = `≈ ${hours} hour${hours !== 1 ? 's' : ''} ${minutes} minute${minutes !== 1 ? 's' : ''}`;
|
||||
}
|
||||
}
|
||||
|
||||
spanElement.textContent = displayText;
|
||||
}
|
||||
};
|
||||
|
||||
// Export the huntarrApp object for use in other modules
|
||||
window.huntarrApp = huntarrApp;
|
||||
@@ -1,630 +0,0 @@
|
||||
// Main entry point for Huntarr application
|
||||
|
||||
// Wait for DOM content to be loaded before initializing the app
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize the core application
|
||||
if (window.HuntarrApp) { // Changed from huntarrApp to HuntarrApp to match object name
|
||||
window.HuntarrApp.init();
|
||||
} else {
|
||||
console.error('Error: Huntarr core module not loaded');
|
||||
}
|
||||
});
|
||||
|
||||
// Define the HuntarrApp object
|
||||
const HuntarrApp = {
|
||||
// ... (keep existing properties like currentSection, currentApp, etc.) ...
|
||||
currentSection: 'home',
|
||||
currentApp: 'sonarr',
|
||||
currentSettingsTab: 'sonarr',
|
||||
autoScroll: true,
|
||||
darkMode: false,
|
||||
eventSources: {},
|
||||
configuredApps: {
|
||||
sonarr: false,
|
||||
radarr: false,
|
||||
lidarr: false
|
||||
},
|
||||
elements: {},
|
||||
originalSettings: {}, // Added to store original settings for comparison
|
||||
|
||||
init: function() {
|
||||
this.cacheElements();
|
||||
this.setupEventListeners();
|
||||
this.loadTheme();
|
||||
this.loadUsername();
|
||||
this.checkAppConnections();
|
||||
this.handleHashNavigation(); // Handle initial section based on hash
|
||||
|
||||
// Initialize settings if we're on the settings page or hash points to settings
|
||||
if (window.location.pathname === '/settings' || window.location.hash === '#settings') {
|
||||
this.initializeSettings();
|
||||
}
|
||||
},
|
||||
|
||||
cacheElements: function() {
|
||||
// Cache all necessary DOM elements
|
||||
this.elements.navItems = document.querySelectorAll('.nav-item');
|
||||
this.elements.homeNav = document.getElementById('homeNav');
|
||||
this.elements.logsNav = document.getElementById('logsNav');
|
||||
this.elements.settingsNav = document.getElementById('settingsNav');
|
||||
this.elements.userNav = document.getElementById('userNav');
|
||||
|
||||
this.elements.sections = document.querySelectorAll('.content-section');
|
||||
this.elements.homeSection = document.getElementById('homeSection');
|
||||
this.elements.logsSection = document.getElementById('logsSection');
|
||||
this.elements.settingsSection = document.getElementById('settingsSection');
|
||||
this.elements.userSection = document.getElementById('userSection'); // Assuming a user section exists
|
||||
|
||||
this.elements.appTabs = document.querySelectorAll('.app-tab');
|
||||
this.elements.settingsTabs = document.querySelectorAll('.settings-tab');
|
||||
this.elements.appSettingsPanels = document.querySelectorAll('.app-settings-panel');
|
||||
|
||||
this.elements.logsContainer = document.getElementById('logsContainer');
|
||||
this.elements.autoScrollCheckbox = document.getElementById('autoScrollCheckbox');
|
||||
this.elements.clearLogsButton = document.getElementById('clearLogsButton');
|
||||
this.elements.logConnectionStatus = document.getElementById('logConnectionStatus');
|
||||
|
||||
this.elements.saveSettingsButton = document.getElementById('saveSettingsButton');
|
||||
this.elements.resetSettingsButton = document.getElementById('resetSettingsButton');
|
||||
|
||||
this.elements.sonarrHomeStatus = document.getElementById('sonarrHomeStatus');
|
||||
this.elements.radarrHomeStatus = document.getElementById('radarrHomeStatus');
|
||||
this.elements.lidarrHomeStatus = document.getElementById('lidarrHomeStatus');
|
||||
this.elements.readarrHomeStatus = document.getElementById('readarrHomeStatus'); // Added Readarr status
|
||||
|
||||
this.elements.startHuntButton = document.getElementById('startHuntButton');
|
||||
this.elements.stopHuntButton = document.getElementById('stopHuntButton');
|
||||
|
||||
this.elements.themeToggle = document.getElementById('themeToggle');
|
||||
this.elements.currentPageTitle = document.getElementById('currentPageTitle');
|
||||
this.elements.usernameDisplay = document.getElementById('username'); // For top bar
|
||||
},
|
||||
|
||||
setupEventListeners: function() {
|
||||
// Navigation
|
||||
this.elements.navItems.forEach(item => {
|
||||
item.addEventListener('click', this.handleNavigation.bind(this));
|
||||
});
|
||||
|
||||
// App tabs (Logs page)
|
||||
this.elements.appTabs.forEach(tab => {
|
||||
tab.addEventListener('click', this.handleAppTabChange.bind(this));
|
||||
});
|
||||
|
||||
// Settings tabs
|
||||
this.elements.settingsTabs.forEach(tab => {
|
||||
tab.addEventListener('click', this.handleSettingsTabChange.bind(this));
|
||||
});
|
||||
|
||||
// Logs controls
|
||||
if (this.elements.autoScrollCheckbox) {
|
||||
this.elements.autoScrollCheckbox.addEventListener('change', (e) => {
|
||||
this.autoScroll = e.target.checked;
|
||||
});
|
||||
}
|
||||
if (this.elements.clearLogsButton) {
|
||||
this.elements.clearLogsButton.addEventListener('click', this.clearLogs.bind(this));
|
||||
}
|
||||
|
||||
// Settings controls
|
||||
if (this.elements.saveSettingsButton) {
|
||||
this.elements.saveSettingsButton.addEventListener('click', this.saveSettings.bind(this));
|
||||
}
|
||||
if (this.elements.resetSettingsButton) {
|
||||
this.elements.resetSettingsButton.addEventListener('click', this.resetSettings.bind(this));
|
||||
}
|
||||
|
||||
// Actions
|
||||
if (this.elements.startHuntButton) {
|
||||
this.elements.startHuntButton.addEventListener('click', this.startHunt.bind(this));
|
||||
}
|
||||
if (this.elements.stopHuntButton) {
|
||||
this.elements.stopHuntButton.addEventListener('click', this.stopHunt.bind(this));
|
||||
}
|
||||
|
||||
// Theme toggle
|
||||
if (this.elements.themeToggle) {
|
||||
this.elements.themeToggle.addEventListener('change', this.handleThemeToggle.bind(this));
|
||||
}
|
||||
|
||||
// Window hash changes
|
||||
window.addEventListener('hashchange', this.handleHashNavigation.bind(this));
|
||||
},
|
||||
|
||||
handleNavigation: function(e) {
|
||||
e.preventDefault();
|
||||
const target = e.currentTarget;
|
||||
const section = target.getAttribute('href').substring(1);
|
||||
window.location.hash = section; // Use hash for SPA navigation
|
||||
},
|
||||
|
||||
handleHashNavigation: function() {
|
||||
const hash = window.location.hash || '#home';
|
||||
const section = hash.substring(1);
|
||||
this.switchSection(section);
|
||||
},
|
||||
|
||||
switchSection: function(section) {
|
||||
this.currentSection = section;
|
||||
|
||||
// Hide all sections
|
||||
this.elements.sections.forEach(s => s.classList.remove('active'));
|
||||
// Deactivate all nav items
|
||||
this.elements.navItems.forEach(n => n.classList.remove('active'));
|
||||
|
||||
// Activate the target section and nav item
|
||||
const targetSection = document.getElementById(section + 'Section');
|
||||
const targetNav = document.querySelector(`.nav-item[href="#${section}"]`);
|
||||
|
||||
if (targetSection) {
|
||||
targetSection.classList.add('active');
|
||||
if (this.elements.currentPageTitle) {
|
||||
this.elements.currentPageTitle.textContent = this.capitalizeFirst(section);
|
||||
}
|
||||
} else {
|
||||
// Fallback to home if section not found
|
||||
this.elements.homeSection.classList.add('active');
|
||||
section = 'home';
|
||||
if (this.elements.currentPageTitle) {
|
||||
this.elements.currentPageTitle.textContent = 'Home';
|
||||
}
|
||||
}
|
||||
|
||||
if (targetNav) {
|
||||
targetNav.classList.add('active');
|
||||
}
|
||||
|
||||
// Section-specific actions
|
||||
if (section === 'logs') {
|
||||
this.connectToLogs();
|
||||
} else {
|
||||
this.disconnectAllEventSources(); // Disconnect logs when leaving the page
|
||||
}
|
||||
|
||||
if (section === 'settings') {
|
||||
this.initializeSettings(); // Load settings when entering the settings section
|
||||
}
|
||||
},
|
||||
|
||||
initializeSettings: function() {
|
||||
console.log("Initializing settings...");
|
||||
const activeTab = document.querySelector('.settings-tab.active');
|
||||
const defaultTab = 'sonarr'; // Or load from a saved preference
|
||||
this.currentSettingsTab = activeTab ? activeTab.getAttribute('data-settings') : defaultTab;
|
||||
|
||||
// Ensure the correct panel is visible
|
||||
this.elements.appSettingsPanels.forEach(panel => panel.classList.remove('active'));
|
||||
const activePanel = document.getElementById(`${this.currentSettingsTab}Settings`);
|
||||
if (activePanel) {
|
||||
activePanel.classList.add('active');
|
||||
}
|
||||
|
||||
// Ensure the correct tab is highlighted
|
||||
this.elements.settingsTabs.forEach(tab => {
|
||||
if (tab.getAttribute('data-settings') === this.currentSettingsTab) {
|
||||
tab.classList.add('active');
|
||||
} else {
|
||||
tab.classList.remove('active');
|
||||
}
|
||||
});
|
||||
|
||||
this.loadAllSettings(); // Load settings for all tabs
|
||||
},
|
||||
|
||||
handleAppTabChange: function(e) {
|
||||
const app = e.target.getAttribute('data-app');
|
||||
if (!app || app === this.currentApp) return;
|
||||
|
||||
this.currentApp = app;
|
||||
this.elements.appTabs.forEach(tab => tab.classList.remove('active'));
|
||||
e.target.classList.add('active');
|
||||
|
||||
// Reconnect logs for the new app
|
||||
this.connectToLogs();
|
||||
},
|
||||
|
||||
handleSettingsTabChange: function(e) {
|
||||
const tab = e.target.getAttribute('data-settings');
|
||||
if (!tab || tab === this.currentSettingsTab) return;
|
||||
|
||||
this.currentSettingsTab = tab;
|
||||
|
||||
// Update active tab styling
|
||||
this.elements.settingsTabs.forEach(t => t.classList.remove('active'));
|
||||
e.target.classList.add('active');
|
||||
|
||||
// Show the corresponding settings panel
|
||||
this.elements.appSettingsPanels.forEach(panel => panel.classList.remove('active'));
|
||||
const panelElement = document.getElementById(`${tab}Settings`);
|
||||
if (panelElement) {
|
||||
panelElement.classList.add('active');
|
||||
// Settings for this tab should already be loaded by loadAllSettings
|
||||
// If not, call this.loadSettings(tab) here.
|
||||
}
|
||||
},
|
||||
|
||||
// Logs handling
|
||||
connectToLogs: function() {
|
||||
this.disconnectAllEventSources(); // Ensure only one connection
|
||||
|
||||
// Use the unified /logs endpoint, filtering happens server-side or client-side if needed
|
||||
// For now, assuming server sends all logs and we might filter later if necessary
|
||||
const logUrl = `/logs?app=${this.currentApp}`; // Pass current app context if needed by backend
|
||||
|
||||
try {
|
||||
const eventSource = new EventSource(logUrl);
|
||||
this.eventSources['logs'] = eventSource; // Store under a generic key
|
||||
|
||||
eventSource.onopen = () => {
|
||||
if (this.elements.logConnectionStatus) {
|
||||
this.elements.logConnectionStatus.textContent = 'Connected';
|
||||
this.elements.logConnectionStatus.className = 'status-connected';
|
||||
}
|
||||
};
|
||||
|
||||
eventSource.onmessage = (event) => {
|
||||
// Assuming event.data is a string log line
|
||||
this.addLogMessage(event.data);
|
||||
};
|
||||
|
||||
eventSource.onerror = () => {
|
||||
if (this.elements.logConnectionStatus) {
|
||||
this.elements.logConnectionStatus.textContent = 'Disconnected';
|
||||
this.elements.logConnectionStatus.className = 'status-disconnected';
|
||||
}
|
||||
eventSource.close();
|
||||
// Optional: Implement retry logic
|
||||
setTimeout(() => {
|
||||
if (this.currentSection === 'logs') { // Only reconnect if still on logs page
|
||||
this.connectToLogs();
|
||||
}
|
||||
}, 5000);
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error connecting to logs event source:', error);
|
||||
if (this.elements.logConnectionStatus) {
|
||||
this.elements.logConnectionStatus.textContent = 'Error';
|
||||
this.elements.logConnectionStatus.className = 'status-disconnected';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
disconnectAllEventSources: function() {
|
||||
Object.values(this.eventSources).forEach(source => {
|
||||
if (source && source.readyState !== EventSource.CLOSED) {
|
||||
source.close();
|
||||
}
|
||||
});
|
||||
this.eventSources = {}; // Clear sources
|
||||
},
|
||||
|
||||
addLogMessage: function(logLine) {
|
||||
if (!this.elements.logsContainer) return;
|
||||
|
||||
const logEntry = document.createElement('div');
|
||||
logEntry.className = 'log-entry';
|
||||
// Add level-based coloring (similar to new-main.js)
|
||||
if (logLine.includes(' - INFO - ')) logEntry.classList.add('log-info');
|
||||
else if (logLine.includes(' - WARNING - ')) logEntry.classList.add('log-warning');
|
||||
else if (logLine.includes(' - ERROR - ')) logEntry.classList.add('log-error');
|
||||
else if (logLine.includes(' - DEBUG - ')) logEntry.classList.add('log-debug');
|
||||
|
||||
logEntry.textContent = logLine;
|
||||
this.elements.logsContainer.appendChild(logEntry);
|
||||
|
||||
if (this.autoScroll) {
|
||||
this.elements.logsContainer.scrollTop = this.elements.logsContainer.scrollHeight;
|
||||
}
|
||||
},
|
||||
|
||||
clearLogs: function() {
|
||||
if (this.elements.logsContainer) {
|
||||
this.elements.logsContainer.innerHTML = '';
|
||||
}
|
||||
},
|
||||
|
||||
// Settings handling
|
||||
loadAllSettings: function() {
|
||||
// Fetch combined settings
|
||||
fetch('/api/settings') // Use the combined endpoint
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
this.originalSettings = JSON.parse(JSON.stringify(data)); // Store originals
|
||||
// Populate forms for each app section present in the data
|
||||
['global', 'sonarr', 'radarr', 'lidarr', 'readarr', 'ui'].forEach(app => {
|
||||
if (data[app]) {
|
||||
this.populateSettingsForm(app, data[app]);
|
||||
}
|
||||
// Populate API keys if they exist at top level (backward compatibility)
|
||||
if (app !== 'global' && app !== 'ui' && data.api_url && data.api_key && app === data.global?.app_type) {
|
||||
this.populateApiSettings(app, data.api_url, data.api_key);
|
||||
}
|
||||
// Populate API keys from the app section itself (new structure)
|
||||
else if (data[app] && data[app].api_url !== undefined && data[app].api_key !== undefined) {
|
||||
this.populateApiSettings(app, data[app].api_url, data[app].api_key);
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading all settings:', error);
|
||||
this.showNotification('Error loading settings', 'error');
|
||||
});
|
||||
},
|
||||
|
||||
// Separate function to populate API fields to avoid duplication
|
||||
populateApiSettings: function(app, apiUrl, apiKey) {
|
||||
const urlInput = document.getElementById(`${app}_api_url`);
|
||||
const keyInput = document.getElementById(`${app}_api_key`);
|
||||
if (urlInput) urlInput.value = apiUrl || '';
|
||||
if (keyInput) keyInput.value = apiKey || '';
|
||||
},
|
||||
|
||||
// Simplified loadSettings - now part of loadAllSettings
|
||||
// loadSettings: function(app) { ... },
|
||||
|
||||
populateSettingsForm: function(app, settings) {
|
||||
const container = document.getElementById(`${app}Settings`);
|
||||
if (!container) return;
|
||||
|
||||
// Use SettingsForms helper if available (assuming it exists and works)
|
||||
if (window.SettingsForms && typeof window.SettingsForms[`generate${this.capitalizeFirst(app)}Form`] === 'function') {
|
||||
container.innerHTML = ''; // Clear previous
|
||||
window.SettingsForms[`generate${this.capitalizeFirst(app)}Form`](container, settings);
|
||||
} else {
|
||||
// Basic fallback population (similar to previous logic but simplified)
|
||||
console.warn(`Settings form generator for ${app} not found. Using basic population.`);
|
||||
for (const key in settings) {
|
||||
const input = container.querySelector(`#${app}_${key}`);
|
||||
if (input) {
|
||||
if (input.type === 'checkbox') {
|
||||
input.checked = settings[key];
|
||||
} else {
|
||||
input.value = settings[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Populate API fields separately using the dedicated function
|
||||
this.populateApiSettings(app, settings.api_url, settings.api_key);
|
||||
},
|
||||
|
||||
saveSettings: function() {
|
||||
const app = this.currentSettingsTab;
|
||||
const settings = this.collectSettingsFromForm(app);
|
||||
settings.app_type = app; // Add app_type for the backend
|
||||
|
||||
// Simple check for changes (can be enhanced)
|
||||
// if (JSON.stringify(settings) === JSON.stringify(this.originalSettings[app])) {
|
||||
// this.showNotification('No changes detected.', 'info');
|
||||
// return;
|
||||
// }
|
||||
|
||||
fetch(`/api/settings`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(settings)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
this.showNotification('Settings saved successfully', 'success');
|
||||
// Update original settings after save
|
||||
this.originalSettings = JSON.parse(JSON.stringify(data.settings || this.originalSettings));
|
||||
// Re-populate form to reflect saved state and update originals
|
||||
this.populateSettingsForm(app, this.originalSettings[app]);
|
||||
this.populateApiSettings(app, this.originalSettings[app]?.api_url, this.originalSettings[app]?.api_key);
|
||||
// Check connections again as API keys might have changed
|
||||
this.checkAppConnections();
|
||||
} else {
|
||||
this.showNotification(`Error saving settings: ${data.message || 'Unknown error'}`, 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error saving settings:', error);
|
||||
this.showNotification('Error saving settings', 'error');
|
||||
});
|
||||
},
|
||||
|
||||
resetSettings: function() {
|
||||
const app = this.currentSettingsTab;
|
||||
if (confirm(`Are you sure you want to reset ${app.toUpperCase()} settings to default values?`)) {
|
||||
fetch(`/api/settings/reset`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ app: app })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
this.showNotification(`${this.capitalizeFirst(app)} settings reset to defaults`, 'success');
|
||||
// Reload all settings to reflect the reset
|
||||
this.loadAllSettings();
|
||||
} else {
|
||||
this.showNotification(`Error resetting settings: ${data.message || 'Unknown error'}`, 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error resetting settings:', error);
|
||||
this.showNotification('Error resetting settings', 'error');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
collectSettingsFromForm: function(app) {
|
||||
const settings = {};
|
||||
const container = document.getElementById(`${app}Settings`);
|
||||
if (!container) return settings;
|
||||
|
||||
const inputs = container.querySelectorAll('input, select');
|
||||
inputs.forEach(input => {
|
||||
// Extract key name correctly (e.g., from 'sonarr_api_key' to 'api_key')
|
||||
const key = input.id.startsWith(`${app}_`) ? input.id.substring(app.length + 1) : input.id;
|
||||
|
||||
if (key) { // Ensure key is not empty
|
||||
if (input.type === 'checkbox') {
|
||||
settings[key] = input.checked;
|
||||
} else if (input.type === 'number') {
|
||||
// Try parsing as float first, then int, fallback to string
|
||||
const num = parseFloat(input.value);
|
||||
settings[key] = isNaN(num) ? input.value : num;
|
||||
} else {
|
||||
settings[key] = input.value;
|
||||
}
|
||||
}
|
||||
});
|
||||
return settings;
|
||||
},
|
||||
|
||||
// App connections
|
||||
checkAppConnections: function() {
|
||||
// Fetch the configuration status for all apps
|
||||
fetch('/api/configured-apps')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
this.configuredApps = data;
|
||||
// Update status indicators based on the fetched data
|
||||
Object.keys(this.configuredApps).forEach(app => {
|
||||
// For now, just update based on configured status
|
||||
// A real connection check might be needed separately
|
||||
this.updateConnectionStatus(app, this.configuredApps[app]);
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching configured apps:', error);
|
||||
// Assume all are disconnected on error
|
||||
Object.keys(this.configuredApps).forEach(app => {
|
||||
this.updateConnectionStatus(app, false);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// Simplified connection check - maybe call a dedicated /api/status/{app} later
|
||||
// checkAppConnection: function(app) { ... },
|
||||
|
||||
updateConnectionStatus: function(app, isConnected) {
|
||||
// Update status badges on the Home page
|
||||
const statusElement = this.elements[`${app}HomeStatus`];
|
||||
if (statusElement) {
|
||||
if (isConnected) {
|
||||
statusElement.className = 'status-badge connected';
|
||||
statusElement.innerHTML = '<i class="fas fa-check-circle"></i> Configured'; // Changed text
|
||||
} else {
|
||||
statusElement.className = 'status-badge not-connected';
|
||||
statusElement.innerHTML = '<i class="fas fa-times-circle"></i> Not Configured'; // Changed text
|
||||
}
|
||||
}
|
||||
// Potentially update status elsewhere (e.g., settings page)
|
||||
},
|
||||
|
||||
// User actions
|
||||
startHunt: function() {
|
||||
fetch('/api/hunt/start', { method: 'POST' })
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
this.showNotification(data.message || (data.success ? 'Hunt started' : 'Failed to start hunt'), data.success ? 'success' : 'error');
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error starting hunt:', error);
|
||||
this.showNotification('Error starting hunt', 'error');
|
||||
});
|
||||
},
|
||||
|
||||
stopHunt: function() {
|
||||
fetch('/api/hunt/stop', { method: 'POST' })
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
this.showNotification(data.message || (data.success ? 'Hunt stopped' : 'Failed to stop hunt'), data.success ? 'success' : 'error');
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error stopping hunt:', error);
|
||||
this.showNotification('Error stopping hunt', 'error');
|
||||
});
|
||||
},
|
||||
|
||||
// Theme handling
|
||||
loadTheme: function() {
|
||||
fetch('/api/settings/theme')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
this.setTheme(data.dark_mode);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading theme:', error);
|
||||
// Default to dark mode on error?
|
||||
this.setTheme(true);
|
||||
});
|
||||
},
|
||||
|
||||
setTheme: function(isDark) {
|
||||
this.darkMode = isDark;
|
||||
if (isDark) {
|
||||
document.body.classList.add('dark-theme');
|
||||
document.body.classList.remove('light-theme');
|
||||
} else {
|
||||
document.body.classList.remove('dark-theme');
|
||||
document.body.classList.add('light-theme');
|
||||
}
|
||||
if (this.elements.themeToggle) {
|
||||
this.elements.themeToggle.checked = isDark;
|
||||
}
|
||||
// Store preference
|
||||
localStorage.setItem('huntarr-dark-mode', isDark);
|
||||
},
|
||||
|
||||
handleThemeToggle: function(e) {
|
||||
const isDarkMode = e.target.checked;
|
||||
this.setTheme(isDarkMode);
|
||||
// Save theme preference to backend
|
||||
fetch('/api/settings/theme', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ dark_mode: isDarkMode })
|
||||
}).catch(error => console.error('Error saving theme:', error));
|
||||
},
|
||||
|
||||
// User info
|
||||
loadUsername: function() {
|
||||
// Assuming username is fetched elsewhere or passed via template
|
||||
// If fetched via API:
|
||||
/*
|
||||
fetch('/api/user/info') // Example endpoint
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.username && this.elements.usernameDisplay) {
|
||||
this.elements.usernameDisplay.textContent = data.username;
|
||||
}
|
||||
})
|
||||
.catch(error => console.error('Error loading username:', error));
|
||||
*/
|
||||
},
|
||||
|
||||
// Utility functions
|
||||
showNotification: function(message, type = 'info') {
|
||||
const container = document.getElementById('notification-container') || document.body;
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `notification ${type}`;
|
||||
notification.textContent = message;
|
||||
|
||||
// Remove existing notifications
|
||||
const existing = container.querySelector('.notification');
|
||||
if (existing) existing.remove();
|
||||
|
||||
container.appendChild(notification);
|
||||
|
||||
// Auto-remove after a delay
|
||||
setTimeout(() => {
|
||||
notification.classList.add('fade-out');
|
||||
setTimeout(() => notification.remove(), 500); // Match fade-out duration
|
||||
}, 3000);
|
||||
},
|
||||
|
||||
capitalizeFirst: function(string) {
|
||||
return string ? string.charAt(0).toUpperCase() + string.slice(1) : '';
|
||||
}
|
||||
};
|
||||
+185
-94
@@ -16,6 +16,7 @@ const huntarrUI = {
|
||||
radarr: false,
|
||||
lidarr: false
|
||||
},
|
||||
originalSettings: {}, // Store the full original settings object
|
||||
|
||||
// Logo URL
|
||||
logoUrl: '/static/logo/64.png',
|
||||
@@ -144,6 +145,29 @@ const huntarrUI = {
|
||||
|
||||
// Handle window hash change
|
||||
window.addEventListener('hashchange', this.handleHashNavigation.bind(this));
|
||||
|
||||
// Add listeners to settings forms AFTER they are populated
|
||||
// This needs to be done dynamically or delegated
|
||||
// Option: Use event delegation on the settings form container
|
||||
const settingsFormContainer = document.querySelector('.settings-form');
|
||||
if (settingsFormContainer) {
|
||||
settingsFormContainer.addEventListener('input', (event) => {
|
||||
if (event.target.closest('.app-settings-panel.active')) {
|
||||
// Check if the target is an input, select, or textarea within the active panel
|
||||
if (event.target.matches('input, select, textarea')) {
|
||||
this.handleSettingChange();
|
||||
}
|
||||
}
|
||||
});
|
||||
settingsFormContainer.addEventListener('change', (event) => {
|
||||
if (event.target.closest('.app-settings-panel.active')) {
|
||||
// Handle changes for checkboxes and selects that use 'change' event
|
||||
if (event.target.matches('input[type="checkbox"], select')) {
|
||||
this.handleSettingChange();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Setup logo handling to prevent flashing during navigation
|
||||
@@ -268,7 +292,10 @@ const huntarrUI = {
|
||||
if (panelElement) {
|
||||
panelElement.classList.add('active');
|
||||
this.currentSettingsTab = tab;
|
||||
this.loadSettings(tab);
|
||||
// Ensure settings are populated for this tab using the stored originalSettings
|
||||
this.populateSettingsForm(tab, this.originalSettings[tab] || {});
|
||||
// Reset save button state when switching tabs
|
||||
this.updateSaveResetButtonState(tab, false);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -371,128 +398,162 @@ const huntarrUI = {
|
||||
|
||||
// Settings handling
|
||||
loadAllSettings: function() {
|
||||
this.loadSettings('global');
|
||||
this.loadSettings('sonarr');
|
||||
this.loadSettings('radarr');
|
||||
this.loadSettings('lidarr');
|
||||
},
|
||||
|
||||
loadSettings: function(app) {
|
||||
fetch(`/api/settings/${app}`)
|
||||
.then(response => response.json())
|
||||
console.log("[huntarrUI] Loading all settings...");
|
||||
fetch(`/api/settings`) // Fetch the entire settings object
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
this.populateSettingsForm(app, data);
|
||||
console.log("[huntarrUI] All settings loaded:", data);
|
||||
this.originalSettings = JSON.parse(JSON.stringify(data)); // Store deep copy
|
||||
|
||||
// Populate the currently active settings form
|
||||
this.populateSettingsForm(this.currentSettingsTab, this.originalSettings[this.currentSettingsTab] || {});
|
||||
// Optionally pre-populate others if needed, but might be redundant if done on tab switch
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(`Error loading ${app} settings:`, error);
|
||||
console.error(`Error loading all settings:`, error);
|
||||
this.showNotification(`Error loading settings: ${error.message}`, 'error');
|
||||
this.originalSettings = {}; // Reset on error
|
||||
});
|
||||
},
|
||||
|
||||
populateSettingsForm: function(app, settings) {
|
||||
populateSettingsForm: function(app, appSettings) {
|
||||
const container = document.getElementById(`${app}Settings`);
|
||||
if (!container) return;
|
||||
|
||||
// If we already populated this container, don't do it again
|
||||
if (container.querySelector('.settings-group')) return;
|
||||
|
||||
// Create groups based on settings categories
|
||||
const groups = {};
|
||||
|
||||
// For demonstration, create some example settings
|
||||
// In reality, this would dynamically create settings based on the API response
|
||||
if (app === 'sonarr' || app === 'radarr' || app === 'lidarr') {
|
||||
container.innerHTML = `
|
||||
<div class="settings-group">
|
||||
<h3>${this.capitalizeFirst(app)} Connection</h3>
|
||||
<div class="setting-item">
|
||||
<label for="${app}_url">URL:</label>
|
||||
<input type="text" id="${app}_url" value="${settings.url || ''}">
|
||||
<p class="setting-help">Base URL for ${this.capitalizeFirst(app)} (e.g., http://localhost:8989)</p>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label for="${app}_api_key">API Key:</label>
|
||||
<input type="text" id="${app}_api_key" value="${settings.api_key || ''}">
|
||||
<p class="setting-help">API key for ${this.capitalizeFirst(app)}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<h3>Search Settings</h3>
|
||||
<div class="setting-item">
|
||||
<label for="${app}_search_type">Search Type:</label>
|
||||
<select id="${app}_search_type">
|
||||
<option value="random" ${settings.search_type === 'random' ? 'selected' : ''}>Random</option>
|
||||
<option value="sequential" ${settings.search_type === 'sequential' ? 'selected' : ''}>Sequential</option>
|
||||
</select>
|
||||
<p class="setting-help">How to select items to search</p>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label for="${app}_search_interval">Search Interval:</label>
|
||||
<input type="number" id="${app}_search_interval" value="${settings.search_interval || 15}" min="1">
|
||||
<p class="setting-help">Interval between searches (seconds)</p>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label for="${app}_enabled">Enable ${this.capitalizeFirst(app)}:</label>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="${app}_enabled" ${settings.enabled ? 'checked' : ''}>
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
<p class="setting-help">Toggle ${this.capitalizeFirst(app)} functionality</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
if (!container) {
|
||||
console.warn(`[huntarrUI] Container not found for populating settings: ${app}Settings`);
|
||||
return;
|
||||
}
|
||||
console.log(`[huntarrUI] Populating settings form for ${app}`, appSettings);
|
||||
|
||||
// Use SettingsForms to generate the form structure if not already present
|
||||
// This assumes SettingsForms is available globally or imported
|
||||
if (typeof SettingsForms !== 'undefined' && !container.querySelector('.settings-group')) {
|
||||
const formGenerator = SettingsForms[`generate${this.capitalizeFirst(app)}Form`];
|
||||
if (formGenerator) {
|
||||
console.log(`[huntarrUI] Generating form structure for ${app}`);
|
||||
formGenerator(container, appSettings); // Generate structure AND populate initial values
|
||||
} else {
|
||||
console.warn(`[huntarrUI] Form generator not found for ${app}`);
|
||||
}
|
||||
} else {
|
||||
// If form structure exists, just update values
|
||||
console.log(`[huntarrUI] Updating existing form values for ${app}`);
|
||||
const inputs = container.querySelectorAll('input, select, textarea');
|
||||
inputs.forEach(input => {
|
||||
const key = input.id.replace(`${app}_`, ''); // Get the setting key from the ID
|
||||
|
||||
if (appSettings.hasOwnProperty(key)) {
|
||||
const value = appSettings[key];
|
||||
if (input.type === 'checkbox') {
|
||||
input.checked = value === true;
|
||||
} else if (input.type === 'radio') {
|
||||
// Handle radio buttons if necessary (check by value)
|
||||
if (input.value === String(value)) {
|
||||
input.checked = true;
|
||||
}
|
||||
} else {
|
||||
input.value = value;
|
||||
}
|
||||
} else {
|
||||
// Optional: Clear or set default for fields not in settings?
|
||||
// console.warn(`[huntarrUI] Setting key "${key}" not found in settings for ${app}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Special handling for duration displays if needed (might be better in SettingsForms)
|
||||
if (typeof SettingsForms !== 'undefined' && typeof SettingsForms.updateDurationDisplay === 'function') {
|
||||
SettingsForms.updateDurationDisplay();
|
||||
}
|
||||
|
||||
// Ensure save/reset buttons are initially disabled after populating
|
||||
this.updateSaveResetButtonState(app, false);
|
||||
},
|
||||
|
||||
|
||||
// Called when any setting input changes in the active tab
|
||||
handleSettingChange: function() {
|
||||
console.log(`[huntarrUI] Setting change detected in tab: ${this.currentSettingsTab}`);
|
||||
this.updateSaveResetButtonState(this.currentSettingsTab, true); // Enable save button
|
||||
},
|
||||
|
||||
saveSettings: function() {
|
||||
const app = this.currentSettingsTab;
|
||||
console.log(`[huntarrUI] saveSettings called for app: ${app}`); // Added log
|
||||
console.log(`[huntarrUI] saveSettings called for app: ${app}`);
|
||||
const settings = this.collectSettingsFromForm(app);
|
||||
|
||||
|
||||
if (!settings) {
|
||||
console.error(`[huntarrUI] Failed to collect settings for app: ${app}`); // Added log
|
||||
console.error(`[huntarrUI] Failed to collect settings for app: ${app}`);
|
||||
this.showNotification('Error collecting settings from form.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[huntarrUI] Collected settings for ${app}:`, settings); // Added log
|
||||
|
||||
// Add app_type to the payload
|
||||
settings.app_type = app;
|
||||
|
||||
console.log(`[huntarrUI] Sending settings payload for ${app}:`, settings); // Added log
|
||||
console.log(`[huntarrUI] Collected settings for ${app}:`, settings);
|
||||
|
||||
// Add app_type to the payload if needed by backend (confirm backend logic)
|
||||
// Assuming the backend merges based on the top-level key matching the app name
|
||||
const payload = { [app]: settings };
|
||||
|
||||
console.log(`[huntarrUI] Sending settings payload for ${app}:`, payload);
|
||||
|
||||
// Use the correct endpoint /api/settings
|
||||
fetch(`/api/settings`, { // Corrected endpoint
|
||||
fetch(`/api/settings`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(settings)
|
||||
body: JSON.stringify(payload) // Send payload structured as { appName: { settings... } }
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
this.showNotification('Settings saved successfully', 'success');
|
||||
|
||||
// Reload settings to update original values and check connection status
|
||||
this.loadSettings(app, true); // Force reload
|
||||
|
||||
// Update connection status if connection settings changed
|
||||
// This might be handled within loadSettings now
|
||||
// if (app !== 'global') {
|
||||
// this.checkAppConnection(app);
|
||||
// }
|
||||
} else {
|
||||
this.showNotification(`Error saving settings: ${data.message || 'Unknown error'}`, 'error');
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
// Try to get error message from response body
|
||||
return response.json().then(errData => {
|
||||
throw new Error(errData.error || `HTTP error! status: ${response.status}`);
|
||||
}).catch(() => {
|
||||
// Fallback if response body is not JSON or empty
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
});
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(savedConfig => { // Backend returns the full, updated config
|
||||
console.log('[huntarrUI] Settings saved successfully. Full config received:', savedConfig);
|
||||
this.showNotification('Settings saved successfully', 'success');
|
||||
|
||||
// Update original settings state with the full config returned from backend
|
||||
// Ensure savedConfig is the full object { sonarr: {...}, radarr: {...}, ... }
|
||||
if (typeof savedConfig === 'object' && savedConfig !== null) {
|
||||
this.originalSettings = JSON.parse(JSON.stringify(savedConfig));
|
||||
} else {
|
||||
console.error('[huntarrUI] Invalid config received from backend after save:', savedConfig);
|
||||
// Attempt to reload all settings as a fallback
|
||||
this.loadAllSettings();
|
||||
return; // Avoid further processing with invalid data
|
||||
}
|
||||
|
||||
// Re-populate the current form with the saved data for consistency
|
||||
const currentAppSettings = this.originalSettings[app] || {};
|
||||
this.populateSettingsForm(app, currentAppSettings);
|
||||
|
||||
// Update connection status for the saved app
|
||||
this.checkAppConnection(app);
|
||||
|
||||
// Update general UI elements like home page statuses
|
||||
this.updateHomeConnectionStatus(); // Assuming this function exists and works
|
||||
|
||||
// Disable save/reset buttons as changes are now saved
|
||||
this.updateSaveResetButtonState(app, false);
|
||||
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error saving settings:', error);
|
||||
this.showNotification('Error saving settings', 'error');
|
||||
this.showNotification(`Error saving settings: ${error.message}`, 'error');
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
resetSettings: function() {
|
||||
if (confirm('Are you sure you want to reset these settings to default values?')) {
|
||||
const app = this.currentSettingsTab;
|
||||
@@ -713,7 +774,37 @@ const huntarrUI = {
|
||||
|
||||
capitalizeFirst: function(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
},
|
||||
|
||||
// Add or modify this function to handle enabling/disabling save/reset
|
||||
updateSaveResetButtonState: function(app, hasChanges) {
|
||||
// Find buttons relevant to the current app context if they exist
|
||||
// This might need adjustment based on actual button IDs/classes
|
||||
const saveButton = this.elements.saveSettingsButton; // Assuming a general save button
|
||||
const resetButton = this.elements.resetSettingsButton; // Assuming a general reset button
|
||||
|
||||
if (saveButton) {
|
||||
saveButton.disabled = !hasChanges;
|
||||
// Add/remove a class for styling disabled state if needed
|
||||
if (hasChanges) {
|
||||
saveButton.classList.remove('disabled-button'); // Example class
|
||||
} else {
|
||||
saveButton.classList.add('disabled-button'); // Example class
|
||||
}
|
||||
}
|
||||
// Reset button logic (enable/disable based on changes or always enabled?)
|
||||
// if (resetButton) {
|
||||
// resetButton.disabled = !hasChanges;
|
||||
// }
|
||||
},
|
||||
|
||||
// Add updateHomeConnectionStatus if it doesn't exist or needs adjustment
|
||||
updateHomeConnectionStatus: function() {
|
||||
console.log('[huntarrUI] Updating home connection statuses...');
|
||||
// This function should ideally call checkAppConnection for all relevant apps
|
||||
// or use the stored configuredApps status if checkAppConnection updates it.
|
||||
this.checkAppConnections(); // Re-check all connections after a save might be simplest
|
||||
},
|
||||
};
|
||||
|
||||
// Initialize when document is ready
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
/**
|
||||
* Settings Initializer
|
||||
* Ensures settings forms are properly loaded on page load
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Check if we're on the settings page
|
||||
if (window.location.pathname === '/settings' || window.location.hash === '#settings') {
|
||||
console.log('Settings page detected, initializing settings...');
|
||||
|
||||
// Initialize active tab settings
|
||||
initializeActiveTabSettings();
|
||||
|
||||
// Set up tab switching
|
||||
setupTabSwitching();
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize settings for the active tab
|
||||
function initializeActiveTabSettings() {
|
||||
const activeTab = document.querySelector('.settings-tab.active');
|
||||
if (!activeTab) {
|
||||
console.warn('No active settings tab found');
|
||||
return;
|
||||
}
|
||||
|
||||
const app = activeTab.getAttribute('data-settings');
|
||||
if (!app) {
|
||||
console.warn('Active tab has no data-settings attribute');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Loading settings for active tab: ${app}`);
|
||||
loadAppSettings(app);
|
||||
}
|
||||
|
||||
// Load settings for a specific app
|
||||
function loadAppSettings(app) {
|
||||
console.log(`Fetching settings for ${app}...`);
|
||||
|
||||
// First get general settings
|
||||
fetch('/api/settings')
|
||||
.then(response => response.json())
|
||||
.then(allSettings => {
|
||||
console.log('All settings loaded:', allSettings);
|
||||
|
||||
// Then get app-specific API settings if needed
|
||||
fetch(`/api/app-settings?app=${app}`)
|
||||
.then(response => response.json())
|
||||
.then(apiData => {
|
||||
console.log(`API data for ${app}:`, apiData);
|
||||
|
||||
// Prepare settings object
|
||||
const settings = allSettings;
|
||||
|
||||
// Add API credentials if available
|
||||
if (apiData && apiData.success) {
|
||||
settings.api_url = apiData.api_url;
|
||||
settings.api_key = apiData.api_key;
|
||||
}
|
||||
|
||||
// Find the container for this app's settings
|
||||
const container = document.getElementById(`${app}Settings`);
|
||||
if (!container) {
|
||||
console.error(`Container for ${app} settings not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Generating form for ${app}...`);
|
||||
|
||||
// Generate the form based on the app type
|
||||
switch(app) {
|
||||
case 'sonarr':
|
||||
SettingsForms.generateSonarrForm(container, settings);
|
||||
break;
|
||||
case 'radarr':
|
||||
SettingsForms.generateRadarrForm(container, settings);
|
||||
break;
|
||||
case 'lidarr':
|
||||
SettingsForms.generateLidarrForm(container, settings);
|
||||
break;
|
||||
case 'readarr':
|
||||
SettingsForms.generateReadarrForm(container, settings);
|
||||
break;
|
||||
case 'global':
|
||||
SettingsForms.generateGlobalForm(container, settings);
|
||||
break;
|
||||
default:
|
||||
console.error(`Unknown app type: ${app}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update any dynamic displays
|
||||
if (typeof SettingsForms.updateDurationDisplay === 'function') {
|
||||
SettingsForms.updateDurationDisplay();
|
||||
}
|
||||
|
||||
console.log(`${app} settings form generated successfully`);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(`Error loading API settings for ${app}:`, error);
|
||||
const container = document.getElementById(`${app}Settings`);
|
||||
if (container) {
|
||||
container.innerHTML = `<div class="error-message">Error loading API settings: ${error.message}</div>`;
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(`Error loading settings:`, error);
|
||||
const container = document.getElementById(`${app}Settings`);
|
||||
if (container) {
|
||||
container.innerHTML = `<div class="error-message">Error loading settings: ${error.message}</div>`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Set up tab switching
|
||||
function setupTabSwitching() {
|
||||
const settingsTabs = document.querySelectorAll('.settings-tab');
|
||||
settingsTabs.forEach(tab => {
|
||||
tab.addEventListener('click', function() {
|
||||
const app = this.getAttribute('data-settings');
|
||||
|
||||
// Update active tab
|
||||
settingsTabs.forEach(t => t.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
|
||||
// Show selected settings panel
|
||||
const panels = document.querySelectorAll('.app-settings-panel');
|
||||
panels.forEach(panel => panel.classList.remove('active'));
|
||||
|
||||
const selectedPanel = document.getElementById(`${app}Settings`);
|
||||
if (selectedPanel) {
|
||||
selectedPanel.classList.add('active');
|
||||
|
||||
// Check if settings are already loaded
|
||||
if (!selectedPanel.querySelector('.settings-group')) {
|
||||
loadAppSettings(app);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,375 +0,0 @@
|
||||
/**
|
||||
* Settings loader for Huntarr
|
||||
* This file handles loading settings for each app tab
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize settings on page load
|
||||
initializeSettings();
|
||||
|
||||
// Set up tab switching
|
||||
setupTabSwitching();
|
||||
|
||||
// REMOVED call to setupSettingsButtons
|
||||
// setupSettingsButtons();
|
||||
});
|
||||
|
||||
// Initialize settings
|
||||
function initializeSettings() {
|
||||
// Load settings for all apps
|
||||
loadAppSettings('sonarr');
|
||||
loadAppSettings('radarr');
|
||||
loadAppSettings('lidarr');
|
||||
loadAppSettings('global');
|
||||
}
|
||||
|
||||
// Load settings for a specific app
|
||||
function loadAppSettings(app) {
|
||||
fetch(`/api/settings`)
|
||||
.then(response => response.json())
|
||||
.then(allSettings => {
|
||||
// Extract the app-specific settings
|
||||
let settings = {};
|
||||
|
||||
// Get general settings
|
||||
if (app === 'global') {
|
||||
settings = allSettings.global || {};
|
||||
if (allSettings.ui) {
|
||||
settings.ui = allSettings.ui;
|
||||
}
|
||||
} else {
|
||||
// Extract app-specific settings
|
||||
settings = allSettings[app] || {};
|
||||
}
|
||||
|
||||
// Get app-specific API settings if needed
|
||||
fetch(`/api/app-settings?app=${app}`)
|
||||
.then(response => response.json())
|
||||
.then(apiData => {
|
||||
// Merge the API credentials with the settings
|
||||
if (apiData && apiData.success) {
|
||||
settings.api_url = apiData.api_url;
|
||||
settings.api_key = apiData.api_key;
|
||||
}
|
||||
|
||||
// Find the container for this app's settings
|
||||
const container = document.getElementById(`${app}Settings`);
|
||||
if (!container) return;
|
||||
|
||||
// Generate the form based on the app type
|
||||
switch(app) {
|
||||
case 'sonarr':
|
||||
SettingsForms.generateSonarrForm(container, settings);
|
||||
break;
|
||||
case 'radarr':
|
||||
SettingsForms.generateRadarrForm(container, settings);
|
||||
break;
|
||||
case 'lidarr':
|
||||
SettingsForms.generateLidarrForm(container, settings);
|
||||
break;
|
||||
case 'readarr':
|
||||
SettingsForms.generateReadarrForm(container, settings);
|
||||
break;
|
||||
case 'global':
|
||||
SettingsForms.generateGlobalForm(container, settings);
|
||||
break;
|
||||
}
|
||||
|
||||
// Update any dynamic displays
|
||||
SettingsForms.updateDurationDisplay();
|
||||
|
||||
// Set up test connection buttons
|
||||
setupTestConnectionButtons();
|
||||
|
||||
console.log(`${app} settings loaded successfully`, settings);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(`Error loading API settings for ${app}:`, error);
|
||||
|
||||
// If API settings fetch fails, still try to load the form with what we have
|
||||
const container = document.getElementById(`${app}Settings`);
|
||||
if (!container) return;
|
||||
|
||||
// Generate the form based on the app type
|
||||
switch(app) {
|
||||
case 'sonarr':
|
||||
SettingsForms.generateSonarrForm(container, settings);
|
||||
break;
|
||||
case 'radarr':
|
||||
SettingsForms.generateRadarrForm(container, settings);
|
||||
break;
|
||||
case 'lidarr':
|
||||
SettingsForms.generateLidarrForm(container, settings);
|
||||
break;
|
||||
case 'readarr':
|
||||
SettingsForms.generateReadarrForm(container, settings);
|
||||
break;
|
||||
case 'global':
|
||||
SettingsForms.generateGlobalForm(container, settings);
|
||||
break;
|
||||
}
|
||||
|
||||
// Update any dynamic displays
|
||||
SettingsForms.updateDurationDisplay();
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(`Error loading settings for ${app}:`, error);
|
||||
|
||||
// If the settings fetch fails entirely, try to load with default empty settings
|
||||
const container = document.getElementById(`${app}Settings`);
|
||||
if (!container) return;
|
||||
|
||||
// Generate the form with default values
|
||||
switch(app) {
|
||||
case 'sonarr':
|
||||
SettingsForms.generateSonarrForm(container, getDefaultSettings('sonarr'));
|
||||
break;
|
||||
case 'radarr':
|
||||
SettingsForms.generateRadarrForm(container, getDefaultSettings('radarr'));
|
||||
break;
|
||||
case 'lidarr':
|
||||
SettingsForms.generateLidarrForm(container, getDefaultSettings('lidarr'));
|
||||
break;
|
||||
case 'readarr':
|
||||
SettingsForms.generateReadarrForm(container, getDefaultSettings('readarr'));
|
||||
break;
|
||||
case 'global':
|
||||
SettingsForms.generateGlobalForm(container, getDefaultSettings('global'));
|
||||
break;
|
||||
}
|
||||
|
||||
// Update any dynamic displays
|
||||
SettingsForms.updateDurationDisplay();
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to get default settings for any app
|
||||
function getDefaultSettings(app) {
|
||||
switch (app) {
|
||||
case 'sonarr':
|
||||
return {
|
||||
api_url: '',
|
||||
api_key: '',
|
||||
hunt_missing_shows: 1,
|
||||
hunt_upgrade_episodes: 0,
|
||||
sleep_duration: 900,
|
||||
state_reset_interval_hours: 168,
|
||||
monitored_only: true,
|
||||
skip_future_episodes: true,
|
||||
skip_series_refresh: false,
|
||||
random_missing: true,
|
||||
random_upgrades: true,
|
||||
debug_mode: false,
|
||||
api_timeout: 60,
|
||||
command_wait_delay: 1,
|
||||
command_wait_attempts: 600,
|
||||
minimum_download_queue_size: -1,
|
||||
log_refresh_interval_seconds: 30
|
||||
};
|
||||
case 'radarr':
|
||||
return {
|
||||
api_url: '',
|
||||
api_key: '',
|
||||
hunt_missing_movies: 1,
|
||||
hunt_upgrade_movies: 0,
|
||||
sleep_duration: 900,
|
||||
state_reset_interval_hours: 168,
|
||||
monitored_only: true,
|
||||
skip_future_releases: true,
|
||||
skip_movie_refresh: false,
|
||||
random_missing: true,
|
||||
random_upgrades: true,
|
||||
debug_mode: false,
|
||||
api_timeout: 60,
|
||||
command_wait_delay: 1,
|
||||
command_wait_attempts: 600,
|
||||
minimum_download_queue_size: -1,
|
||||
log_refresh_interval_seconds: 30
|
||||
};
|
||||
case 'lidarr':
|
||||
return {
|
||||
api_url: '',
|
||||
api_key: '',
|
||||
hunt_missing_albums: 1,
|
||||
hunt_upgrade_tracks: 0,
|
||||
sleep_duration: 900,
|
||||
state_reset_interval_hours: 168,
|
||||
monitored_only: true,
|
||||
skip_future_releases: true,
|
||||
skip_artist_refresh: false,
|
||||
random_missing: true,
|
||||
random_upgrades: true,
|
||||
debug_mode: false,
|
||||
api_timeout: 60,
|
||||
command_wait_delay: 1,
|
||||
command_wait_attempts: 600,
|
||||
minimum_download_queue_size: -1,
|
||||
log_refresh_interval_seconds: 30
|
||||
};
|
||||
case 'readarr':
|
||||
return {
|
||||
api_url: '',
|
||||
api_key: '',
|
||||
hunt_missing_books: 1,
|
||||
hunt_upgrade_books: 0,
|
||||
sleep_duration: 900,
|
||||
state_reset_interval_hours: 168,
|
||||
monitored_only: true,
|
||||
skip_future_releases: true,
|
||||
skip_author_refresh: false,
|
||||
random_missing: true,
|
||||
random_upgrades: true,
|
||||
debug_mode: false,
|
||||
api_timeout: 60,
|
||||
command_wait_delay: 1,
|
||||
command_wait_attempts: 600,
|
||||
minimum_download_queue_size: -1,
|
||||
log_refresh_interval_seconds: 30
|
||||
};
|
||||
default:
|
||||
// Return a generic structure or handle unknown apps
|
||||
console.warn(`Requesting default settings for unknown app: ${app}`);
|
||||
return {
|
||||
api_url: '',
|
||||
api_key: '',
|
||||
// Add other common fields if applicable, or leave empty
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Set up tab switching
|
||||
function setupTabSwitching() {
|
||||
const settingsTabs = document.querySelectorAll('.settings-tab');
|
||||
settingsTabs.forEach(tab => {
|
||||
tab.addEventListener('click', function() {
|
||||
const app = this.getAttribute('data-settings');
|
||||
|
||||
// Update active tab
|
||||
settingsTabs.forEach(t => t.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
|
||||
// Show selected settings panel
|
||||
const panels = document.querySelectorAll('.app-settings-panel');
|
||||
panels.forEach(panel => panel.classList.remove('active'));
|
||||
|
||||
const selectedPanel = document.getElementById(`${app}Settings`);
|
||||
if (selectedPanel) {
|
||||
selectedPanel.classList.add('active');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Set up test connection buttons - remove this function or empty it
|
||||
function setupTestConnectionButtons() {
|
||||
// Function emptied - no longer setting up test connection buttons
|
||||
}
|
||||
|
||||
// Set up save and reset buttons - REMOVED to avoid conflict with new-main.js
|
||||
/*
|
||||
function setupSettingsButtons() {
|
||||
// Save settings button
|
||||
const saveBtn = document.getElementById('saveSettingsButton');
|
||||
if (saveBtn) {
|
||||
saveBtn.addEventListener('click', function() {
|
||||
saveCurrentSettings();
|
||||
});
|
||||
}
|
||||
|
||||
// Reset settings button
|
||||
const resetBtn = document.getElementById('resetSettingsButton');
|
||||
if (resetBtn) {
|
||||
resetBtn.addEventListener('click', function() {
|
||||
resetCurrentSettings();
|
||||
});
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// Helper function to reload settings from server
|
||||
function reloadSettingsFromServer(app) {
|
||||
// Force a full reload of all settings from the server
|
||||
fetch('/api/settings/refresh', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(refreshResponse => refreshResponse.json())
|
||||
.then(refreshData => {
|
||||
console.log('Settings refreshed from server');
|
||||
|
||||
// Now reload the app settings to display the current values
|
||||
loadAppSettings(app);
|
||||
|
||||
// If we're using a module-specific loadSettings function, call that too
|
||||
if (window.huntarrApp && window.huntarrApp.currentApp === app) {
|
||||
if (app === 'sonarr' && window.huntarrApp.sonarrModule && window.huntarrApp.sonarrModule.loadSettings) {
|
||||
window.huntarrApp.sonarrModule.loadSettings();
|
||||
} else if (app === 'radarr' && window.huntarrApp.radarrModule && window.huntarrApp.radarrModule.loadSettings) {
|
||||
window.huntarrApp.radarrModule.loadSettings();
|
||||
} else if (app === 'lidarr' && window.huntarrApp.lidarrModule && window.huntarrApp.lidarrModule.loadSettings) {
|
||||
window.huntarrApp.lidarrModule.loadSettings();
|
||||
} else if (app === 'readarr' && window.huntarrApp.readarrModule && window.huntarrApp.readarrModule.loadSettings) {
|
||||
window.huntarrApp.readarrModule.loadSettings();
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(refreshError => {
|
||||
console.error('Error refreshing settings:', refreshError);
|
||||
// Still try to reload the settings
|
||||
loadAppSettings(app);
|
||||
});
|
||||
}
|
||||
|
||||
// Show a non-blocking notification instead of using alert()
|
||||
function showNotification(message, type = 'info') {
|
||||
// Create or find the notification container
|
||||
let notificationContainer = document.getElementById('notification-container');
|
||||
if (!notificationContainer) {
|
||||
notificationContainer = document.createElement('div');
|
||||
notificationContainer.id = 'notification-container';
|
||||
notificationContainer.style.position = 'fixed';
|
||||
notificationContainer.style.top = '20px';
|
||||
notificationContainer.style.right = '20px';
|
||||
notificationContainer.style.zIndex = '1000';
|
||||
document.body.appendChild(notificationContainer);
|
||||
}
|
||||
|
||||
// Create the notification element
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `notification ${type}`;
|
||||
notification.textContent = message;
|
||||
notification.style.backgroundColor = type === 'success' ? 'rgba(46, 204, 113, 0.9)' :
|
||||
type === 'error' ? 'rgba(231, 76, 60, 0.9)' :
|
||||
'rgba(52, 152, 219, 0.9)';
|
||||
notification.style.color = 'white';
|
||||
notification.style.padding = '12px 20px';
|
||||
notification.style.marginBottom = '10px';
|
||||
notification.style.borderRadius = '4px';
|
||||
notification.style.boxShadow = '0 2px 10px rgba(0,0,0,0.2)';
|
||||
notification.style.display = 'block';
|
||||
notification.style.opacity = '0';
|
||||
notification.style.transition = 'opacity 0.3s ease-in-out';
|
||||
|
||||
// Add the notification to the container
|
||||
notificationContainer.appendChild(notification);
|
||||
|
||||
// Fade in
|
||||
setTimeout(() => {
|
||||
notification.style.opacity = '1';
|
||||
}, 10);
|
||||
|
||||
// Remove after delay
|
||||
setTimeout(() => {
|
||||
notification.style.opacity = '0';
|
||||
setTimeout(() => {
|
||||
notification.remove();
|
||||
// Remove container if empty
|
||||
if (notificationContainer.children.length === 0) {
|
||||
notificationContainer.remove();
|
||||
}
|
||||
}, 300);
|
||||
}, 3000);
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/**
|
||||
* Settings synchronization utilities for Huntarr
|
||||
* Ensures all JS modules are updated when settings are saved
|
||||
*/
|
||||
|
||||
// Event to notify system of settings changes
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Create a custom event system for settings changes
|
||||
window.HuntarrEvents = window.HuntarrEvents || {
|
||||
// Dispatch a settings changed event
|
||||
dispatchSettingsChanged: function(app) {
|
||||
const event = new CustomEvent('huntarr:settings-changed', {
|
||||
detail: { app: app }
|
||||
});
|
||||
document.dispatchEvent(event);
|
||||
},
|
||||
|
||||
// Add a listener for settings changes
|
||||
onSettingsChanged: function(callback) {
|
||||
document.addEventListener('huntarr:settings-changed', callback);
|
||||
}
|
||||
};
|
||||
|
||||
// Listen for clicks on save buttons and trigger proper reload
|
||||
// const saveButtons = document.querySelectorAll('.save-button, [id^="saveSettings"]');
|
||||
// saveButtons.forEach(button => {
|
||||
// button.addEventListener('click', function() {
|
||||
// // Determine which app's settings are being saved
|
||||
// let app = 'global';
|
||||
// const activeSettingsTab = document.querySelector('.settings-tab.active');
|
||||
// if (activeSettingsTab) {
|
||||
// app = activeSettingsTab.getAttribute('data-settings') || 'global';
|
||||
// }
|
||||
|
||||
// // Dispatch the event after a short delay to allow save operation to potentially complete
|
||||
// setTimeout(() => {
|
||||
// console.log(`Settings sync: Dispatching settings-changed for ${app}`);
|
||||
// window.HuntarrEvents.dispatchSettingsChanged(app);
|
||||
// }, 100); // Delay to ensure save logic runs first
|
||||
// });
|
||||
// });
|
||||
});
|
||||
|
||||
// Helper to parse numeric values consistently
|
||||
function parseNumericSetting(value, defaultValue = 0) {
|
||||
if (value === undefined || value === null) return defaultValue;
|
||||
if (typeof value === 'number') return value;
|
||||
|
||||
const parsed = parseInt(value, 10);
|
||||
return isNaN(parsed) ? defaultValue : parsed;
|
||||
}
|
||||
|
||||
// Export utility
|
||||
window.parseNumericSetting = parseNumericSetting;
|
||||
@@ -1,123 +0,0 @@
|
||||
/**
|
||||
* Huntarr Settings Synchronization Utility
|
||||
*
|
||||
* This module prevents race conditions between different parts of the app
|
||||
* that might try to update settings simultaneously.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
// Track when settings are being loaded/saved to prevent conflicts
|
||||
let settingsLock = false;
|
||||
let pendingReloads = {};
|
||||
|
||||
// Function to acquire lock
|
||||
window.acquireSettingsLock = function(timeoutMs = 5000) {
|
||||
if (settingsLock) {
|
||||
console.log('Settings lock already acquired, waiting...');
|
||||
return new Promise((resolve) => {
|
||||
// Wait for the lock to be released
|
||||
const checkInterval = setInterval(() => {
|
||||
if (!settingsLock) {
|
||||
clearInterval(checkInterval);
|
||||
settingsLock = true;
|
||||
resolve(true);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
// Set a timeout to prevent infinite waiting
|
||||
setTimeout(() => {
|
||||
clearInterval(checkInterval);
|
||||
console.warn('Settings lock timeout exceeded, forcing acquisition');
|
||||
settingsLock = true;
|
||||
resolve(true);
|
||||
}, timeoutMs);
|
||||
});
|
||||
}
|
||||
|
||||
settingsLock = true;
|
||||
return Promise.resolve(true);
|
||||
};
|
||||
|
||||
// Function to release lock
|
||||
window.releaseSettingsLock = function() {
|
||||
settingsLock = false;
|
||||
|
||||
// Process any pending reloads
|
||||
Object.keys(pendingReloads).forEach(app => {
|
||||
if (pendingReloads[app]) {
|
||||
console.log(`Processing pending reload for ${app}`);
|
||||
pendingReloads[app] = false;
|
||||
|
||||
// Determine which reload function to call
|
||||
if (app === 'sonarr' && window.huntarrApp?.sonarrModule?.loadSettings) {
|
||||
window.huntarrApp.sonarrModule.loadSettings();
|
||||
} else if (app === 'radarr' && window.huntarrApp?.radarrModule?.loadSettings) {
|
||||
window.huntarrApp.radarrModule.loadSettings();
|
||||
} else if (app === 'lidarr' && window.huntarrApp?.lidarrModule?.loadSettings) {
|
||||
window.huntarrApp.lidarrModule.loadSettings();
|
||||
} else if (app === 'readarr' && window.huntarrApp?.readarrModule?.loadSettings) {
|
||||
window.huntarrApp.readarrModule.loadSettings();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Function to schedule a reload
|
||||
window.scheduleSettingsReload = function(app) {
|
||||
pendingReloads[app] = true;
|
||||
|
||||
// If lock is not active, process immediately
|
||||
if (!settingsLock) {
|
||||
window.releaseSettingsLock();
|
||||
}
|
||||
};
|
||||
|
||||
// Add to document load to ensure settings are loaded properly on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Ensure all modules hook into the lock system
|
||||
const originalFetch = window.fetch;
|
||||
window.fetch = function(url, options) {
|
||||
// If this is a settings API call, manage the lock
|
||||
if (typeof url === 'string' &&
|
||||
(url.includes('/api/settings') || url.includes('/api/app-settings'))) {
|
||||
|
||||
if (options?.method === 'POST') {
|
||||
// For POST requests, acquire lock before and release after
|
||||
return acquireSettingsLock()
|
||||
.then(() => originalFetch(url, options))
|
||||
.then(response => {
|
||||
// Don't release lock until response is processed
|
||||
return response.clone().json()
|
||||
.then(data => {
|
||||
setTimeout(() => releaseSettingsLock(), 200);
|
||||
return response;
|
||||
})
|
||||
.catch(() => {
|
||||
setTimeout(() => releaseSettingsLock(), 200);
|
||||
return response;
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
releaseSettingsLock();
|
||||
throw err;
|
||||
});
|
||||
} else {
|
||||
// For GET requests, just manage the lock
|
||||
return acquireSettingsLock()
|
||||
.then(() => originalFetch(url, options))
|
||||
.then(response => {
|
||||
setTimeout(() => releaseSettingsLock(), 200);
|
||||
return response;
|
||||
})
|
||||
.catch(err => {
|
||||
releaseSettingsLock();
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// For non-settings requests, just pass through
|
||||
return originalFetch(url, options);
|
||||
};
|
||||
});
|
||||
})();
|
||||
@@ -9,12 +9,13 @@
|
||||
<body>
|
||||
<!-- ...existing code... -->
|
||||
<!-- Scripts -->
|
||||
<script src="/static/js/settings_sync_utility.js"></script>
|
||||
<script src="/static/js/core.js"></script>
|
||||
<script src="/static/js/settings_loader.js"></script>
|
||||
<script src="/static/js/settings_forms.js"></script>
|
||||
<script src="/static/js/settings_initializer.js"></script> <!-- Add our new settings initializer script -->
|
||||
<script src="/static/js/settings_sync.js"></script>
|
||||
<!-- <script src="/static/js/settings_sync_utility.js"></script> Removed -->
|
||||
<!-- <script src="/static/js/core.js"></script> Removed -->
|
||||
<!-- <script src="/static/js/settings_loader.js"></script> Removed -->
|
||||
<script src="/static/js/settings_forms.js"></script> <!-- Keep for now -->
|
||||
<!-- <script src="/static/js/settings_initializer.js"></script> Removed -->
|
||||
<!-- <script src="/static/js/settings_sync.js"></script> Removed -->
|
||||
<script src="/static/js/new-main.js"></script> <!-- Consolidated main script -->
|
||||
<!-- App-specific scripts -->
|
||||
<script src="/static/js/apps/sonarr.js"></script>
|
||||
<script src="/static/js/apps/radarr.js"></script>
|
||||
|
||||
Reference in New Issue
Block a user