This commit is contained in:
Admin9705
2025-04-23 13:17:34 -04:00
parent 13825d6478
commit 770c5aade7
12 changed files with 425 additions and 3381 deletions
+71 -331
View File
@@ -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
+52 -330
View File
@@ -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
+52 -437
View File
@@ -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
+58 -329
View File
@@ -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
-529
View File
@@ -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;
-630
View File
@@ -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
View File
@@ -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
-143
View File
@@ -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);
}
}
});
});
}
-375
View File
@@ -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);
}
-54
View File
@@ -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;
-123
View File
@@ -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);
};
});
})();
+7 -6
View File
@@ -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>