From 963ddd936ef9b8aeee054e9a8288fe469bbea39a Mon Sep 17 00:00:00 2001
From: Admin9705 <9705@duck.com>
Date: Sun, 22 Feb 2026 13:50:48 -0500
Subject: [PATCH] feat: Introduce and ignore `.agents` directory, and add it to
UI sections that skip page refresh.
---
.dockerignore | 1 +
.gitignore | 1 +
frontend/static/js/dist/bundle-app.js | 191 +-
frontend/static/js/dist/bundle-core.js | 64 +-
frontend/static/js/dist/bundle-media.js | 76 +-
frontend/static/js/dist/bundle-settings.js | 2013 ++++++++++----------
6 files changed, 1170 insertions(+), 1176 deletions(-)
diff --git a/.dockerignore b/.dockerignore
index 1d49c3bb..7eb087ab 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -6,6 +6,7 @@
# IDE / Editor
.cursor
.kiro
+.agents
.vscode
.idea
*.swp
diff --git a/.gitignore b/.gitignore
index 0f519559..b604cfcf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -48,6 +48,7 @@ chrome-user-data
.cursor
*.mdc
.kiro/
+.agents/
*.swp
*.swo
diff --git a/frontend/static/js/dist/bundle-app.js b/frontend/static/js/dist/bundle-app.js
index 7b52526d..45c0736e 100644
--- a/frontend/static/js/dist/bundle-app.js
+++ b/frontend/static/js/dist/bundle-app.js
@@ -1774,7 +1774,7 @@ window.refreshStateManagementTimezone = function() {
Object.assign(huntarrUI, {
- switchSection: function(section) {
+ switchSection: function (section) {
console.log(`[huntarrUI] *** SWITCH SECTION CALLED *** section: ${section}, current: ${this.currentSection}`);
// Redirect legacy Movie Hunt home to Media Collection (discovery is under Requestarr)
if (section === 'movie-hunt-home') section = 'movie-hunt-collection';
@@ -1826,7 +1826,7 @@ Object.assign(huntarrUI, {
return; // User chose to stay and save changes
}
}
-
+
// Check for unsaved Settings changes if leaving Settings section
if (this.currentSection === 'settings' && window.SettingsForms && typeof window.SettingsForms.checkUnsavedChanges === 'function') {
if (!window.SettingsForms.checkUnsavedChanges()) {
@@ -1834,7 +1834,7 @@ Object.assign(huntarrUI, {
return; // User chose to stay and save changes
}
}
-
+
// Check for unsaved Notifications changes if leaving Notifications section
if (this.currentSection === 'notifications' && window.SettingsForms && typeof window.SettingsForms.checkUnsavedChanges === 'function') {
if (!window.SettingsForms.checkUnsavedChanges()) {
@@ -1842,7 +1842,7 @@ Object.assign(huntarrUI, {
return; // User chose to stay and save changes
}
}
-
+
// Check for unsaved App instance changes if leaving Apps section
const appSections = ['apps'];
if (appSections.includes(this.currentSection) && window.SettingsForms && typeof window.SettingsForms.checkUnsavedChanges === 'function') {
@@ -1851,7 +1851,7 @@ Object.assign(huntarrUI, {
return; // User chose to stay and save changes
}
}
-
+
// Check for unsaved Prowlarr changes if leaving Prowlarr section
if (this.currentSection === 'prowlarr' && window.SettingsForms && typeof window.SettingsForms.checkUnsavedChanges === 'function') {
if (!window.SettingsForms.checkUnsavedChanges()) {
@@ -1859,10 +1859,10 @@ Object.assign(huntarrUI, {
return; // User chose to stay and save changes
}
}
-
+
// Check for unsaved Profile Editor changes if leaving Profile Editor
if (this.currentSection === 'profile-editor' && section !== 'profile-editor' && window.SettingsForms && typeof window.SettingsForms.isProfileEditorDirty === 'function' && window.SettingsForms.isProfileEditorDirty()) {
- window.SettingsForms.confirmLeaveProfileEditor(function(result) {
+ window.SettingsForms.confirmLeaveProfileEditor(function (result) {
if (result === 'save') {
window.SettingsForms.saveProfileFromEditor(section);
} else if (result === 'discard') {
@@ -1874,7 +1874,7 @@ Object.assign(huntarrUI, {
// Check for unsaved Movie Management changes if leaving Movie Management
if (this.currentSection === 'settings-media-management' && section !== 'settings-media-management' && window.MovieManagement && typeof window.MovieManagement.isDirty === 'function' && window.MovieManagement.isDirty()) {
- window.MovieManagement.confirmLeave(function(result) {
+ window.MovieManagement.confirmLeave(function (result) {
if (result === 'save') {
window.MovieManagement.save(section);
} else if (result === 'discard') {
@@ -1886,7 +1886,7 @@ Object.assign(huntarrUI, {
// Check for unsaved TV Management changes if leaving TV Management
if (this.currentSection === 'tv-hunt-settings-tv-management' && section !== 'tv-hunt-settings-tv-management' && window.TVManagement && typeof window.TVManagement.isDirty === 'function' && window.TVManagement.isDirty()) {
- window.TVManagement.confirmLeave(function(result) {
+ window.TVManagement.confirmLeave(function (result) {
if (result === 'save') {
window.TVManagement.save(section);
} else if (result === 'discard') {
@@ -1902,7 +1902,7 @@ Object.assign(huntarrUI, {
if (result === 'save') {
// true means navigate back after save
window.SettingsForms._instanceEditorNextSection = section;
- window.SettingsForms.saveInstanceFromEditor(true);
+ window.SettingsForms.saveInstanceFromEditor(true);
} else if (result === 'discard') {
window.SettingsForms.cancelInstanceEditor(section);
}
@@ -1915,11 +1915,11 @@ Object.assign(huntarrUI, {
window.NzbHunt._confirmLeaveServerEditor(section);
return;
}
-
+
// Don't refresh page when navigating to/from instance editor or between app sections
const noRefreshSections = ['home', 'instance-editor', 'profile-editor', 'movie-hunt-instance-editor', 'sonarr', 'radarr', 'lidarr', 'readarr', 'whisparr', 'eros', 'prowlarr', 'swaparr', 'movie-hunt-home', 'movie-hunt-collection', 'media-hunt-collection', 'media-hunt-calendar', 'activity-queue', 'activity-history', 'activity-blocklist', 'activity-logs', 'logs-media-hunt', 'movie-hunt-settings', 'media-hunt-settings', 'media-hunt-instances', 'settings-instance-management', 'settings-media-management', 'settings-profiles', 'settings-sizes', 'settings-indexers', 'settings-clients', 'settings-import-lists', 'settings-import-media', 'settings-custom-formats', 'settings-root-folders', 'tv-hunt-collection', 'media-hunt-collection', 'tv-hunt-settings', 'media-hunt-settings', 'tv-hunt-settings-profiles', 'tv-hunt-settings-sizes', 'tv-hunt-settings-custom-formats', 'tv-hunt-settings-indexers', 'tv-hunt-settings-clients', 'tv-hunt-settings-import-lists', 'tv-hunt-settings-root-folders', 'tv-hunt-settings-tv-management', 'tv-hunt-activity-queue', 'tv-hunt-activity-history', 'tv-hunt-activity-blocklist', 'tv-hunt-instance-editor', 'logs-tv-hunt', 'system', 'hunt-manager', 'logs', 'about', 'settings', 'scheduling', 'notifications', 'backup-restore', 'settings-logs', 'user', 'nzb-hunt-home', 'nzb-hunt-activity', 'nzb-hunt-folders', 'nzb-hunt-servers', 'nzb-hunt-advanced', 'nzb-hunt-settings', 'nzb-hunt-settings-folders', 'nzb-hunt-settings-servers', 'nzb-hunt-settings-processing', 'nzb-hunt-settings-advanced', 'nzb-hunt-server-editor', 'tor-hunt-home', 'tor-hunt-settings', 'requestarr', 'requestarr-discover', 'requestarr-movies', 'requestarr-tv', 'requestarr-hidden', 'requestarr-personal-blacklist', 'requestarr-filters', 'requestarr-settings', 'requestarr-smarthunt', 'requestarr-smarthunt-settings', 'requestarr-users', 'requestarr-bundles', 'requestarr-requests', 'requestarr-global-blacklist', 'indexer-hunt', 'indexer-hunt-stats', 'indexer-hunt-history'];
const skipRefresh = noRefreshSections.includes(section) || noRefreshSections.includes(this.currentSection);
-
+
if (!skipRefresh) {
console.log(`[huntarrUI] User switching from ${this.currentSection} to ${section}, refreshing page...`);
// Store the target section in localStorage so we can navigate to it after refresh
@@ -1930,7 +1930,7 @@ Object.assign(huntarrUI, {
console.log(`[huntarrUI] Switching from ${this.currentSection} to ${section} without page refresh (app/editor navigation)`);
}
}
-
+
// Stop stats polling when leaving home section
if (window.HuntarrStats) window.HuntarrStats.stopPolling();
@@ -1959,17 +1959,17 @@ Object.assign(huntarrUI, {
s.classList.remove('active');
s.style.display = 'none';
});
-
+
// Additionally, make sure scheduling section is completely hidden
if (section !== 'scheduling' && this.elements.schedulingSection) {
this.elements.schedulingSection.style.display = 'none';
}
-
+
// Update navigation
this.elements.navItems.forEach(item => {
item.classList.remove('active');
});
-
+
// Show selected section
let newTitle = 'Home'; // Default title
const sponsorsSection = document.getElementById('sponsorsSection'); // Get sponsors section element
@@ -1981,13 +1981,13 @@ Object.assign(huntarrUI, {
if (this.elements.homeNav) this.elements.homeNav.classList.add('active');
newTitle = 'Home';
this.currentSection = 'home';
-
+
// Show main sidebar when returning to home
this.showMainSidebar();
-
+
// Disconnect logs if switching away from logs
- this.disconnectAllEventSources();
-
+ this.disconnectAllEventSources();
+
// Check app connections when returning to home page to update status
// This will call updateEmptyStateVisibility() after all checks complete
this.checkAppConnections();
@@ -2020,7 +2020,7 @@ Object.assign(huntarrUI, {
newTitle = 'Logs';
this.currentSection = section;
this.showMovieHuntSidebar();
- _checkLogsMediaHuntInstances(function(state) {
+ _checkLogsMediaHuntInstances(function (state) {
var noInst = document.getElementById('logs-media-hunt-no-instances');
var noIdx = document.getElementById('logs-media-hunt-no-indexers');
var noCli = document.getElementById('logs-media-hunt-no-clients');
@@ -2053,20 +2053,20 @@ Object.assign(huntarrUI, {
var systemSection = document.getElementById('systemSection');
systemSection.classList.add('active');
systemSection.style.display = 'block';
-
+
// Determine which tab to show
var activeTab = section === 'system' ? 'hunt-manager' : section;
if (window.HuntarrNavigation) window.HuntarrNavigation.switchSystemTab(activeTab);
-
+
// Set title based on active tab
var tabTitles = { 'hunt-manager': 'Hunt Manager', 'logs': 'Logs' };
newTitle = tabTitles[activeTab] || 'System';
this.currentSection = section === 'system' ? 'hunt-manager' : section;
-
+
// Expand System group in unified sidebar
if (typeof expandSidebarGroup === 'function') expandSidebarGroup('sidebar-group-system');
if (typeof setActiveNavItem === 'function') setActiveNavItem();
-
+
// Initialize the active tab's module
if (activeTab === 'hunt-manager') {
if (typeof huntManagerModule !== 'undefined') huntManagerModule.refresh();
@@ -2132,7 +2132,7 @@ Object.assign(huntarrUI, {
if (typeof window.NzbHunt.initSettings === 'function') window.NzbHunt.initSettings();
if (typeof window.NzbHunt._populateServerEditorForm === 'function') window.NzbHunt._populateServerEditorForm();
}
- // ── Tor Hunt sections ─────────────────────────────────────────
+ // ── Tor Hunt sections ─────────────────────────────────────────
} else if (section === 'tor-hunt-home' && document.getElementById('tor-hunt-section')) {
if (this._enableMediaHunt === false) { this.switchSection('home'); return; }
document.getElementById('tor-hunt-section').classList.add('active');
@@ -2159,7 +2159,7 @@ Object.assign(huntarrUI, {
if (window.TorHunt && typeof window.TorHunt.showView === 'function') {
window.TorHunt.showView('settings');
}
- // ── Indexer Hunt sections ──────────────────────────────────────
+ // ── Indexer Hunt sections ──────────────────────────────────────
} else if (section === 'indexer-hunt' && document.getElementById('indexer-hunt-section')) {
document.getElementById('indexer-hunt-section').classList.add('active');
document.getElementById('indexer-hunt-section').style.display = 'block';
@@ -2204,7 +2204,7 @@ Object.assign(huntarrUI, {
newTitle = 'Logs';
this.currentSection = section;
this.showTVHuntSidebar();
- _checkLogsMediaHuntInstances(function(state) {
+ _checkLogsMediaHuntInstances(function (state) {
var noInst = document.getElementById('logs-media-hunt-no-instances');
var noIdx = document.getElementById('logs-media-hunt-no-indexers');
var noCli = document.getElementById('logs-media-hunt-no-clients');
@@ -2240,7 +2240,7 @@ Object.assign(huntarrUI, {
}
document.getElementById('mediaHuntSection').classList.add('active');
document.getElementById('mediaHuntSection').style.display = 'block';
- ['mediaHuntInstanceManagementSection', 'mediaHuntInstanceEditorSection', 'tvHuntSettingsCustomFormatsSection', 'mediaHuntProfilesSection', 'tvHuntSettingsIndexersSection', 'tvHuntSettingsClientsSection', 'tvHuntSettingsRootFoldersSection', 'mediaHuntSettingsImportMediaSection', 'tvHuntSettingsTVManagementSection', 'tvManagementSection', 'tvHuntSettingsImportListsSection'].forEach(function(id) {
+ ['mediaHuntInstanceManagementSection', 'mediaHuntInstanceEditorSection', 'tvHuntSettingsCustomFormatsSection', 'mediaHuntProfilesSection', 'tvHuntSettingsIndexersSection', 'tvHuntSettingsClientsSection', 'tvHuntSettingsRootFoldersSection', 'mediaHuntSettingsImportMediaSection', 'tvHuntSettingsTVManagementSection', 'tvManagementSection', 'tvHuntSettingsImportListsSection'].forEach(function (id) {
var el = document.getElementById(id);
if (el) { el.classList.remove('active'); el.style.display = 'none'; }
});
@@ -2265,7 +2265,7 @@ Object.assign(huntarrUI, {
// ── Setup Wizard gate — show wizard if setup is incomplete ──
var _hash = window.location.hash || '';
if (window.SetupWizard && typeof window.SetupWizard.check === 'function') {
- window.SetupWizard.check(function(needsWizard) {
+ window.SetupWizard.check(function (needsWizard) {
if (needsWizard) {
window.SetupWizard.show();
} else {
@@ -2387,7 +2387,7 @@ Object.assign(huntarrUI, {
this.currentSection = 'tv-hunt-settings-clients';
this.showTVHuntSidebar();
if (window.TVHuntInstanceDropdown && window.TVHuntInstanceDropdown.attach) {
- window.TVHuntInstanceDropdown.attach('tv-hunt-settings-clients-instance-select', function() {
+ window.TVHuntInstanceDropdown.attach('tv-hunt-settings-clients-instance-select', function () {
if (window.TVHuntSettingsForms && typeof window.TVHuntSettingsForms.refreshClientsList === 'function') {
window.TVHuntSettingsForms.refreshClientsList();
}
@@ -2485,7 +2485,7 @@ Object.assign(huntarrUI, {
if (hNav) hNav.setAttribute('href', './#tv-hunt-activity-history');
if (bNav) bNav.setAttribute('href', './#tv-hunt-activity-blocklist');
// Hide all TV Hunt settings/main sections
- ['mediaHuntSection', 'mediaHuntInstanceManagementSection', 'mediaHuntInstanceEditorSection', 'tvHuntSettingsCustomFormatsSection', 'mediaHuntProfilesSection', 'tvHuntSettingsIndexersSection', 'tvHuntSettingsClientsSection', 'tvHuntSettingsRootFoldersSection', 'mediaHuntSettingsImportMediaSection', 'tvHuntSettingsTVManagementSection', 'tvManagementSection', 'tvHuntSettingsImportListsSection'].forEach(function(id) {
+ ['mediaHuntSection', 'mediaHuntInstanceManagementSection', 'mediaHuntInstanceEditorSection', 'tvHuntSettingsCustomFormatsSection', 'mediaHuntProfilesSection', 'tvHuntSettingsIndexersSection', 'tvHuntSettingsClientsSection', 'tvHuntSettingsRootFoldersSection', 'mediaHuntSettingsImportMediaSection', 'tvHuntSettingsTVManagementSection', 'tvManagementSection', 'tvHuntSettingsImportListsSection'].forEach(function (id) {
var el = document.getElementById(id);
if (el) { el.classList.remove('active'); el.style.display = 'none'; }
});
@@ -2561,7 +2561,7 @@ Object.assign(huntarrUI, {
if (document.getElementById('tvHuntSettingsClientsSection')) { document.getElementById('tvHuntSettingsClientsSection').classList.remove('active'); document.getElementById('tvHuntSettingsClientsSection').style.display = 'none'; }
if (document.getElementById('tvHuntSettingsRootFoldersSection')) { document.getElementById('tvHuntSettingsRootFoldersSection').classList.remove('active'); document.getElementById('tvHuntSettingsRootFoldersSection').style.display = 'none'; }
if (document.getElementById('tvHuntSettingsTVManagementSection')) { document.getElementById('tvHuntSettingsTVManagementSection').classList.remove('active'); document.getElementById('tvHuntSettingsTVManagementSection').style.display = 'none'; }
- if (document.getElementById('tvManagementSection')) { document.getElementById('tvManagementSection').classList.remove('active'); document.getElementById('tvManagementSection').style.display = 'none'; }
+ if (document.getElementById('tvManagementSection')) { document.getElementById('tvManagementSection').classList.remove('active'); document.getElementById('tvManagementSection').style.display = 'none'; }
if (document.getElementById('tvHuntSettingsImportListsSection')) { document.getElementById('tvHuntSettingsImportListsSection').classList.remove('active'); document.getElementById('tvHuntSettingsImportListsSection').style.display = 'none'; }
if (document.getElementById('mediaHuntSection')) { document.getElementById('mediaHuntSection').classList.remove('active'); document.getElementById('mediaHuntSection').style.display = 'none'; }
if (document.getElementById('mediaHuntCalendarSection')) { document.getElementById('mediaHuntCalendarSection').classList.remove('active'); document.getElementById('mediaHuntCalendarSection').style.display = 'none'; }
@@ -2589,10 +2589,10 @@ Object.assign(huntarrUI, {
if (document.getElementById('requestarrNav')) document.getElementById('requestarrNav').classList.add('active');
newTitle = 'Discover';
this.currentSection = 'requestarr';
-
+
// Switch to Requestarr sidebar
this.showRequestarrSidebar();
-
+
// Show discover view by default
this.runWhenRequestarrReady('discover', () => {
if (window.RequestarrDiscover && typeof window.RequestarrDiscover.switchView === 'function') {
@@ -2605,10 +2605,10 @@ Object.assign(huntarrUI, {
if (document.getElementById('requestarrDiscoverNav')) document.getElementById('requestarrDiscoverNav').classList.add('active');
newTitle = 'Discover';
this.currentSection = 'requestarr-discover';
-
+
// Switch to Requestarr sidebar
this.showRequestarrSidebar();
-
+
// Show discover view
this.runWhenRequestarrReady('discover', () => {
if (window.RequestarrDiscover && typeof window.RequestarrDiscover.switchView === 'function') {
@@ -2618,17 +2618,17 @@ Object.assign(huntarrUI, {
} else if (section === 'requestarr-movies' && document.getElementById('requestarr-section')) {
document.getElementById('requestarr-section').classList.add('active');
document.getElementById('requestarr-section').style.display = 'block';
-
+
// Check if we came from Movie Search in Movie Hunt sidebar
var fromMovieSearch = false;
- try { fromMovieSearch = sessionStorage.getItem('requestarr-from-movie-search'); sessionStorage.removeItem('requestarr-from-movie-search'); } catch (err) {}
-
+ try { fromMovieSearch = sessionStorage.getItem('requestarr-from-movie-search'); sessionStorage.removeItem('requestarr-from-movie-search'); } catch (err) { }
+
if (fromMovieSearch) {
// Keep Movie Hunt sidebar group active with Movie Search highlighted
this.showMovieHuntSidebar();
// Clear all Media Hunt nav items first
var movieHuntNavItems = document.querySelectorAll('#sidebar-group-media-hunt .nav-item');
- if (movieHuntNavItems.length) movieHuntNavItems.forEach(function(el) { el.classList.remove('active'); });
+ if (movieHuntNavItems.length) movieHuntNavItems.forEach(function (el) { el.classList.remove('active'); });
// Then highlight only Movie Search
if (document.getElementById('movieHuntMovieSearchNav')) document.getElementById('movieHuntMovieSearchNav').classList.add('active');
} else {
@@ -2636,10 +2636,10 @@ Object.assign(huntarrUI, {
if (document.getElementById('requestarrMoviesNav')) document.getElementById('requestarrMoviesNav').classList.add('active');
this.showRequestarrSidebar();
}
-
+
newTitle = 'Movies';
this.currentSection = 'requestarr-movies';
-
+
// Force movies view layout immediately
const viewIds = [
'requestarr-discover-view',
@@ -2678,10 +2678,10 @@ Object.assign(huntarrUI, {
if (document.getElementById('requestarrTVNav')) document.getElementById('requestarrTVNav').classList.add('active');
newTitle = 'TV Shows';
this.currentSection = 'requestarr-tv';
-
+
// Switch to Requestarr sidebar
this.showRequestarrSidebar();
-
+
// Show TV view
this.runWhenRequestarrReady('tv', () => {
if (window.RequestarrDiscover && typeof window.RequestarrDiscover.switchView === 'function') {
@@ -2694,10 +2694,10 @@ Object.assign(huntarrUI, {
if (document.getElementById('requestarrPersonalBlacklistNav')) document.getElementById('requestarrPersonalBlacklistNav').classList.add('active');
newTitle = 'Personal Blacklist';
this.currentSection = 'requestarr-personal-blacklist';
-
+
// Switch to Requestarr sidebar
this.showRequestarrSidebar();
-
+
// Show hidden view
this.runWhenRequestarrReady('hidden', () => {
if (window.RequestarrDiscover && typeof window.RequestarrDiscover.switchView === 'function') {
@@ -2710,10 +2710,10 @@ Object.assign(huntarrUI, {
if (document.getElementById('requestarrSettingsNav')) document.getElementById('requestarrSettingsNav').classList.add('active');
newTitle = 'Filters';
this.currentSection = 'requestarr-filters';
-
+
// Switch to Requestarr sidebar
this.showRequestarrSidebar();
-
+
// Show settings view
this.runWhenRequestarrReady('settings', () => {
if (window.RequestarrDiscover && typeof window.RequestarrDiscover.switchView === 'function') {
@@ -2726,10 +2726,10 @@ Object.assign(huntarrUI, {
if (document.getElementById('requestarrSmartHuntNav')) document.getElementById('requestarrSmartHuntNav').classList.add('active');
newTitle = 'Smart Hunt';
this.currentSection = 'requestarr-smarthunt';
-
+
// Switch to Requestarr sidebar
this.showRequestarrSidebar();
-
+
// Show Smart Hunt view
this.runWhenRequestarrReady('smarthunt', () => {
if (window.RequestarrDiscover && typeof window.RequestarrDiscover.switchView === 'function') {
@@ -2742,10 +2742,10 @@ Object.assign(huntarrUI, {
if (document.getElementById('requestarrSmartHuntSettingsNav')) document.getElementById('requestarrSmartHuntSettingsNav').classList.add('active');
newTitle = 'Smart Hunt';
this.currentSection = 'requestarr-smarthunt-settings';
-
+
// Switch to Requestarr sidebar
this.showRequestarrSidebar();
-
+
// Show Smart Hunt settings view
this.runWhenRequestarrReady('smarthunt-settings', () => {
if (window.RequestarrDiscover && typeof window.RequestarrDiscover.switchView === 'function') {
@@ -2758,10 +2758,10 @@ Object.assign(huntarrUI, {
if (document.getElementById('requestarrUsersNav')) document.getElementById('requestarrUsersNav').classList.add('active');
newTitle = 'Users';
this.currentSection = 'requestarr-users';
-
+
// Switch to Requestarr sidebar
this.showRequestarrSidebar();
-
+
// Show users view
this.runWhenRequestarrReady('users', () => {
if (window.RequestarrDiscover && typeof window.RequestarrDiscover.switchView === 'function') {
@@ -2774,10 +2774,10 @@ Object.assign(huntarrUI, {
if (document.getElementById('requestarrBundlesNav')) document.getElementById('requestarrBundlesNav').classList.add('active');
newTitle = 'Bundles';
this.currentSection = 'requestarr-bundles';
-
+
// Switch to Requestarr sidebar
this.showRequestarrSidebar();
-
+
// Show bundles view
this.runWhenRequestarrReady('bundles', () => {
if (window.RequestarrDiscover && typeof window.RequestarrDiscover.switchView === 'function') {
@@ -2790,10 +2790,10 @@ Object.assign(huntarrUI, {
if (document.getElementById('requestarrRequestsNav')) document.getElementById('requestarrRequestsNav').classList.add('active');
newTitle = 'Requests';
this.currentSection = 'requestarr-requests';
-
+
// Switch to Requestarr sidebar
this.showRequestarrSidebar();
-
+
// Show requests view
this.runWhenRequestarrReady('requests', () => {
if (window.RequestarrDiscover && typeof window.RequestarrDiscover.switchView === 'function') {
@@ -2806,10 +2806,10 @@ Object.assign(huntarrUI, {
if (document.getElementById('requestarrGlobalBlacklistNav')) document.getElementById('requestarrGlobalBlacklistNav').classList.add('active');
newTitle = 'Global Blacklist';
this.currentSection = 'requestarr-global-blacklist';
-
+
// Switch to Requestarr sidebar
this.showRequestarrSidebar();
-
+
// Show global blacklist view
this.runWhenRequestarrReady('global-blacklist', () => {
if (window.RequestarrDiscover && typeof window.RequestarrDiscover.switchView === 'function') {
@@ -2828,10 +2828,10 @@ Object.assign(huntarrUI, {
if (document.getElementById('appsSonarrNav')) document.getElementById('appsSonarrNav').classList.add('active');
newTitle = 'Sonarr';
this.currentSection = 'sonarr';
-
+
// Switch to Apps sidebar
this.showAppsSidebar();
-
+
// Initialize app module for sonarr
if (typeof appsModule !== 'undefined') {
appsModule.init('sonarr');
@@ -2842,10 +2842,10 @@ Object.assign(huntarrUI, {
if (document.getElementById('appsRadarrNav')) document.getElementById('appsRadarrNav').classList.add('active');
newTitle = 'Radarr';
this.currentSection = 'radarr';
-
+
// Switch to Apps sidebar
this.showAppsSidebar();
-
+
// Initialize app module for radarr
if (typeof appsModule !== 'undefined') {
appsModule.init('radarr');
@@ -2856,10 +2856,10 @@ Object.assign(huntarrUI, {
if (document.getElementById('appsLidarrNav')) document.getElementById('appsLidarrNav').classList.add('active');
newTitle = 'Lidarr';
this.currentSection = 'lidarr';
-
+
// Switch to Apps sidebar
this.showAppsSidebar();
-
+
// Initialize app module for lidarr
if (typeof appsModule !== 'undefined') {
appsModule.init('lidarr');
@@ -2870,10 +2870,10 @@ Object.assign(huntarrUI, {
if (document.getElementById('appsReadarrNav')) document.getElementById('appsReadarrNav').classList.add('active');
newTitle = 'Readarr';
this.currentSection = 'readarr';
-
+
// Switch to Apps sidebar
this.showAppsSidebar();
-
+
// Initialize app module for readarr
if (typeof appsModule !== 'undefined') {
appsModule.init('readarr');
@@ -2884,10 +2884,10 @@ Object.assign(huntarrUI, {
if (document.getElementById('appsWhisparrNav')) document.getElementById('appsWhisparrNav').classList.add('active');
newTitle = 'Whisparr V2';
this.currentSection = 'whisparr';
-
+
// Switch to Apps sidebar
this.showAppsSidebar();
-
+
// Initialize app module for whisparr
if (typeof appsModule !== 'undefined') {
appsModule.init('whisparr');
@@ -2898,10 +2898,10 @@ Object.assign(huntarrUI, {
if (document.getElementById('appsErosNav')) document.getElementById('appsErosNav').classList.add('active');
newTitle = 'Whisparr V3';
this.currentSection = 'eros';
-
+
// Switch to Apps sidebar
this.showAppsSidebar();
-
+
// Initialize app module for eros
if (typeof appsModule !== 'undefined') {
appsModule.init('eros');
@@ -2912,10 +2912,10 @@ Object.assign(huntarrUI, {
if (document.getElementById('appsSwaparrNav')) document.getElementById('appsSwaparrNav').classList.add('active');
newTitle = 'Swaparr';
this.currentSection = 'swaparr';
-
+
// Show Apps sidebar (Swaparr lives under Apps)
this.showAppsSidebar();
-
+
// Initialize Swaparr section
this.initializeSwaparr();
} else if (section === 'settings' && document.getElementById('settingsSection')) {
@@ -3181,28 +3181,13 @@ Object.assign(huntarrUI, {
}
this.switchSection('indexer-hunt');
return;
- } else if (section === 'settings-clients' && document.getElementById('settingsClientsSection')) {
- document.getElementById('settingsClientsSection').classList.add('active');
- document.getElementById('settingsClientsSection').style.display = 'block';
- if (document.getElementById('movieHuntClientsMainNav')) document.getElementById('movieHuntClientsMainNav').classList.add('active');
- if (document.getElementById('mediaHuntInstanceManagementSection')) {
- document.getElementById('mediaHuntInstanceManagementSection').classList.remove('active');
- document.getElementById('mediaHuntInstanceManagementSection').style.display = 'none';
- }
- if (document.getElementById('movieManagementSection')) {
- document.getElementById('movieManagementSection').classList.remove('active');
- document.getElementById('movieManagementSection').style.display = 'none';
- }
- if (document.getElementById('settingsSizesSection')) {
- document.getElementById('settingsSizesSection').classList.remove('active');
- document.getElementById('settingsSizesSection').style.display = 'none';
- }
- newTitle = 'Clients';
- this.currentSection = 'settings-clients';
- this.showMovieHuntSidebar();
- if (window.SettingsForms && typeof window.SettingsForms.refreshClientsList === 'function') {
- window.SettingsForms.refreshClientsList();
+ } else if (section === 'settings-clients') {
+ /* Clients page removed; redirect to Root Folders which now includes Remote Path Mappings */
+ if (window.location.hash !== '#settings-root-folders') {
+ window.history.replaceState(null, document.title, window.location.pathname + (window.location.search || '') + '#settings-root-folders');
}
+ this.switchSection('settings-root-folders');
+ return;
} else if (section === 'settings-import-lists' && document.getElementById('settingsImportListsSection')) {
if (document.getElementById('tvHuntSettingsImportListsSection')) { document.getElementById('tvHuntSettingsImportListsSection').classList.remove('active'); document.getElementById('tvHuntSettingsImportListsSection').style.display = 'none'; }
document.getElementById('settingsImportListsSection').classList.add('active');
@@ -3259,6 +3244,10 @@ Object.assign(huntarrUI, {
if (window.RootFolders && typeof window.RootFolders.initOrRefresh === 'function') {
window.RootFolders.initOrRefresh('movie');
}
+ // Also refresh remote path mappings (now part of root folders page)
+ if (window.RemoteMappings && typeof window.RemoteMappings.refreshList === 'function') {
+ window.RemoteMappings.refreshList();
+ }
} else if (section === 'settings-logs' && document.getElementById('settingsLogsSection')) {
document.getElementById('settingsLogsSection').classList.add('active');
document.getElementById('settingsLogsSection').style.display = 'block';
@@ -3295,10 +3284,10 @@ Object.assign(huntarrUI, {
if (document.getElementById('appsProwlarrNav')) document.getElementById('appsProwlarrNav').classList.add('active');
newTitle = 'Prowlarr';
this.currentSection = 'prowlarr';
-
+
// Switch to Apps sidebar for prowlarr
this.showAppsSidebar();
-
+
// Initialize prowlarr settings if not already done
this.initializeProwlarr();
} else if (section === 'user' && document.getElementById('userSection')) {
@@ -3320,8 +3309,8 @@ Object.assign(huntarrUI, {
this.showMovieHuntSidebar();
this._highlightMovieHuntNavForEditor('indexer');
} else if (window.SettingsForms && window.SettingsForms._currentEditing && window.SettingsForms._currentEditing.appType === 'client') {
- var ct = (window.SettingsForms._currentEditing.originalInstance && window.SettingsForms._currentEditing.originalInstance.type) ? String(window.SettingsForms._currentEditing.originalInstance.type).toLowerCase() : 'nzbget';
- newTitle = (ct === 'nzbhunt' ? 'NZB Hunt (Built-in)' : ct === 'sabnzbd' ? 'SABnzbd' : ct === 'nzbget' ? 'NZBGet' : ct) + ' Connection Settings';
+ var ct = (window.SettingsForms._currentEditing.originalInstance && window.SettingsForms._currentEditing.originalInstance.type) ? String(window.SettingsForms._currentEditing.originalInstance.type).toLowerCase() : 'nzbhunt';
+ newTitle = (ct === 'nzbhunt' ? 'NZB Hunt (Built-in)' : ct === 'torhunt' ? 'Tor Hunt (Built-in)' : ct === 'qbittorrent' ? 'qBittorrent' : ct) + ' Connection Settings';
this.showMovieHuntSidebar();
this._highlightMovieHuntNavForEditor('client');
} else {
@@ -3342,7 +3331,7 @@ Object.assign(huntarrUI, {
if (this.elements.homeNav) this.elements.homeNav.classList.add('active');
newTitle = 'Home';
this.currentSection = 'home';
-
+
// Show main sidebar
this.showMainSidebar();
}
diff --git a/frontend/static/js/dist/bundle-core.js b/frontend/static/js/dist/bundle-core.js
index f36a6fab..0787c266 100644
--- a/frontend/static/js/dist/bundle-core.js
+++ b/frontend/static/js/dist/bundle-core.js
@@ -578,13 +578,13 @@ window.HuntarrNotifications = {
window.HuntarrNavigation = {
// Handle navigation clicks
- handleNavigation: function(e) {
+ handleNavigation: function (e) {
e.preventDefault();
-
+
const target = e.currentTarget;
const href = target.getAttribute('href');
const isInternalLink = href && href.startsWith('#');
-
+
// Check for unsaved changes before navigating
if (window.huntarrUI && typeof window.huntarrUI.suppressUnsavedChangesCheck === 'boolean') {
if (window.huntarrUI.suppressUnsavedChangesCheck) {
@@ -592,7 +592,7 @@ window.HuntarrNavigation = {
window.huntarrUI.suppressUnsavedChangesCheck = false;
}
}
-
+
// Add special handling for apps section - clear global app module flags
if (window.huntarrUI && window.huntarrUI.currentSection === 'apps' && href && !href.includes('apps')) {
// Reset the app module flags when navigating away
@@ -616,8 +616,8 @@ window.HuntarrNavigation = {
window.location.href = href;
}
},
-
- handleHashNavigation: function(hash) {
+
+ handleHashNavigation: function (hash) {
let section = (hash || '').replace(/^#+/, '').trim();
if (section.indexOf('%23') >= 0) section = section.split('%23').pop() || section;
if (section.indexOf('./') === 0) section = section.replace(/^\.?\/*/, '');
@@ -746,7 +746,7 @@ window.HuntarrNavigation = {
'tv-hunt-settings-custom-formats': 'settings-custom-formats',
'tv-hunt-settings-profiles': 'settings-profiles',
'tv-hunt-settings-indexers': 'indexer-hunt',
- 'tv-hunt-settings-clients': 'settings-clients',
+ 'tv-hunt-settings-clients': 'settings-root-folders',
'tv-hunt-settings-root-folders': 'settings-root-folders',
'settings-import-media-tv': 'settings-import-media',
'tv-hunt-settings-sizes': 'settings-sizes',
@@ -782,31 +782,31 @@ window.HuntarrNavigation = {
// switchSection is handled by huntarrUI.switchSection() in app.js.
// This module only provides handleHashNavigation() which delegates to it.
-
+
// System tab management
- switchSystemTab: function(tab) {
+ switchSystemTab: function (tab) {
// Update tab buttons
- document.querySelectorAll('#systemSection .system-tab').forEach(function(t) {
+ document.querySelectorAll('#systemSection .system-tab').forEach(function (t) {
t.classList.toggle('active', t.getAttribute('data-system-tab') === tab);
});
// Update tab panels
- document.querySelectorAll('#systemSection .system-tab-panel').forEach(function(p) {
+ document.querySelectorAll('#systemSection .system-tab-panel').forEach(function (p) {
var isActive = p.getAttribute('data-system-panel') === tab;
p.style.display = isActive ? 'block' : 'none';
p.classList.toggle('active', isActive);
});
// Toggle page header bars
- document.querySelectorAll('#systemSection .system-page-header').forEach(function(h) {
+ document.querySelectorAll('#systemSection .system-page-header').forEach(function (h) {
h.style.display = 'none';
});
var hdr = document.getElementById('system-header-' + tab);
if (hdr) hdr.style.display = 'block';
},
- setupSystemTabs: function() {
+ setupSystemTabs: function () {
var self = this;
- document.querySelectorAll('#systemSection .system-tab').forEach(function(tab) {
- tab.addEventListener('click', function() {
+ document.querySelectorAll('#systemSection .system-tab').forEach(function (tab) {
+ tab.addEventListener('click', function () {
var t = tab.getAttribute('data-system-tab');
if (t) {
// Update the hash to reflect the tab
@@ -822,7 +822,7 @@ window.HuntarrNavigation = {
// Each function now expands the relevant accordion group instead
// of toggling display on separate sidebar divs.
- showMainSidebar: function() {
+ showMainSidebar: function () {
// Home page — collapse all groups
if (typeof expandSidebarGroup === 'function') {
// Let setActiveNavItem handle it via hashchange
@@ -830,77 +830,77 @@ window.HuntarrNavigation = {
if (typeof setActiveNavItem === 'function') setActiveNavItem();
},
- showAppsSidebar: function() {
+ showAppsSidebar: function () {
if (typeof expandSidebarGroup === 'function') expandSidebarGroup('sidebar-group-apps');
if (typeof setActiveNavItem === 'function') setActiveNavItem();
},
- showSettingsSidebar: function() {
+ showSettingsSidebar: function () {
if (typeof expandSidebarGroup === 'function') expandSidebarGroup('sidebar-group-settings');
if (typeof setActiveNavItem === 'function') setActiveNavItem();
},
- showRequestarrSidebar: function() {
+ showRequestarrSidebar: function () {
if (typeof expandSidebarGroup === 'function') expandSidebarGroup('sidebar-group-requests');
if (typeof setActiveNavItem === 'function') setActiveNavItem();
},
- showMovieHuntSidebar: function() {
+ showMovieHuntSidebar: function () {
if (typeof expandSidebarGroup === 'function') expandSidebarGroup('sidebar-group-media-hunt');
this.updateMovieHuntSidebarActive();
},
- showTVHuntSidebar: function() {
+ showTVHuntSidebar: function () {
this.showMovieHuntSidebar();
},
- updateMovieHuntSidebarActive: function() {
+ updateMovieHuntSidebarActive: function () {
// Sub-group expansion is handled exclusively by setActiveNavItem() in sidebar.html.
// This function only manages the activity-view CSS class (used by CSS to hide items)
// and delegates active-item highlighting to setActiveNavItem().
if (typeof setActiveNavItem === 'function') setActiveNavItem();
},
- updateTVHuntSidebarActive: function() {
+ updateTVHuntSidebarActive: function () {
// TV Hunt sidebar removed; no-op
},
- updateAppsSidebarActive: function() {
+ updateAppsSidebarActive: function () {
// Active state is handled by setActiveNavItem() in the inline script
if (typeof setActiveNavItem === 'function') setActiveNavItem();
},
- updateSettingsSidebarActive: function() {
+ updateSettingsSidebarActive: function () {
if (typeof setActiveNavItem === 'function') setActiveNavItem();
},
- updateRequestarrSidebarActive: function() {
+ updateRequestarrSidebarActive: function () {
if (typeof setActiveNavItem === 'function') setActiveNavItem();
},
- setupAppsNavigation: function() {
+ setupAppsNavigation: function () {
// Navigation is handled by hash links — no extra click listeners needed with unified sidebar
},
- setupSettingsNavigation: function() {
+ setupSettingsNavigation: function () {
// Navigation is handled by hash links
},
// setupRequestarrNavigation: handled by HuntarrRequestarr.setupRequestarrNavigation() in requestarr-controller.js
- setupMovieHuntNavigation: function() {
+ setupMovieHuntNavigation: function () {
// Navigation is handled by hash links
},
- setupTVHuntNavigation: function() {
+ setupTVHuntNavigation: function () {
// TV Hunt sidebar removed; no-op
},
- setupNzbHuntNavigation: function() {
+ setupNzbHuntNavigation: function () {
// Navigation is handled by hash links
},
- updateRequestarrNavigation: function(view) {
+ updateRequestarrNavigation: function (view) {
if (!window.RequestarrDiscover || !window.RequestarrDiscover.switchView) {
console.warn('[Navigation] RequestarrDiscover not available');
return;
diff --git a/frontend/static/js/dist/bundle-media.js b/frontend/static/js/dist/bundle-media.js
index fc31b334..eebf7e89 100644
--- a/frontend/static/js/dist/bundle-media.js
+++ b/frontend/static/js/dist/bundle-media.js
@@ -696,7 +696,7 @@
* Media Hunt – unified discover for Movie Hunt and TV Hunt.
* Mode from window._mediaHuntSectionMode ('movie' | 'tv'). Uses #media-hunt-* elements.
*/
-(function() {
+(function () {
'use strict';
const SEARCH_DEBOUNCE_MS = 500;
@@ -730,8 +730,8 @@
if (betaEl) {
betaEl.innerHTML = mode === 'movie'
- ? 'Beta feature: Movie Hunt is in active development. Things may be broken and will change quickly. There is little to no support until it is officially released. Only USENET (SABnzbd, NZBGet) is supported for now. Wiki & Docker setup '
- : 'Beta feature: TV Hunt is in active development. Things may be broken and will change quickly. Only USENET (SABnzbd, NZBGet) is supported for now.';
+ ? 'Beta feature: Movie Hunt is in active development. Things may be broken and will change quickly. There is little to no support until it is officially released. Wiki & Docker setup '
+ : 'Beta feature: TV Hunt is in active development. Things may be broken and will change quickly.';
}
if (searchInput) searchInput.placeholder = mode === 'movie' ? 'Search Movies' : 'Search TV Shows';
if (loadingText) loadingText.textContent = mode === 'movie' ? 'Loading movies...' : 'Loading TV shows...';
@@ -756,7 +756,7 @@
{ v: 'name.asc', l: 'Title (A-Z)' },
{ v: 'name.desc', l: 'Title (Z-A)' }
];
- opts.forEach(function(o) {
+ opts.forEach(function (o) {
const opt = document.createElement('option');
opt.value = o.v;
opt.textContent = o.l;
@@ -828,7 +828,7 @@
const self = this;
const input = document.getElementById('media-hunt-search-input');
if (!input) return;
- input.addEventListener('input', function() {
+ input.addEventListener('input', function () {
if (self.searchTimeout) clearTimeout(self.searchTimeout);
const query = (input.value || '').trim();
if (!query) {
@@ -837,7 +837,7 @@
else self.loadDiscover();
return;
}
- self.searchTimeout = setTimeout(function() { self.performSearch(query); }, SEARCH_DEBOUNCE_MS);
+ self.searchTimeout = setTimeout(function () { self.performSearch(query); }, SEARCH_DEBOUNCE_MS);
});
},
@@ -854,32 +854,32 @@
if (mode === 'movie') {
fetch('./api/requestarr/search?q=' + encodeURIComponent(query) + '&app_type=radarr&instance_name=search')
- .then(function(r) { return r.json(); })
- .then(function(data) {
+ .then(function (r) { return r.json(); })
+ .then(function (data) {
const results = data.results || [];
grid.innerHTML = '';
if (results.length > 0) {
- results.forEach(function(item) { grid.appendChild(window.MediaHunt.createCard(item)); });
+ results.forEach(function (item) { grid.appendChild(window.MediaHunt.createCard(item)); });
} else {
grid.innerHTML = '
No movies found
';
}
})
- .catch(function() {
+ .catch(function () {
grid.innerHTML = 'Search failed
';
});
} else {
fetch('./api/tv-hunt/search?q=' + encodeURIComponent(query))
- .then(function(r) { return r.json(); })
- .then(function(data) {
+ .then(function (r) { return r.json(); })
+ .then(function (data) {
const results = data.results || [];
grid.innerHTML = '';
if (results.length > 0) {
- results.forEach(function(show) { grid.appendChild(window.MediaHunt.createShowCard(show)); });
+ results.forEach(function (show) { grid.appendChild(window.MediaHunt.createShowCard(show)); });
} else {
grid.innerHTML = 'No results found.
';
}
})
- .catch(function() {
+ .catch(function () {
grid.innerHTML = 'Search failed
';
});
}
@@ -896,7 +896,7 @@
const self = this;
const sortSelect = document.getElementById('media-hunt-sort');
if (!sortSelect) return;
- sortSelect.addEventListener('change', function() {
+ sortSelect.addEventListener('change', function () {
self._currentSort = sortSelect.value;
self.page = 1;
self.hasMore = true;
@@ -915,7 +915,7 @@
setupFilterButton() {
const btn = document.getElementById('media-hunt-filter-btn');
if (btn) {
- btn.addEventListener('click', function() {
+ btn.addEventListener('click', function () {
if (window.MediaHuntFilters && window.MediaHuntFilters.openFiltersModal) {
window.MediaHuntFilters.openFiltersModal();
}
@@ -943,8 +943,8 @@
else url += '&sort_by=' + encodeURIComponent(this.getSortParam());
fetch(url)
- .then(function(r) { return r.json(); })
- .then(function(data) {
+ .then(function (r) { return r.json(); })
+ .then(function (data) {
if (token !== window.MediaHunt.requestToken) return;
if (page === 1) grid.innerHTML = '';
else {
@@ -953,17 +953,17 @@
}
const results = data.results || [];
if (results.length > 0) {
- results.forEach(function(item) { grid.appendChild(window.MediaHunt.createCard(item)); });
+ results.forEach(function (item) { grid.appendChild(window.MediaHunt.createCard(item)); });
} else if (page === 1) {
grid.innerHTML = 'No movies found
';
}
window.MediaHunt.hasMore = data.has_more !== false && results.length >= 20;
})
- .catch(function() {
+ .catch(function () {
if (page === 1) grid.innerHTML = 'Failed to load movies
';
window.MediaHunt.hasMore = false;
})
- .finally(function() {
+ .finally(function () {
window.MediaHunt.loading = false;
window.MediaHunt.page = page;
const sentinel = document.getElementById('media-hunt-scroll-sentinel');
@@ -987,15 +987,15 @@
const self = this;
const sortParam = (document.getElementById('media-hunt-sort') && document.getElementById('media-hunt-sort').value) || 'popularity.desc';
fetch('./api/tv-hunt/discover/tv?page=' + this.page + '&sort_by=' + encodeURIComponent(sortParam))
- .then(function(r) { return r.json(); })
- .then(function(data) {
+ .then(function (r) { return r.json(); })
+ .then(function (data) {
const results = data.results || [];
if (self.page === 1) grid.innerHTML = '';
- results.forEach(function(show) { grid.appendChild(window.MediaHunt.createShowCard(show)); });
+ results.forEach(function (show) { grid.appendChild(window.MediaHunt.createShowCard(show)); });
self.hasMore = results.length >= 20;
self.loading = false;
})
- .catch(function() {
+ .catch(function () {
if (self.page === 1) grid.innerHTML = 'Failed to load TV shows.
';
self.loading = false;
});
@@ -1007,8 +1007,8 @@
const self = this;
const scrollRoot = document.querySelector('.main-content') || null;
this.observer = new IntersectionObserver(
- function(entries) {
- entries.forEach(function(entry) {
+ function (entries) {
+ entries.forEach(function (entry) {
if (!entry.isIntersecting) return;
if (getMode() === 'movie') {
if (self.hasMore && !self.loading) self.loadMovies(self.page + 1);
@@ -1064,7 +1064,7 @@
status: inLibrary ? 'available' : (partial ? 'requested' : 'requested'),
hasFile: inLibrary,
appType: 'movie_hunt',
- onDeleted: function() { window.MediaUtils.animateCardRemoval(cardElement); }
+ onDeleted: function () { window.MediaUtils.animateCardRemoval(cardElement); }
});
},
@@ -1107,7 +1107,7 @@
const requestBtn = card.querySelector('.media-card-request-btn');
const hideBtnEl = card.querySelector('.media-card-hide-btn');
const deleteBtnEl = card.querySelector('.media-card-delete-btn');
- const openRequestModal = function() {
+ const openRequestModal = function () {
const id = item.tmdb_id || item.id;
if (id && window.RequestarrDiscover && window.RequestarrDiscover.modal) {
let suggestedInstance = null;
@@ -1120,7 +1120,7 @@
window.RequestarrDiscover.modal.openModal(id, 'movie', suggestedInstance);
}
};
- const openDetailPage = function() {
+ const openDetailPage = function () {
if (window.RequestarrDetail) {
window.RequestarrDetail.openDetail(item);
} else {
@@ -1129,10 +1129,10 @@
};
// When not in library and not requested: any click opens modal. When requested or in library: click opens detail page.
const shouldOpenModal = !inLibrary && !partial;
- if (hideBtnEl) hideBtnEl.addEventListener('click', function(e) { e.preventDefault(); e.stopPropagation(); window.MediaHunt.hideMediaFromHome(item, card); });
- if (deleteBtnEl) deleteBtnEl.addEventListener('click', function(e) { e.preventDefault(); e.stopPropagation(); window.MediaHunt.openDeleteModalFromHome(item, card); });
+ if (hideBtnEl) hideBtnEl.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); window.MediaHunt.hideMediaFromHome(item, card); });
+ if (deleteBtnEl) deleteBtnEl.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); window.MediaHunt.openDeleteModalFromHome(item, card); });
card.style.cursor = 'pointer';
- card.addEventListener('click', function(e) {
+ card.addEventListener('click', function (e) {
if (hideBtnEl && (e.target === hideBtnEl || hideBtnEl.contains(e.target))) return;
if (deleteBtnEl && (e.target === deleteBtnEl || deleteBtnEl.contains(e.target))) return;
if (requestBtn && (e.target === requestBtn || requestBtn.contains(e.target))) { e.preventDefault(); e.stopPropagation(); openRequestModal(); return; }
@@ -1174,13 +1174,13 @@
instance_id: instId,
})
})
- .then(function(r) { return r.json(); })
- .then(function(data) {
+ .then(function (r) { return r.json(); })
+ .then(function (data) {
if (data.exists && window.huntarrUI) window.huntarrUI.showNotification('Series already in collection.', 'info');
else if (data.success && window.huntarrUI) window.huntarrUI.showNotification('Added to collection!', 'success');
else if (window.huntarrUI) window.huntarrUI.showNotification(data.error || 'Failed to add.', 'error');
})
- .catch(function() {
+ .catch(function () {
if (window.huntarrUI) window.huntarrUI.showNotification('Network error adding to collection.', 'error');
});
},
@@ -1201,12 +1201,12 @@
const addBtn = card.querySelector('.add-to-collection-btn');
if (addBtn) {
- addBtn.addEventListener('click', function(e) {
+ addBtn.addEventListener('click', function (e) {
e.stopPropagation();
if (window.MediaHunt && window.MediaHunt.addToCollection) window.MediaHunt.addToCollection(show);
});
}
- card.addEventListener('click', function() {
+ card.addEventListener('click', function () {
if (window.RequestarrTVDetail) {
window.RequestarrTVDetail.openDetail({ tmdb_id: show.id, id: show.id, title: show.name || show.title, poster_path: show.poster_path });
}
diff --git a/frontend/static/js/dist/bundle-settings.js b/frontend/static/js/dist/bundle-settings.js
index e2e62242..35e3064a 100644
--- a/frontend/static/js/dist/bundle-settings.js
+++ b/frontend/static/js/dist/bundle-settings.js
@@ -982,60 +982,60 @@ document.head.appendChild(styleEl);
* Instance editor (Sonarr/Radarr/Lidarr/Readarr/Whisparr/Eros) - extends SettingsForms.
* Loaded after settings/core.js.
*/
-(function() {
+(function () {
'use strict';
if (typeof window.SettingsForms === 'undefined') return;
let _instanceEditorDirty = false;
Object.assign(window.SettingsForms, {
- getAppIcon: function(appType) {
- const icons = {
- sonarr: 'fa-tv',
- radarr: 'fa-film',
- lidarr: 'fa-music',
- readarr: 'fa-book',
- whisparr: 'fa-venus',
- eros: 'fa-venus-mars',
- prowlarr: 'fa-search'
- };
- return icons[appType] || 'fa-server';
- },
+ getAppIcon: function (appType) {
+ const icons = {
+ sonarr: 'fa-tv',
+ radarr: 'fa-film',
+ lidarr: 'fa-music',
+ readarr: 'fa-book',
+ whisparr: 'fa-venus',
+ eros: 'fa-venus-mars',
+ prowlarr: 'fa-search'
+ };
+ return icons[appType] || 'fa-server';
+ },
- // Render a single instance card. options: { hideDelete: true } for single-instance apps (e.g. Prowlarr).
- renderInstanceCard: function(appType, instance, index, options) {
- const isDefault = index === 0;
-
- // Determine connection status; disabled instances are never tested
- let statusClass = 'status-unknown';
- let statusIcon = 'fa-question-circle';
-
- if (instance.enabled === false) {
- statusClass = 'status-disabled';
- statusIcon = 'fa-ban';
- } else if (instance.api_url && instance.api_key) {
- // Has URL and API key - check if connection test passed
- if (instance.connection_status === 'connected' || instance.connection_test_passed === true) {
- statusClass = 'status-connected';
- statusIcon = 'fa-check-circle';
- } else if (instance.connection_status === 'error' || instance.connection_test_passed === false) {
+ // Render a single instance card. options: { hideDelete: true } for single-instance apps (e.g. Prowlarr).
+ renderInstanceCard: function (appType, instance, index, options) {
+ const isDefault = index === 0;
+
+ // Determine connection status; disabled instances are never tested
+ let statusClass = 'status-unknown';
+ let statusIcon = 'fa-question-circle';
+
+ if (instance.enabled === false) {
+ statusClass = 'status-disabled';
+ statusIcon = 'fa-ban';
+ } else if (instance.api_url && instance.api_key) {
+ // Has URL and API key - check if connection test passed
+ if (instance.connection_status === 'connected' || instance.connection_test_passed === true) {
+ statusClass = 'status-connected';
+ statusIcon = 'fa-check-circle';
+ } else if (instance.connection_status === 'error' || instance.connection_test_passed === false) {
+ statusClass = 'status-error';
+ statusIcon = 'fa-minus-circle';
+ } else {
+ statusClass = 'status-unknown';
+ statusIcon = 'fa-question-circle';
+ }
+ } else {
statusClass = 'status-error';
statusIcon = 'fa-minus-circle';
- } else {
- statusClass = 'status-unknown';
- statusIcon = 'fa-question-circle';
}
- } else {
- statusClass = 'status-error';
- statusIcon = 'fa-minus-circle';
- }
-
- const hideDelete = (options && options.hideDelete) === true;
- const footerButtons = hideDelete
- ? ` Edit `
- : ` Edit
+
+ const hideDelete = (options && options.hideDelete) === true;
+ const footerButtons = hideDelete
+ ? ` Edit `
+ : ` Edit
Delete `;
- return `
+ return `
`;
- },
+ },
- // Navigate to the instance editor section
- navigateToInstanceEditor: function(appType, index = null) {
- console.log(`[SettingsForms] navigateToInstanceEditor called for ${appType}, index: ${index}`);
-
- // Reset next section tracking
- this._instanceEditorNextSection = null;
+ // Navigate to the instance editor section
+ navigateToInstanceEditor: function (appType, index = null) {
+ console.log(`[SettingsForms] navigateToInstanceEditor called for ${appType}, index: ${index}`);
- if (!window.huntarrUI || !window.huntarrUI.originalSettings) {
- console.error('[SettingsForms] window.huntarrUI.originalSettings is missing');
- if (window.huntarrUI && window.huntarrUI.showNotification) window.huntarrUI.showNotification('Error: Settings not loaded. Please refresh the page.', 'error');
- else alert('Error: Settings not loaded. Please refresh the page.');
- return;
- }
+ // Reset next section tracking
+ this._instanceEditorNextSection = null;
- const settings = window.huntarrUI.originalSettings[appType];
- if (!settings) {
- console.error(`[SettingsForms] Settings for ${appType} not found in originalSettings`);
- if (window.huntarrUI && window.huntarrUI.showNotification) window.huntarrUI.showNotification('Error: Settings for ' + appType + ' not found. Please refresh the page.', 'error');
- else alert('Error: Settings for ' + appType + ' not found. Please refresh the page.');
- return;
- }
-
- const isEdit = index !== null;
- let instance;
-
- if (isEdit) {
- if (!settings.instances || !settings.instances[index]) {
- console.error(`[SettingsForms] Instance at index ${index} not found for ${appType}`);
- if (window.huntarrUI && window.huntarrUI.showNotification) window.huntarrUI.showNotification('Error: Instance not found.', 'error');
- else alert('Error: Instance not found.');
+ if (!window.huntarrUI || !window.huntarrUI.originalSettings) {
+ console.error('[SettingsForms] window.huntarrUI.originalSettings is missing');
+ if (window.huntarrUI && window.huntarrUI.showNotification) window.huntarrUI.showNotification('Error: Settings not loaded. Please refresh the page.', 'error');
+ else alert('Error: Settings not loaded. Please refresh the page.');
return;
}
- instance = settings.instances[index];
- } else {
- instance = {
- name: '',
- api_url: '',
- api_key: '',
- external_url: '',
- enabled: true,
- hunt_missing_items: 1,
- hunt_upgrade_items: 0,
- hunt_missing_mode: 'seasons_packs',
- upgrade_mode: 'seasons_packs',
- state_management_mode: 'custom',
- state_management_hours: 72,
- swaparr_enabled: false
- };
- }
- // Store current editing state
- this._currentEditing = { appType, index, originalInstance: JSON.parse(JSON.stringify(instance)) };
- _instanceEditorDirty = false;
-
- // Update breadcrumb in the header
- const bcAppName = document.getElementById('ie-breadcrumb-app-name');
- const bcInstanceName = document.getElementById('ie-breadcrumb-instance-name');
- const bcAppIcon = document.getElementById('ie-breadcrumb-app-icon');
- if (bcAppName) bcAppName.textContent = appType.charAt(0).toUpperCase() + appType.slice(1);
- if (bcInstanceName) bcInstanceName.textContent = instance.name || (isEdit ? 'Edit Instance' : 'New Instance');
- if (bcAppIcon) {
- bcAppIcon.className = 'fas ' + this.getAppIcon(appType);
- }
-
- const contentEl = document.getElementById('instance-editor-content');
- if (contentEl) {
- try {
- const html = this.generateEditorHtml(appType, instance, index);
- contentEl.innerHTML = html;
- console.log('[SettingsForms] Editor HTML injected, length:', html.length);
- this.setupExemptTagsListeners(contentEl);
- } catch (e) {
- console.error('[SettingsForms] Error generating editor HTML:', e);
- contentEl.innerHTML = `
Error generating editor: ${e.message}
`;
+ const settings = window.huntarrUI.originalSettings[appType];
+ if (!settings) {
+ console.error(`[SettingsForms] Settings for ${appType} not found in originalSettings`);
+ if (window.huntarrUI && window.huntarrUI.showNotification) window.huntarrUI.showNotification('Error: Settings for ' + appType + ' not found. Please refresh the page.', 'error');
+ else alert('Error: Settings for ' + appType + ' not found. Please refresh the page.');
+ return;
}
- } else {
- console.error('[SettingsForms] instance-editor-content element not found');
- }
- // Setup button listeners
- const saveBtn = document.getElementById('instance-editor-save');
- const backBtn = document.getElementById('instance-editor-back');
+ const isEdit = index !== null;
+ let instance;
- if (saveBtn) {
- saveBtn.onclick = () => this.saveInstanceFromEditor();
- }
- if (backBtn) {
- backBtn.onclick = () => {
- this.confirmLeaveInstanceEditor((result) => {
- if (result === 'save') {
- this.saveInstanceFromEditor(true); // true means navigate back after save
- } else if (result === 'discard') {
- this.cancelInstanceEditor();
- }
- });
- };
- }
-
- // Setup connection validation for URL and API Key inputs
- const urlInput = document.getElementById('editor-url');
- const keyInput = document.getElementById('editor-key');
-
- if (urlInput && keyInput) {
- let validationTimeout;
- const validateConnection = () => {
- clearTimeout(validationTimeout);
- validationTimeout = setTimeout(() => {
- const url = urlInput.value.trim();
- const key = keyInput.value.trim();
- this.checkEditorConnection(appType, url, key);
- }, 500); // Debounce 500ms
- };
-
- urlInput.addEventListener('input', validateConnection);
- keyInput.addEventListener('input', validateConnection);
-
- const enabledSelect = document.getElementById('editor-enabled');
- if (enabledSelect) {
- enabledSelect.addEventListener('change', validateConnection);
- }
-
- // Initial validation - checkEditorConnection shows "Disabled" or runs test
- this.checkEditorConnection(appType, urlInput.value.trim(), keyInput.value.trim());
- }
-
- // Switch to the editor section
- console.log('[SettingsForms] Switching to instance-editor section');
- if (window.huntarrUI && window.huntarrUI.switchSection) {
- window.huntarrUI.switchSection('instance-editor');
- // Update URL hash for app instance editors (radarr, sonarr, etc.)
- const appInstanceEditors = ['sonarr', 'radarr', 'lidarr', 'readarr', 'whisparr', 'eros', 'prowlarr'];
- if (appInstanceEditors.includes(appType)) {
- const hashPart = (index !== null && index !== undefined) ? appType + '-settings/' + index : appType + '-settings';
- const newUrl = (window.location.pathname || '') + (window.location.search || '') + '#' + hashPart;
- try { window.history.replaceState(null, '', newUrl); } catch (e) { /* ignore */ }
- }
- // Add change detection after a short delay to let values settle
- setTimeout(() => {
- this.setupEditorChangeDetection();
- // Initialize form field states based on enabled status
- this.toggleFormFields();
- // Sync upgrade tag group and upgrade-items-tag section visibility (tags vs cutoff mode)
- this.toggleUpgradeTagVisibility();
- // Start polling state status if state management is enabled
- if (instance.state_management_mode !== 'disabled') {
- this.startStateStatusPolling(appType, index);
+ if (isEdit) {
+ if (!settings.instances || !settings.instances[index]) {
+ console.error(`[SettingsForms] Instance at index ${index} not found for ${appType}`);
+ if (window.huntarrUI && window.huntarrUI.showNotification) window.huntarrUI.showNotification('Error: Instance not found.', 'error');
+ else alert('Error: Instance not found.');
+ return;
}
- }, 100);
- } else {
- console.error('[SettingsForms] window.huntarrUI.switchSection not available');
- }
- },
-
- // Setup exempt tags add/remove in the instance editor
- setupExemptTagsListeners: function(container) {
- if (!container) return;
- const addBtn = container.querySelector('#editor-exempt-tag-add');
- const input = container.querySelector('#editor-exempt-tag-input');
- const list = container.querySelector('#editor-exempt-tags-list');
- if (!addBtn || !input || !list) return;
- const self = this;
- addBtn.addEventListener('click', function() { self.addExemptTag(input, list); });
- input.addEventListener('keydown', function(e) {
- if (e.key === 'Enter') { e.preventDefault(); self.addExemptTag(input, list); }
- });
- list.addEventListener('click', function(e) {
- const removeEl = e.target.classList.contains('exempt-tag-remove') ? e.target : e.target.closest('.exempt-tag-remove');
- if (removeEl) {
- const chip = removeEl.closest('.exempt-tag-chip');
- if (chip) chip.remove();
- _instanceEditorDirty = true;
- const saveBtn = document.getElementById('instance-editor-save');
- if (saveBtn) { saveBtn.disabled = false; saveBtn.classList.add('enabled'); }
- }
- });
- },
- addExemptTag: function(inputEl, listEl) {
- const tag = (inputEl.value || '').trim();
- if (!tag) return;
- if (tag.toLowerCase() === 'upgradinatorr') {
- if (window.huntarrUI && window.huntarrUI.showNotification) {
- window.huntarrUI.showNotification('The tag "upgradinatorr" cannot be added as an exempt tag.', 'warning');
+ instance = settings.instances[index];
} else {
- if (window.huntarrUI && window.huntarrUI.showNotification) window.huntarrUI.showNotification('The tag "upgradinatorr" cannot be added as an exempt tag.', 'error');
- else alert('The tag "upgradinatorr" cannot be added as an exempt tag.');
+ instance = {
+ name: '',
+ api_url: '',
+ api_key: '',
+ external_url: '',
+ enabled: true,
+ hunt_missing_items: 1,
+ hunt_upgrade_items: 0,
+ hunt_missing_mode: 'seasons_packs',
+ upgrade_mode: 'seasons_packs',
+ state_management_mode: 'custom',
+ state_management_hours: 72,
+ swaparr_enabled: false
+ };
}
- return;
- }
- const existing = listEl.querySelectorAll('.exempt-tag-chip');
- for (let i = 0; i < existing.length; i++) {
- if ((existing[i].getAttribute('data-tag') || '') === tag) return;
- }
- const chip = document.createElement('span');
- chip.className = 'exempt-tag-chip';
- chip.setAttribute('data-tag', tag);
- chip.innerHTML = '
× ' + String(tag).replace(//g, '>') + ' ';
- listEl.appendChild(chip);
- inputEl.value = '';
- _instanceEditorDirty = true;
- const saveBtn = document.getElementById('instance-editor-save');
- if (saveBtn) { saveBtn.disabled = false; saveBtn.classList.add('enabled'); }
- },
- // Setup change detection for the editor
- setupEditorChangeDetection: function() {
- const contentEl = document.getElementById('instance-editor-content');
- const saveBtn = document.getElementById('instance-editor-save');
- if (!contentEl || !saveBtn) return;
+ // Store current editing state
+ this._currentEditing = { appType, index, originalInstance: JSON.parse(JSON.stringify(instance)) };
+ _instanceEditorDirty = false;
- // Initial state: disabled
- saveBtn.disabled = true;
- saveBtn.classList.remove('enabled');
+ // Update breadcrumb in the header
+ const bcAppName = document.getElementById('ie-breadcrumb-app-name');
+ const bcInstanceName = document.getElementById('ie-breadcrumb-instance-name');
+ const bcAppIcon = document.getElementById('ie-breadcrumb-app-icon');
+ if (bcAppName) bcAppName.textContent = appType.charAt(0).toUpperCase() + appType.slice(1);
+ if (bcInstanceName) bcInstanceName.textContent = instance.name || (isEdit ? 'Edit Instance' : 'New Instance');
+ if (bcAppIcon) {
+ bcAppIcon.className = 'fas ' + this.getAppIcon(appType);
+ }
- const handleInputChange = () => {
- _instanceEditorDirty = true;
- saveBtn.disabled = false;
- saveBtn.classList.add('enabled');
- };
+ const contentEl = document.getElementById('instance-editor-content');
+ if (contentEl) {
+ try {
+ const html = this.generateEditorHtml(appType, instance, index);
+ contentEl.innerHTML = html;
+ console.log('[SettingsForms] Editor HTML injected, length:', html.length);
+ this.setupExemptTagsListeners(contentEl);
+ } catch (e) {
+ console.error('[SettingsForms] Error generating editor HTML:', e);
+ contentEl.innerHTML = `
Error generating editor: ${e.message}
`;
+ }
+ } else {
+ console.error('[SettingsForms] instance-editor-content element not found');
+ }
- // Listen for any input or change event within the content area
- contentEl.addEventListener('input', handleInputChange);
- contentEl.addEventListener('change', handleInputChange);
+ // Setup button listeners
+ const saveBtn = document.getElementById('instance-editor-save');
+ const backBtn = document.getElementById('instance-editor-back');
- // Show warning when API cap hourly is above 25 (indexer ban risk)
- const capInput = document.getElementById('editor-hourly-cap');
- const capWarning = document.getElementById('editor-hourly-cap-warning');
- if (capInput && capWarning) {
- const updateHourlyCapWarning = () => {
- const val = parseInt(capInput.value, 10);
- capWarning.style.display = (val > 25) ? 'block' : 'none';
- };
- updateHourlyCapWarning();
- capInput.addEventListener('input', updateHourlyCapWarning);
- capInput.addEventListener('change', updateHourlyCapWarning);
- }
- },
+ if (saveBtn) {
+ saveBtn.onclick = () => this.saveInstanceFromEditor();
+ }
+ if (backBtn) {
+ backBtn.onclick = () => {
+ this.confirmLeaveInstanceEditor((result) => {
+ if (result === 'save') {
+ this.saveInstanceFromEditor(true); // true means navigate back after save
+ } else if (result === 'discard') {
+ this.cancelInstanceEditor();
+ }
+ });
+ };
+ }
- confirmLeaveInstanceEditor: function(done) {
- if (!_instanceEditorDirty) {
- if (typeof done === 'function') done('discard');
- return true;
- }
- if (window.HuntarrConfirm && window.HuntarrConfirm.show) {
- window.HuntarrConfirm.show({
- title: 'Unsaved Changes',
- message: 'You have unsaved changes that will be lost if you leave.',
- confirmLabel: 'Go Back',
- cancelLabel: 'Leave',
- onConfirm: function() {
- // Stay on the editor — modal just closes, user can save manually
- },
- onCancel: function() {
- if (typeof done === 'function') done('discard');
+ // Setup connection validation for URL and API Key inputs
+ const urlInput = document.getElementById('editor-url');
+ const keyInput = document.getElementById('editor-key');
+
+ if (urlInput && keyInput) {
+ let validationTimeout;
+ const validateConnection = () => {
+ clearTimeout(validationTimeout);
+ validationTimeout = setTimeout(() => {
+ const url = urlInput.value.trim();
+ const key = keyInput.value.trim();
+ this.checkEditorConnection(appType, url, key);
+ }, 500); // Debounce 500ms
+ };
+
+ urlInput.addEventListener('input', validateConnection);
+ keyInput.addEventListener('input', validateConnection);
+
+ const enabledSelect = document.getElementById('editor-enabled');
+ if (enabledSelect) {
+ enabledSelect.addEventListener('change', validateConnection);
+ }
+
+ // Initial validation - checkEditorConnection shows "Disabled" or runs test
+ this.checkEditorConnection(appType, urlInput.value.trim(), keyInput.value.trim());
+ }
+
+ // Switch to the editor section
+ console.log('[SettingsForms] Switching to instance-editor section');
+ if (window.huntarrUI && window.huntarrUI.switchSection) {
+ window.huntarrUI.switchSection('instance-editor');
+ // Update URL hash for app instance editors (radarr, sonarr, etc.)
+ const appInstanceEditors = ['sonarr', 'radarr', 'lidarr', 'readarr', 'whisparr', 'eros', 'prowlarr'];
+ if (appInstanceEditors.includes(appType)) {
+ const hashPart = (index !== null && index !== undefined) ? appType + '-settings/' + index : appType + '-settings';
+ const newUrl = (window.location.pathname || '') + (window.location.search || '') + '#' + hashPart;
+ try { window.history.replaceState(null, '', newUrl); } catch (e) { /* ignore */ }
+ }
+ // Add change detection after a short delay to let values settle
+ setTimeout(() => {
+ this.setupEditorChangeDetection();
+ // Initialize form field states based on enabled status
+ this.toggleFormFields();
+ // Sync upgrade tag group and upgrade-items-tag section visibility (tags vs cutoff mode)
+ this.toggleUpgradeTagVisibility();
+ // Start polling state status if state management is enabled
+ if (instance.state_management_mode !== 'disabled') {
+ this.startStateStatusPolling(appType, index);
+ }
+ }, 100);
+ } else {
+ console.error('[SettingsForms] window.huntarrUI.switchSection not available');
+ }
+ },
+
+ // Setup exempt tags add/remove in the instance editor
+ setupExemptTagsListeners: function (container) {
+ if (!container) return;
+ const addBtn = container.querySelector('#editor-exempt-tag-add');
+ const input = container.querySelector('#editor-exempt-tag-input');
+ const list = container.querySelector('#editor-exempt-tags-list');
+ if (!addBtn || !input || !list) return;
+ const self = this;
+ addBtn.addEventListener('click', function () { self.addExemptTag(input, list); });
+ input.addEventListener('keydown', function (e) {
+ if (e.key === 'Enter') { e.preventDefault(); self.addExemptTag(input, list); }
+ });
+ list.addEventListener('click', function (e) {
+ const removeEl = e.target.classList.contains('exempt-tag-remove') ? e.target : e.target.closest('.exempt-tag-remove');
+ if (removeEl) {
+ const chip = removeEl.closest('.exempt-tag-chip');
+ if (chip) chip.remove();
+ _instanceEditorDirty = true;
+ const saveBtn = document.getElementById('instance-editor-save');
+ if (saveBtn) { saveBtn.disabled = false; saveBtn.classList.add('enabled'); }
}
});
- } else {
- // Fallback to native confirm
- if (!confirm('You have unsaved changes that will be lost. Leave anyway?')) return;
- if (typeof done === 'function') done('discard');
- }
- return false;
- },
+ },
+ addExemptTag: function (inputEl, listEl) {
+ const tag = (inputEl.value || '').trim();
+ if (!tag) return;
+ if (tag.toLowerCase() === 'upgradinatorr') {
+ if (window.huntarrUI && window.huntarrUI.showNotification) {
+ window.huntarrUI.showNotification('The tag "upgradinatorr" cannot be added as an exempt tag.', 'warning');
+ } else {
+ if (window.huntarrUI && window.huntarrUI.showNotification) window.huntarrUI.showNotification('The tag "upgradinatorr" cannot be added as an exempt tag.', 'error');
+ else alert('The tag "upgradinatorr" cannot be added as an exempt tag.');
+ }
+ return;
+ }
+ const existing = listEl.querySelectorAll('.exempt-tag-chip');
+ for (let i = 0; i < existing.length; i++) {
+ if ((existing[i].getAttribute('data-tag') || '') === tag) return;
+ }
+ const chip = document.createElement('span');
+ chip.className = 'exempt-tag-chip';
+ chip.setAttribute('data-tag', tag);
+ chip.innerHTML = '
× ' + String(tag).replace(//g, '>') + ' ';
+ listEl.appendChild(chip);
+ inputEl.value = '';
+ _instanceEditorDirty = true;
+ const saveBtn = document.getElementById('instance-editor-save');
+ if (saveBtn) { saveBtn.disabled = false; saveBtn.classList.add('enabled'); }
+ },
- isInstanceEditorDirty: function() {
- return !!_instanceEditorDirty;
- },
+ // Setup change detection for the editor
+ setupEditorChangeDetection: function () {
+ const contentEl = document.getElementById('instance-editor-content');
+ const saveBtn = document.getElementById('instance-editor-save');
+ if (!contentEl || !saveBtn) return;
- // Public method to clear the dirty flag and disable the save button (used by Prowlarr editor etc.)
- clearInstanceEditorDirty: function() {
- _instanceEditorDirty = false;
- const saveBtn = document.getElementById('instance-editor-save');
- if (saveBtn) {
+ // Initial state: disabled
saveBtn.disabled = true;
saveBtn.classList.remove('enabled');
- }
- },
-
- // Check connection status for editor
- checkEditorConnection: function(appType, url, apiKey) {
- const container = document.getElementById('connection-status-container');
- if (!container) return;
-
- // Add flex-end to push to right
- container.style.display = 'flex';
- container.style.justifyContent = 'flex-end';
- container.style.flex = '1';
-
- // If instance is disabled, do not attempt or show connection status
- const enabledEl = document.getElementById('editor-enabled');
- if (enabledEl && enabledEl.value === 'false') {
- container.innerHTML = `
+
+ const handleInputChange = () => {
+ _instanceEditorDirty = true;
+ saveBtn.disabled = false;
+ saveBtn.classList.add('enabled');
+ };
+
+ // Listen for any input or change event within the content area
+ contentEl.addEventListener('input', handleInputChange);
+ contentEl.addEventListener('change', handleInputChange);
+
+ // Show warning when API cap hourly is above 25 (indexer ban risk)
+ const capInput = document.getElementById('editor-hourly-cap');
+ const capWarning = document.getElementById('editor-hourly-cap-warning');
+ if (capInput && capWarning) {
+ const updateHourlyCapWarning = () => {
+ const val = parseInt(capInput.value, 10);
+ capWarning.style.display = (val > 25) ? 'block' : 'none';
+ };
+ updateHourlyCapWarning();
+ capInput.addEventListener('input', updateHourlyCapWarning);
+ capInput.addEventListener('change', updateHourlyCapWarning);
+ }
+ },
+
+ confirmLeaveInstanceEditor: function (done) {
+ if (!_instanceEditorDirty) {
+ if (typeof done === 'function') done('discard');
+ return true;
+ }
+ if (window.HuntarrConfirm && window.HuntarrConfirm.show) {
+ window.HuntarrConfirm.show({
+ title: 'Unsaved Changes',
+ message: 'You have unsaved changes that will be lost if you leave.',
+ confirmLabel: 'Go Back',
+ cancelLabel: 'Leave',
+ onConfirm: function () {
+ // Stay on the editor — modal just closes, user can save manually
+ },
+ onCancel: function () {
+ if (typeof done === 'function') done('discard');
+ }
+ });
+ } else {
+ // Fallback to native confirm
+ if (!confirm('You have unsaved changes that will be lost. Leave anyway?')) return;
+ if (typeof done === 'function') done('discard');
+ }
+ return false;
+ },
+
+ isInstanceEditorDirty: function () {
+ return !!_instanceEditorDirty;
+ },
+
+ // Public method to clear the dirty flag and disable the save button (used by Prowlarr editor etc.)
+ clearInstanceEditorDirty: function () {
+ _instanceEditorDirty = false;
+ const saveBtn = document.getElementById('instance-editor-save');
+ if (saveBtn) {
+ saveBtn.disabled = true;
+ saveBtn.classList.remove('enabled');
+ }
+ },
+
+ // Check connection status for editor
+ checkEditorConnection: function (appType, url, apiKey) {
+ const container = document.getElementById('connection-status-container');
+ if (!container) return;
+
+ // Add flex-end to push to right
+ container.style.display = 'flex';
+ container.style.justifyContent = 'flex-end';
+ container.style.flex = '1';
+
+ // If instance is disabled, do not attempt or show connection status
+ const enabledEl = document.getElementById('editor-enabled');
+ if (enabledEl && enabledEl.value === 'false') {
+ container.innerHTML = `
Disabled
`;
- return;
- }
-
- // Show appropriate status for incomplete fields (like old version)
- const urlLen = url ? url.trim().length : 0;
- const keyLen = apiKey ? apiKey.trim().length : 0;
+ return;
+ }
- if (urlLen <= 10 && keyLen <= 20) {
- container.innerHTML = `
+ // Show appropriate status for incomplete fields (like old version)
+ const urlLen = url ? url.trim().length : 0;
+ const keyLen = apiKey ? apiKey.trim().length : 0;
+
+ if (urlLen <= 10 && keyLen <= 20) {
+ container.innerHTML = `
Enter URL and API Key
`;
- return;
- } else if (urlLen <= 10) {
- container.innerHTML = `
+ return;
+ } else if (urlLen <= 10) {
+ container.innerHTML = `
Missing URL
`;
- return;
- } else if (keyLen <= 20) {
- container.innerHTML = `
+ return;
+ } else if (keyLen <= 20) {
+ container.innerHTML = `
Missing API Key
`;
- return;
- }
-
- container.innerHTML = `
+ return;
+ }
+
+ container.innerHTML = `
Checking...
`;
-
- // Test the connection using the correct endpoint
- HuntarrUtils.fetchWithTimeout(`./api/${appType}/test-connection`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ api_url: url, api_key: apiKey })
- }, 10000)
- .then(response => response.json())
- .then(data => {
- if (data.success) {
- let statusText = 'Connected';
- if (data.version) {
- statusText = `Connected (${data.version})`;
- }
- container.innerHTML = `
+
+ // Test the connection using the correct endpoint
+ HuntarrUtils.fetchWithTimeout(`./api/${appType}/test-connection`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ api_url: url, api_key: apiKey })
+ }, 10000)
+ .then(response => response.json())
+ .then(data => {
+ if (data.success) {
+ let statusText = 'Connected';
+ if (data.version) {
+ statusText = `Connected (${data.version})`;
+ }
+ container.innerHTML = `
${statusText}
`;
- } else {
- // If connection failed, show the error message from the API if available
- const errorMsg = data.error || data.message || 'Connection failed';
- container.innerHTML = `
+ } else {
+ // If connection failed, show the error message from the API if available
+ const errorMsg = data.error || data.message || 'Connection failed';
+ container.innerHTML = `
${errorMsg}
`;
- }
- })
- .catch(error => {
- container.innerHTML = `
+ }
+ })
+ .catch(error => {
+ container.innerHTML = `
Connection failed: ${error.message || 'Network error'}
`;
- });
- },
+ });
+ },
- // Generate HTML for the full-page editor
- generateEditorHtml: function(appType, instance, index) {
- console.log(`[SettingsForms] Generating editor HTML for ${appType}, instance index: ${index}`);
- const isEdit = index !== null;
- const swaparrEnabled = this.isSwaparrGloballyEnabled();
-
- // Ensure instance properties have defaults if undefined
- const safeInstance = {
- enabled: instance.enabled !== false,
- name: instance.name || '',
- instance_id: instance.instance_id || '',
- api_url: instance.api_url || '',
- api_key: instance.api_key || '',
- external_url: instance.external_url || '',
- hunt_missing_items: instance.hunt_missing_items !== undefined ? instance.hunt_missing_items : 1,
- hunt_upgrade_items: instance.hunt_upgrade_items !== undefined ? instance.hunt_upgrade_items : 0,
- hunt_missing_mode: instance.hunt_missing_mode || 'seasons_packs',
- upgrade_mode: instance.upgrade_mode || 'seasons_packs',
- air_date_delay_days: instance.air_date_delay_days || 0,
- release_date_delay_days: instance.release_date_delay_days || 0,
- state_management_mode: instance.state_management_mode || 'custom',
- state_management_hours: instance.state_management_hours || 72,
- swaparr_enabled: instance.swaparr_enabled === true,
- // Additional Options (per-instance)
- monitored_only: instance.monitored_only !== false,
- skip_future_episodes: instance.skip_future_episodes !== false,
- tag_processed_items: instance.tag_processed_items !== false,
- tag_enable_missing: instance.tag_enable_missing !== false,
- tag_enable_upgrade: instance.tag_enable_upgrade !== false,
- tag_enable_upgraded: instance.tag_enable_upgraded !== false,
- tag_enable_shows_missing: instance.tag_enable_shows_missing !== false,
- // Custom Tags (per-instance)
- custom_tags: instance.custom_tags || {},
- // Exempt Tags (per-instance) - items with these tags are skipped for missing/upgrade
- exempt_tags: Array.isArray(instance.exempt_tags) ? instance.exempt_tags : [],
- // Advanced Settings (per-instance)
- api_timeout: instance.api_timeout || 120,
- command_wait_delay: instance.command_wait_delay || 1,
- command_wait_attempts: instance.command_wait_attempts || 600,
- max_download_queue_size: instance.max_download_queue_size !== undefined ? instance.max_download_queue_size : -1,
- max_seed_queue_size: instance.max_seed_queue_size !== undefined ? instance.max_seed_queue_size : -1,
- seed_check_torrent_client: instance.seed_check_torrent_client && typeof instance.seed_check_torrent_client === 'object' ? instance.seed_check_torrent_client : null,
- // Cycle settings (per-instance; were global in 9.0.x)
- sleep_duration: instance.sleep_duration !== undefined ? instance.sleep_duration : 900,
- hourly_cap: instance.hourly_cap !== undefined ? instance.hourly_cap : 20
- };
+ // Generate HTML for the full-page editor
+ generateEditorHtml: function (appType, instance, index) {
+ console.log(`[SettingsForms] Generating editor HTML for ${appType}, instance index: ${index}`);
+ const isEdit = index !== null;
+ const swaparrEnabled = this.isSwaparrGloballyEnabled();
- // Handle specific fields for different apps
- if (appType === 'sonarr') {
- safeInstance.hunt_missing_items = instance.hunt_missing_items !== undefined ? instance.hunt_missing_items : 1;
- safeInstance.hunt_upgrade_items = instance.hunt_upgrade_items !== undefined ? instance.hunt_upgrade_items : 0;
- safeInstance.upgrade_selection_method = instance.upgrade_selection_method !== undefined ? instance.upgrade_selection_method : 'cutoff';
- safeInstance.upgrade_tag = instance.upgrade_tag !== undefined ? instance.upgrade_tag : '';
- } else if (appType === 'radarr') {
- safeInstance.hunt_missing_items = instance.hunt_missing_movies !== undefined ? instance.hunt_missing_movies : 1;
- safeInstance.hunt_upgrade_items = instance.hunt_upgrade_movies !== undefined ? instance.hunt_upgrade_movies : 0;
- safeInstance.upgrade_selection_method = instance.upgrade_selection_method !== undefined ? instance.upgrade_selection_method : 'cutoff';
- safeInstance.upgrade_tag = instance.upgrade_tag !== undefined ? instance.upgrade_tag : '';
- } else if (appType === 'lidarr') {
- safeInstance.hunt_missing_items = instance.hunt_missing_items !== undefined ? instance.hunt_missing_items : 1;
- safeInstance.hunt_upgrade_items = instance.hunt_upgrade_items !== undefined ? instance.hunt_upgrade_items : 0;
- safeInstance.hunt_missing_mode = instance.hunt_missing_mode || 'album';
- safeInstance.upgrade_selection_method = instance.upgrade_selection_method !== undefined ? instance.upgrade_selection_method : 'cutoff';
- safeInstance.upgrade_tag = instance.upgrade_tag !== undefined ? instance.upgrade_tag : '';
- } else if (appType === 'readarr') {
- safeInstance.hunt_missing_items = instance.hunt_missing_books !== undefined ? instance.hunt_missing_books : 1;
- safeInstance.hunt_upgrade_items = instance.hunt_upgrade_books !== undefined ? instance.hunt_upgrade_books : 0;
- safeInstance.upgrade_selection_method = instance.upgrade_selection_method !== undefined ? instance.upgrade_selection_method : 'cutoff';
- safeInstance.upgrade_tag = instance.upgrade_tag !== undefined ? instance.upgrade_tag : '';
- } else if (appType === 'eros') {
- safeInstance.hunt_missing_items = instance.hunt_missing_items !== undefined ? instance.hunt_missing_items : 1;
- safeInstance.hunt_upgrade_items = instance.hunt_upgrade_items !== undefined ? instance.hunt_upgrade_items : 0;
- safeInstance.search_mode = instance.search_mode !== undefined ? instance.search_mode : 'movie';
- }
+ // Ensure instance properties have defaults if undefined
+ const safeInstance = {
+ enabled: instance.enabled !== false,
+ name: instance.name || '',
+ instance_id: instance.instance_id || '',
+ api_url: instance.api_url || '',
+ api_key: instance.api_key || '',
+ external_url: instance.external_url || '',
+ hunt_missing_items: instance.hunt_missing_items !== undefined ? instance.hunt_missing_items : 1,
+ hunt_upgrade_items: instance.hunt_upgrade_items !== undefined ? instance.hunt_upgrade_items : 0,
+ hunt_missing_mode: instance.hunt_missing_mode || 'seasons_packs',
+ upgrade_mode: instance.upgrade_mode || 'seasons_packs',
+ air_date_delay_days: instance.air_date_delay_days || 0,
+ release_date_delay_days: instance.release_date_delay_days || 0,
+ state_management_mode: instance.state_management_mode || 'custom',
+ state_management_hours: instance.state_management_hours || 72,
+ swaparr_enabled: instance.swaparr_enabled === true,
+ // Additional Options (per-instance)
+ monitored_only: instance.monitored_only !== false,
+ skip_future_episodes: instance.skip_future_episodes !== false,
+ tag_processed_items: instance.tag_processed_items !== false,
+ tag_enable_missing: instance.tag_enable_missing !== false,
+ tag_enable_upgrade: instance.tag_enable_upgrade !== false,
+ tag_enable_upgraded: instance.tag_enable_upgraded !== false,
+ tag_enable_shows_missing: instance.tag_enable_shows_missing !== false,
+ // Custom Tags (per-instance)
+ custom_tags: instance.custom_tags || {},
+ // Exempt Tags (per-instance) - items with these tags are skipped for missing/upgrade
+ exempt_tags: Array.isArray(instance.exempt_tags) ? instance.exempt_tags : [],
+ // Advanced Settings (per-instance)
+ api_timeout: instance.api_timeout || 120,
+ command_wait_delay: instance.command_wait_delay || 1,
+ command_wait_attempts: instance.command_wait_attempts || 600,
+ max_download_queue_size: instance.max_download_queue_size !== undefined ? instance.max_download_queue_size : -1,
+ max_seed_queue_size: instance.max_seed_queue_size !== undefined ? instance.max_seed_queue_size : -1,
+ seed_check_torrent_client: instance.seed_check_torrent_client && typeof instance.seed_check_torrent_client === 'object' ? instance.seed_check_torrent_client : null,
+ // Cycle settings (per-instance; were global in 9.0.x)
+ sleep_duration: instance.sleep_duration !== undefined ? instance.sleep_duration : 900,
+ hourly_cap: instance.hourly_cap !== undefined ? instance.hourly_cap : 20
+ };
- const devMode = !!(window.huntarrUI && window.huntarrUI.originalSettings && window.huntarrUI.originalSettings.general && window.huntarrUI.originalSettings.general.dev_mode);
- const sleepMin = devMode ? 1 : 10;
+ // Handle specific fields for different apps
+ if (appType === 'sonarr') {
+ safeInstance.hunt_missing_items = instance.hunt_missing_items !== undefined ? instance.hunt_missing_items : 1;
+ safeInstance.hunt_upgrade_items = instance.hunt_upgrade_items !== undefined ? instance.hunt_upgrade_items : 0;
+ safeInstance.upgrade_selection_method = instance.upgrade_selection_method !== undefined ? instance.upgrade_selection_method : 'cutoff';
+ safeInstance.upgrade_tag = instance.upgrade_tag !== undefined ? instance.upgrade_tag : '';
+ } else if (appType === 'radarr') {
+ safeInstance.hunt_missing_items = instance.hunt_missing_movies !== undefined ? instance.hunt_missing_movies : 1;
+ safeInstance.hunt_upgrade_items = instance.hunt_upgrade_movies !== undefined ? instance.hunt_upgrade_movies : 0;
+ safeInstance.upgrade_selection_method = instance.upgrade_selection_method !== undefined ? instance.upgrade_selection_method : 'cutoff';
+ safeInstance.upgrade_tag = instance.upgrade_tag !== undefined ? instance.upgrade_tag : '';
+ } else if (appType === 'lidarr') {
+ safeInstance.hunt_missing_items = instance.hunt_missing_items !== undefined ? instance.hunt_missing_items : 1;
+ safeInstance.hunt_upgrade_items = instance.hunt_upgrade_items !== undefined ? instance.hunt_upgrade_items : 0;
+ safeInstance.hunt_missing_mode = instance.hunt_missing_mode || 'album';
+ safeInstance.upgrade_selection_method = instance.upgrade_selection_method !== undefined ? instance.upgrade_selection_method : 'cutoff';
+ safeInstance.upgrade_tag = instance.upgrade_tag !== undefined ? instance.upgrade_tag : '';
+ } else if (appType === 'readarr') {
+ safeInstance.hunt_missing_items = instance.hunt_missing_books !== undefined ? instance.hunt_missing_books : 1;
+ safeInstance.hunt_upgrade_items = instance.hunt_upgrade_books !== undefined ? instance.hunt_upgrade_books : 0;
+ safeInstance.upgrade_selection_method = instance.upgrade_selection_method !== undefined ? instance.upgrade_selection_method : 'cutoff';
+ safeInstance.upgrade_tag = instance.upgrade_tag !== undefined ? instance.upgrade_tag : '';
+ } else if (appType === 'eros') {
+ safeInstance.hunt_missing_items = instance.hunt_missing_items !== undefined ? instance.hunt_missing_items : 1;
+ safeInstance.hunt_upgrade_items = instance.hunt_upgrade_items !== undefined ? instance.hunt_upgrade_items : 0;
+ safeInstance.search_mode = instance.search_mode !== undefined ? instance.search_mode : 'movie';
+ }
- // Default port and example URL per app (for placeholder and help text)
- const defaultPortByApp = { sonarr: 8989, radarr: 7878, lidarr: 8686, readarr: 8787, whisparr: 6969, eros: 6969 };
- const defaultPort = defaultPortByApp[appType] || 8989;
- const exampleUrl = `http://localhost:${defaultPort}`;
- const placeholderUrl = `http://192.168.1.100:${defaultPort}`;
+ const devMode = !!(window.huntarrUI && window.huntarrUI.originalSettings && window.huntarrUI.originalSettings.general && window.huntarrUI.originalSettings.general.dev_mode);
+ const sleepMin = devMode ? 1 : 10;
- let html = `
+ // Default port and example URL per app (for placeholder and help text)
+ const defaultPortByApp = { sonarr: 8989, radarr: 7878, lidarr: 8686, readarr: 8787, whisparr: 6969, eros: 6969 };
+ const defaultPort = defaultPortByApp[appType] || 8989;
+ const exampleUrl = `http://localhost:${defaultPort}`;
+ const placeholderUrl = `http://192.168.1.100:${defaultPort}`;
+
+ let html = `
@@ -1595,8 +1595,8 @@ document.head.appendChild(styleEl);
`;
- if (appType === 'sonarr') {
- html += `
+ if (appType === 'sonarr') {
+ html += `
Search Settings
@@ -1673,8 +1673,8 @@ document.head.appendChild(styleEl);
`;
- } else if (['radarr', 'lidarr', 'readarr', 'whisparr', 'eros'].includes(appType)) {
- html += `
+ } else if (['radarr', 'lidarr', 'readarr', 'whisparr', 'eros'].includes(appType)) {
+ html += `
Search Settings
@@ -1694,8 +1694,8 @@ document.head.appendChild(styleEl);
Number of items to upgrade in each cycle
`;
- if (appType === 'lidarr') {
- html += `
+ if (appType === 'lidarr') {
+ html += `
Missing Search Mode
@@ -1706,9 +1706,9 @@ document.head.appendChild(styleEl);
Search for individual albums (Artist mode deprecated in Huntarr 7.5.0+)
`;
- }
- if (appType === 'eros') {
- html += `
+ }
+ if (appType === 'eros') {
+ html += `
Search Mode
@@ -1720,9 +1720,9 @@ document.head.appendChild(styleEl);
How to search for missing and upgradable Whisparr V3 content (Movie-based or Scene-based)
`;
- }
- if (appType === 'radarr') {
- html += `
+ }
+ if (appType === 'radarr') {
+ html += `
Upgrade Selection Method
@@ -1754,12 +1754,12 @@ document.head.appendChild(styleEl);
Only search for items released at least this many days ago
`;
- }
- if (appType === 'lidarr' || appType === 'readarr') {
- const tagHelp = appType === 'lidarr'
- ? 'Tag name on artists in Lidarr. Huntarr finds artists that don’t have this tag, runs upgrade searches on their albums, then adds the tag when done (tracks what’s been processed).
💡 TrashGuides |
🔗 Upgradinatorr '
- : 'Tag name on authors in Readarr. Huntarr finds authors that don’t have this tag, runs upgrade searches on their books, then adds the tag when done (tracks what’s been processed).
💡 TrashGuides |
🔗 Upgradinatorr ';
- html += `
+ }
+ if (appType === 'lidarr' || appType === 'readarr') {
+ const tagHelp = appType === 'lidarr'
+ ? 'Tag name on artists in Lidarr. Huntarr finds artists that don’t have this tag, runs upgrade searches on their albums, then adds the tag when done (tracks what’s been processed).
💡 TrashGuides |
🔗 Upgradinatorr '
+ : 'Tag name on authors in Readarr. Huntarr finds authors that don’t have this tag, runs upgrade searches on their books, then adds the tag when done (tracks what’s been processed).
💡 TrashGuides |
🔗 Upgradinatorr ';
+ html += `
Upgrade Selection Method
@@ -1781,13 +1781,13 @@ document.head.appendChild(styleEl);
${tagHelp}
`;
+ }
+
+ html += `
`;
}
-
- html += `
`;
- }
-
- // Stateful Management Section (separate from Advanced)
- html += `
+
+ // Stateful Management Section (separate from Advanced)
+ html += `
Stateful Management
@@ -1899,7 +1899,7 @@ document.head.appendChild(styleEl);
Tag added to items when they're found by a missing search (max 25 characters)
-
+
Tag upgrade items
@@ -2045,311 +2045,311 @@ document.head.appendChild(styleEl);
`;
- return html;
- },
+ return html;
+ },
- // Save instance from the full-page editor
- saveInstanceFromEditor: function(navigateBack = false) {
- if (!this._currentEditing) return;
- const { appType, index } = this._currentEditing;
- const settings = window.huntarrUI.originalSettings[appType];
- if (!settings) return;
-
- const tagEnableUpgradeEl = document.getElementById('editor-tag-enable-upgrade');
- const upgradeMethodEl = document.getElementById('editor-upgrade-method');
- const upgradeTagEl = document.getElementById('editor-upgrade-tag');
- const isTagsMode = upgradeMethodEl && upgradeMethodEl.value === 'tags';
- const tagEnableMissing = document.getElementById('editor-tag-enable-missing').checked;
- const tagEnableUpgrade = isTagsMode ? false : (tagEnableUpgradeEl ? tagEnableUpgradeEl.checked : false);
- const tagEnableShowsMissingEl = document.getElementById('editor-tag-enable-shows-missing');
- const tagEnableShowsMissing = tagEnableShowsMissingEl ? tagEnableShowsMissingEl.checked : false;
- const newData = {
- enabled: document.getElementById('editor-enabled').value === 'true',
- name: document.getElementById('editor-name').value,
- api_url: document.getElementById('editor-url').value,
- api_key: document.getElementById('editor-key').value,
- external_url: (document.getElementById('editor-external-url').value || '').trim(),
- state_management_mode: document.getElementById('editor-state-mode').value,
- state_management_hours: parseInt(document.getElementById('editor-state-hours').value) || 72,
- // Additional Options
- monitored_only: document.getElementById('editor-monitored-only').checked,
- tag_processed_items: tagEnableMissing || tagEnableUpgrade || tagEnableShowsMissing,
- tag_enable_missing: tagEnableMissing,
- tag_enable_upgrade: tagEnableUpgrade,
- tag_enable_upgraded: false,
- tag_enable_shows_missing: tagEnableShowsMissing,
- // Custom Tags
- custom_tags: {
- missing: document.getElementById('editor-tag-missing').value,
- upgrade: (document.getElementById('editor-tag-upgrade') ? document.getElementById('editor-tag-upgrade').value : '') || 'huntarr-upgrade'
- },
- // Advanced Settings
- api_timeout: parseInt(document.getElementById('editor-api-timeout').value) || 120,
- command_wait_delay: parseInt(document.getElementById('editor-cmd-wait-delay').value) || 1,
- command_wait_attempts: (function(){ const el = document.getElementById('editor-cmd-wait-attempts'); if (!el) return 600; const v = parseInt(el.value, 10); return (!isNaN(v) && v >= 0) ? v : 600; })(),
- max_download_queue_size: parseInt(document.getElementById('editor-max-queue-size').value) || -1,
- max_seed_queue_size: (function(){ const v = parseInt(document.getElementById('editor-max-seed-queue-size').value, 10); return (!isNaN(v) && v >= -1) ? v : -1; })(),
- seed_check_torrent_client: (function() {
- const typeEl = document.getElementById('editor-seed-client-type');
- const type = (typeEl ? (typeEl.value || '').trim() : '') || 'qbittorrent';
- const hostEl = document.getElementById('editor-seed-client-host');
- const host = hostEl ? (hostEl.value || '').trim() : '';
- if (!host) return null;
- const portEl = document.getElementById('editor-seed-client-port');
- const portVal = portEl && portEl.value !== '' ? parseInt(portEl.value, 10) : (type === 'qbittorrent' ? 8080 : 9091);
- const port = (!isNaN(portVal) && portVal >= 1 && portVal <= 65535) ? portVal : (type === 'qbittorrent' ? 8080 : 9091);
- const userEl = document.getElementById('editor-seed-client-username');
- const passEl = document.getElementById('editor-seed-client-password');
- return { type: type, host: host, port: port, username: userEl ? userEl.value : '', password: passEl ? passEl.value : '' };
- })(),
- // Per-instance cycle settings
- sleep_duration: (parseInt(document.getElementById('editor-sleep-duration').value, 10) || 15) * 60,
- hourly_cap: parseInt(document.getElementById('editor-hourly-cap').value, 10) || 20
- };
-
- // Add skip_future_episodes for Sonarr
- const skipFutureInput = document.getElementById('editor-skip-future');
- if (skipFutureInput) {
- newData.skip_future_episodes = skipFutureInput.checked;
- }
-
- // Add shows_missing tag for Sonarr
- const showsMissingTagInput = document.getElementById('editor-tag-shows-missing');
- if (showsMissingTagInput) {
- newData.custom_tags.shows_missing = showsMissingTagInput.value;
- }
-
- const swaparrInput = document.getElementById('editor-swaparr');
- if (swaparrInput) {
- newData.swaparr_enabled = swaparrInput.checked;
- }
-
- if (appType === 'sonarr') {
- newData.hunt_missing_items = parseInt(document.getElementById('editor-missing-count').value) || 0;
- newData.hunt_upgrade_items = parseInt(document.getElementById('editor-upgrade-count').value) || 0;
- newData.hunt_missing_mode = document.getElementById('editor-missing-mode').value;
- newData.upgrade_mode = document.getElementById('editor-upgrade-mode').value;
- newData.air_date_delay_days = parseInt(document.getElementById('editor-air-date-delay').value) || 0;
- newData.upgrade_selection_method = (upgradeMethodEl && upgradeMethodEl.value) ? upgradeMethodEl.value : 'cutoff';
- // Auto-fill "upgradinatorr" if tags mode is selected but no tag is provided
- let upgradeTagValue = (upgradeTagEl && upgradeTagEl.value) ? String(upgradeTagEl.value).trim() : '';
- if (newData.upgrade_selection_method === 'tags' && !upgradeTagValue) {
- upgradeTagValue = 'upgradinatorr';
+ // Save instance from the full-page editor
+ saveInstanceFromEditor: function (navigateBack = false) {
+ if (!this._currentEditing) return;
+ const { appType, index } = this._currentEditing;
+ const settings = window.huntarrUI.originalSettings[appType];
+ if (!settings) return;
+
+ const tagEnableUpgradeEl = document.getElementById('editor-tag-enable-upgrade');
+ const upgradeMethodEl = document.getElementById('editor-upgrade-method');
+ const upgradeTagEl = document.getElementById('editor-upgrade-tag');
+ const isTagsMode = upgradeMethodEl && upgradeMethodEl.value === 'tags';
+ const tagEnableMissing = document.getElementById('editor-tag-enable-missing').checked;
+ const tagEnableUpgrade = isTagsMode ? false : (tagEnableUpgradeEl ? tagEnableUpgradeEl.checked : false);
+ const tagEnableShowsMissingEl = document.getElementById('editor-tag-enable-shows-missing');
+ const tagEnableShowsMissing = tagEnableShowsMissingEl ? tagEnableShowsMissingEl.checked : false;
+ const newData = {
+ enabled: document.getElementById('editor-enabled').value === 'true',
+ name: document.getElementById('editor-name').value,
+ api_url: document.getElementById('editor-url').value,
+ api_key: document.getElementById('editor-key').value,
+ external_url: (document.getElementById('editor-external-url').value || '').trim(),
+ state_management_mode: document.getElementById('editor-state-mode').value,
+ state_management_hours: parseInt(document.getElementById('editor-state-hours').value) || 72,
+ // Additional Options
+ monitored_only: document.getElementById('editor-monitored-only').checked,
+ tag_processed_items: tagEnableMissing || tagEnableUpgrade || tagEnableShowsMissing,
+ tag_enable_missing: tagEnableMissing,
+ tag_enable_upgrade: tagEnableUpgrade,
+ tag_enable_upgraded: false,
+ tag_enable_shows_missing: tagEnableShowsMissing,
+ // Custom Tags
+ custom_tags: {
+ missing: document.getElementById('editor-tag-missing').value,
+ upgrade: (document.getElementById('editor-tag-upgrade') ? document.getElementById('editor-tag-upgrade').value : '') || 'huntarr-upgrade'
+ },
+ // Advanced Settings
+ api_timeout: parseInt(document.getElementById('editor-api-timeout').value) || 120,
+ command_wait_delay: parseInt(document.getElementById('editor-cmd-wait-delay').value) || 1,
+ command_wait_attempts: (function () { const el = document.getElementById('editor-cmd-wait-attempts'); if (!el) return 600; const v = parseInt(el.value, 10); return (!isNaN(v) && v >= 0) ? v : 600; })(),
+ max_download_queue_size: parseInt(document.getElementById('editor-max-queue-size').value) || -1,
+ max_seed_queue_size: (function () { const v = parseInt(document.getElementById('editor-max-seed-queue-size').value, 10); return (!isNaN(v) && v >= -1) ? v : -1; })(),
+ seed_check_torrent_client: (function () {
+ const typeEl = document.getElementById('editor-seed-client-type');
+ const type = (typeEl ? (typeEl.value || '').trim() : '') || 'qbittorrent';
+ const hostEl = document.getElementById('editor-seed-client-host');
+ const host = hostEl ? (hostEl.value || '').trim() : '';
+ if (!host) return null;
+ const portEl = document.getElementById('editor-seed-client-port');
+ const portVal = portEl && portEl.value !== '' ? parseInt(portEl.value, 10) : (type === 'qbittorrent' ? 8080 : 9091);
+ const port = (!isNaN(portVal) && portVal >= 1 && portVal <= 65535) ? portVal : (type === 'qbittorrent' ? 8080 : 9091);
+ const userEl = document.getElementById('editor-seed-client-username');
+ const passEl = document.getElementById('editor-seed-client-password');
+ return { type: type, host: host, port: port, username: userEl ? userEl.value : '', password: passEl ? passEl.value : '' };
+ })(),
+ // Per-instance cycle settings
+ sleep_duration: (parseInt(document.getElementById('editor-sleep-duration').value, 10) || 15) * 60,
+ hourly_cap: parseInt(document.getElementById('editor-hourly-cap').value, 10) || 20
+ };
+
+ // Add skip_future_episodes for Sonarr
+ const skipFutureInput = document.getElementById('editor-skip-future');
+ if (skipFutureInput) {
+ newData.skip_future_episodes = skipFutureInput.checked;
}
- newData.upgrade_tag = upgradeTagValue;
- }
- const exemptTagsListEl = document.getElementById('editor-exempt-tags-list');
- newData.exempt_tags = exemptTagsListEl ? Array.from(exemptTagsListEl.querySelectorAll('.exempt-tag-chip')).map(el => el.getAttribute('data-tag') || '').filter(Boolean) : [];
- if (appType !== 'sonarr') {
- const missingField = appType === 'radarr' ? 'hunt_missing_movies' : (appType === 'readarr' ? 'hunt_missing_books' : 'hunt_missing_items');
- const upgradeField = appType === 'radarr' ? 'hunt_upgrade_movies' : (appType === 'readarr' ? 'hunt_upgrade_books' : 'hunt_upgrade_items');
-
- newData[missingField] = parseInt(document.getElementById('editor-missing-count').value) || 0;
- newData[upgradeField] = parseInt(document.getElementById('editor-upgrade-count').value) || 0;
-
- if (appType === 'radarr') {
- newData.release_date_delay_days = parseInt(document.getElementById('editor-release-date-delay').value) || 0;
- }
- if (appType === 'radarr' || appType === 'lidarr' || appType === 'readarr') {
- newData.upgrade_selection_method = (upgradeMethodEl && upgradeMethodEl.value) ? upgradeMethodEl.value : 'cutoff';
- // Auto-fill "upgradinatorr" if tags mode is selected but no tag is provided
- let upgradeTagValue = (upgradeTagEl && upgradeTagEl.value) ? String(upgradeTagEl.value).trim() : '';
- if (newData.upgrade_selection_method === 'tags' && !upgradeTagValue) {
- upgradeTagValue = 'upgradinatorr';
- }
- newData.upgrade_tag = upgradeTagValue;
- }
- if (appType === 'lidarr') {
- const lidarrModeEl = document.getElementById('editor-lidarr-missing-mode');
- if (lidarrModeEl) newData.hunt_missing_mode = lidarrModeEl.value || 'album';
- }
- if (appType === 'eros') {
- const erosModeEl = document.getElementById('editor-eros-search-mode');
- if (erosModeEl) newData.search_mode = erosModeEl.value || 'movie';
- }
- }
-
- let finalIndex = index;
- if (index !== null) {
- settings.instances[index] = { ...settings.instances[index], ...newData };
- } else {
- settings.instances.push(newData);
- finalIndex = settings.instances.length - 1;
- }
-
- // Update originalSettings to keep editor in sync
- window.huntarrUI.originalSettings[appType] = settings;
-
- const self = this;
- const savePromise = this.saveAppSettings(appType, settings);
- if (savePromise && typeof savePromise.then === 'function') {
- savePromise.then(function(data) {
- // Server may have generated instance_id for new instances; update the displayed field
- if (data && data.settings && data.settings.instances && data.settings.instances[finalIndex]) {
- const savedInstance = data.settings.instances[finalIndex];
- const instanceId = (savedInstance.instance_id || '').trim();
- if (instanceId) {
- const idInput = document.getElementById('editor-instance-id');
- if (idInput) idInput.value = instanceId;
- if (self._currentEditing && self._currentEditing.originalInstance) {
- self._currentEditing.originalInstance.instance_id = instanceId;
+
+ // Add shows_missing tag for Sonarr
+ const showsMissingTagInput = document.getElementById('editor-tag-shows-missing');
+ if (showsMissingTagInput) {
+ newData.custom_tags.shows_missing = showsMissingTagInput.value;
+ }
+
+ const swaparrInput = document.getElementById('editor-swaparr');
+ if (swaparrInput) {
+ newData.swaparr_enabled = swaparrInput.checked;
+ }
+
+ if (appType === 'sonarr') {
+ newData.hunt_missing_items = parseInt(document.getElementById('editor-missing-count').value) || 0;
+ newData.hunt_upgrade_items = parseInt(document.getElementById('editor-upgrade-count').value) || 0;
+ newData.hunt_missing_mode = document.getElementById('editor-missing-mode').value;
+ newData.upgrade_mode = document.getElementById('editor-upgrade-mode').value;
+ newData.air_date_delay_days = parseInt(document.getElementById('editor-air-date-delay').value) || 0;
+ newData.upgrade_selection_method = (upgradeMethodEl && upgradeMethodEl.value) ? upgradeMethodEl.value : 'cutoff';
+ // Auto-fill "upgradinatorr" if tags mode is selected but no tag is provided
+ let upgradeTagValue = (upgradeTagEl && upgradeTagEl.value) ? String(upgradeTagEl.value).trim() : '';
+ if (newData.upgrade_selection_method === 'tags' && !upgradeTagValue) {
+ upgradeTagValue = 'upgradinatorr';
+ }
+ newData.upgrade_tag = upgradeTagValue;
+ }
+ const exemptTagsListEl = document.getElementById('editor-exempt-tags-list');
+ newData.exempt_tags = exemptTagsListEl ? Array.from(exemptTagsListEl.querySelectorAll('.exempt-tag-chip')).map(el => el.getAttribute('data-tag') || '').filter(Boolean) : [];
+ if (appType !== 'sonarr') {
+ const missingField = appType === 'radarr' ? 'hunt_missing_movies' : (appType === 'readarr' ? 'hunt_missing_books' : 'hunt_missing_items');
+ const upgradeField = appType === 'radarr' ? 'hunt_upgrade_movies' : (appType === 'readarr' ? 'hunt_upgrade_books' : 'hunt_upgrade_items');
+
+ newData[missingField] = parseInt(document.getElementById('editor-missing-count').value) || 0;
+ newData[upgradeField] = parseInt(document.getElementById('editor-upgrade-count').value) || 0;
+
+ if (appType === 'radarr') {
+ newData.release_date_delay_days = parseInt(document.getElementById('editor-release-date-delay').value) || 0;
+ }
+ if (appType === 'radarr' || appType === 'lidarr' || appType === 'readarr') {
+ newData.upgrade_selection_method = (upgradeMethodEl && upgradeMethodEl.value) ? upgradeMethodEl.value : 'cutoff';
+ // Auto-fill "upgradinatorr" if tags mode is selected but no tag is provided
+ let upgradeTagValue = (upgradeTagEl && upgradeTagEl.value) ? String(upgradeTagEl.value).trim() : '';
+ if (newData.upgrade_selection_method === 'tags' && !upgradeTagValue) {
+ upgradeTagValue = 'upgradinatorr';
+ }
+ newData.upgrade_tag = upgradeTagValue;
+ }
+ if (appType === 'lidarr') {
+ const lidarrModeEl = document.getElementById('editor-lidarr-missing-mode');
+ if (lidarrModeEl) newData.hunt_missing_mode = lidarrModeEl.value || 'album';
+ }
+ if (appType === 'eros') {
+ const erosModeEl = document.getElementById('editor-eros-search-mode');
+ if (erosModeEl) newData.search_mode = erosModeEl.value || 'movie';
+ }
+ }
+
+ let finalIndex = index;
+ if (index !== null) {
+ settings.instances[index] = { ...settings.instances[index], ...newData };
+ } else {
+ settings.instances.push(newData);
+ finalIndex = settings.instances.length - 1;
+ }
+
+ // Update originalSettings to keep editor in sync
+ window.huntarrUI.originalSettings[appType] = settings;
+
+ const self = this;
+ const savePromise = this.saveAppSettings(appType, settings);
+ if (savePromise && typeof savePromise.then === 'function') {
+ savePromise.then(function (data) {
+ // Server may have generated instance_id for new instances; update the displayed field
+ if (data && data.settings && data.settings.instances && data.settings.instances[finalIndex]) {
+ const savedInstance = data.settings.instances[finalIndex];
+ const instanceId = (savedInstance.instance_id || '').trim();
+ if (instanceId) {
+ const idInput = document.getElementById('editor-instance-id');
+ if (idInput) idInput.value = instanceId;
+ if (self._currentEditing && self._currentEditing.originalInstance) {
+ self._currentEditing.originalInstance.instance_id = instanceId;
+ }
}
}
- }
- }).catch(function() { /* saveAppSettings already shows error */ });
- }
-
- // Update current editing state with new index (in case it was a new instance)
- this._currentEditing = { appType, index: finalIndex, originalInstance: JSON.parse(JSON.stringify(newData)) };
- _instanceEditorDirty = false;
-
- // Show or hide the stateful block (green box + reset button) and refresh state
- const statefulBlock = document.getElementById('instance-editor-stateful-block');
- if (statefulBlock) {
- statefulBlock.style.display = newData.state_management_mode === 'disabled' ? 'none' : 'block';
- }
- if (newData.state_management_mode !== 'disabled') {
- this.startStateStatusPolling(appType, finalIndex);
- } else {
- this.stopStateStatusPolling();
- }
-
- // Disable save button to show it's saved
- const saveBtn = document.getElementById('instance-editor-save');
- if (saveBtn) {
- saveBtn.disabled = true;
- saveBtn.classList.remove('enabled');
- }
-
- // Show brief success feedback
- const originalText = saveBtn ? saveBtn.innerHTML : '';
- if (saveBtn) {
- saveBtn.innerHTML = '
Saved!';
- saveBtn.style.opacity = '0.7';
- setTimeout(() => {
- saveBtn.innerHTML = originalText;
- saveBtn.style.opacity = '1';
- if (navigateBack) {
- this.cancelInstanceEditor(this._instanceEditorNextSection);
- this._instanceEditorNextSection = null;
- }
- }, 2000);
- } else if (navigateBack) {
- this.cancelInstanceEditor(this._instanceEditorNextSection);
- this._instanceEditorNextSection = null;
- }
-
- // Reset change detection by updating the original instance
- // This allows the save button to be enabled again if user makes more changes
- if (this._currentEditing) {
- this._currentEditing.originalInstance = JSON.parse(JSON.stringify(newData));
- }
-
- // Stay on the editor page - don't navigate away unless navigateBack is true
- },
-
- // Cancel editing and return to app section (or settings-indexers for indexer)
- cancelInstanceEditor: function(optionalNextSection) {
- // Stop polling when leaving editor
- this.stopStateStatusPolling();
-
- if (optionalNextSection) {
- window.huntarrUI.switchSection(optionalNextSection);
- this._currentEditing = null;
- _instanceEditorDirty = false;
- this._updateHashForSection(optionalNextSection);
- return;
- }
-
- if (!this._currentEditing) {
- window.huntarrUI.switchSection('sonarr');
- this._currentEditing = null;
- _instanceEditorDirty = false;
- this._updateHashForSection('sonarr');
- return;
- }
- const appType = this._currentEditing.appType;
- this._currentEditing = null;
- _instanceEditorDirty = false;
- if (appType === 'indexer') {
- window.huntarrUI.switchSection('indexer-hunt');
- this._updateHashForSection('indexer-hunt');
- } else if (appType === 'client') {
- window.huntarrUI.switchSection('settings-clients');
- this._updateHashForSection('settings-clients');
- } else {
- window.huntarrUI.switchSection(appType);
- this._updateHashForSection(appType);
- }
- },
-
- _updateHashForSection: function(section) {
- try {
- const newUrl = (window.location.pathname || '') + (window.location.search || '') + '#' + section;
- window.history.replaceState(null, '', newUrl);
- } catch (e) { /* ignore */ }
- },
-
- // Open the modal for adding/editing an instance
- openInstanceModal: function(appType, index = null) {
- this.navigateToInstanceEditor(appType, index);
- },
-
- // Delete instance
- deleteInstance: function(appType, index) {
- const settings = window.huntarrUI.originalSettings[appType];
- if (!settings || !settings.instances || settings.instances[index] === undefined) {
- console.error(`[huntarrUI] Cannot delete instance: index ${index} not found for ${appType}`);
- return;
- }
-
- const instanceName = settings.instances[index].name || 'Unnamed Instance';
- const isDefault = index === 0;
- const hasOtherInstances = settings.instances.length > 1;
-
- // Custom confirmation message for default instance
- let confirmMessage = `Are you sure you want to delete the instance "${instanceName}"?`;
- if (isDefault && hasOtherInstances) {
- const nextInstance = settings.instances[1];
- confirmMessage = `Are you sure you want to delete the default instance "${instanceName}"?\n\nThe next instance "${nextInstance.name || 'Unnamed'}" will become the new default.`;
- }
-
- const self = this;
- const doDelete = function() {
- console.log(`[huntarrUI] Deleting instance "${instanceName}" (index ${index}) from ${appType}...`);
-
- // Remove the instance from the local settings object
- settings.instances.splice(index, 1);
-
- // Update the global state immediately to ensure re-render uses fresh data
- if (window.huntarrUI && window.huntarrUI.originalSettings) {
- window.huntarrUI.originalSettings[appType] = JSON.parse(JSON.stringify(settings));
+ }).catch(function () { /* saveAppSettings already shows error */ });
}
- // Use a flag to indicate we're doing a structural change that needs full refresh
- window._appsSuppressChangeDetection = true;
+ // Update current editing state with new index (in case it was a new instance)
+ this._currentEditing = { appType, index: finalIndex, originalInstance: JSON.parse(JSON.stringify(newData)) };
+ _instanceEditorDirty = false;
- // Save to backend and trigger refresh
- self.saveAppSettings(appType, settings, `Instance "${instanceName}" deleted successfully`);
+ // Show or hide the stateful block (green box + reset button) and refresh state
+ const statefulBlock = document.getElementById('instance-editor-stateful-block');
+ if (statefulBlock) {
+ statefulBlock.style.display = newData.state_management_mode === 'disabled' ? 'none' : 'block';
+ }
+ if (newData.state_management_mode !== 'disabled') {
+ this.startStateStatusPolling(appType, finalIndex);
+ } else {
+ this.stopStateStatusPolling();
+ }
- // Force a small delay then clear suppression
- setTimeout(() => {
- window._appsSuppressChangeDetection = false;
- }, 800);
- };
+ // Disable save button to show it's saved
+ const saveBtn = document.getElementById('instance-editor-save');
+ if (saveBtn) {
+ saveBtn.disabled = true;
+ saveBtn.classList.remove('enabled');
+ }
- if (window.HuntarrConfirm && window.HuntarrConfirm.show) {
- window.HuntarrConfirm.show({
- title: 'Delete Instance',
- message: confirmMessage,
- confirmLabel: 'Delete',
- onConfirm: doDelete
- });
- } else {
- if (!confirm(confirmMessage)) return;
- doDelete();
- }
- },
+ // Show brief success feedback
+ const originalText = saveBtn ? saveBtn.innerHTML : '';
+ if (saveBtn) {
+ saveBtn.innerHTML = '
Saved!';
+ saveBtn.style.opacity = '0.7';
+ setTimeout(() => {
+ saveBtn.innerHTML = originalText;
+ saveBtn.style.opacity = '1';
+ if (navigateBack) {
+ this.cancelInstanceEditor(this._instanceEditorNextSection);
+ this._instanceEditorNextSection = null;
+ }
+ }, 2000);
+ } else if (navigateBack) {
+ this.cancelInstanceEditor(this._instanceEditorNextSection);
+ this._instanceEditorNextSection = null;
+ }
+
+ // Reset change detection by updating the original instance
+ // This allows the save button to be enabled again if user makes more changes
+ if (this._currentEditing) {
+ this._currentEditing.originalInstance = JSON.parse(JSON.stringify(newData));
+ }
+
+ // Stay on the editor page - don't navigate away unless navigateBack is true
+ },
+
+ // Cancel editing and return to app section (or settings-indexers for indexer)
+ cancelInstanceEditor: function (optionalNextSection) {
+ // Stop polling when leaving editor
+ this.stopStateStatusPolling();
+
+ if (optionalNextSection) {
+ window.huntarrUI.switchSection(optionalNextSection);
+ this._currentEditing = null;
+ _instanceEditorDirty = false;
+ this._updateHashForSection(optionalNextSection);
+ return;
+ }
+
+ if (!this._currentEditing) {
+ window.huntarrUI.switchSection('sonarr');
+ this._currentEditing = null;
+ _instanceEditorDirty = false;
+ this._updateHashForSection('sonarr');
+ return;
+ }
+ const appType = this._currentEditing.appType;
+ this._currentEditing = null;
+ _instanceEditorDirty = false;
+ if (appType === 'indexer') {
+ window.huntarrUI.switchSection('indexer-hunt');
+ this._updateHashForSection('indexer-hunt');
+ } else if (appType === 'client') {
+ window.huntarrUI.switchSection('settings-root-folders');
+ this._updateHashForSection('settings-root-folders');
+ } else {
+ window.huntarrUI.switchSection(appType);
+ this._updateHashForSection(appType);
+ }
+ },
+
+ _updateHashForSection: function (section) {
+ try {
+ const newUrl = (window.location.pathname || '') + (window.location.search || '') + '#' + section;
+ window.history.replaceState(null, '', newUrl);
+ } catch (e) { /* ignore */ }
+ },
+
+ // Open the modal for adding/editing an instance
+ openInstanceModal: function (appType, index = null) {
+ this.navigateToInstanceEditor(appType, index);
+ },
+
+ // Delete instance
+ deleteInstance: function (appType, index) {
+ const settings = window.huntarrUI.originalSettings[appType];
+ if (!settings || !settings.instances || settings.instances[index] === undefined) {
+ console.error(`[huntarrUI] Cannot delete instance: index ${index} not found for ${appType}`);
+ return;
+ }
+
+ const instanceName = settings.instances[index].name || 'Unnamed Instance';
+ const isDefault = index === 0;
+ const hasOtherInstances = settings.instances.length > 1;
+
+ // Custom confirmation message for default instance
+ let confirmMessage = `Are you sure you want to delete the instance "${instanceName}"?`;
+ if (isDefault && hasOtherInstances) {
+ const nextInstance = settings.instances[1];
+ confirmMessage = `Are you sure you want to delete the default instance "${instanceName}"?\n\nThe next instance "${nextInstance.name || 'Unnamed'}" will become the new default.`;
+ }
+
+ const self = this;
+ const doDelete = function () {
+ console.log(`[huntarrUI] Deleting instance "${instanceName}" (index ${index}) from ${appType}...`);
+
+ // Remove the instance from the local settings object
+ settings.instances.splice(index, 1);
+
+ // Update the global state immediately to ensure re-render uses fresh data
+ if (window.huntarrUI && window.huntarrUI.originalSettings) {
+ window.huntarrUI.originalSettings[appType] = JSON.parse(JSON.stringify(settings));
+ }
+
+ // Use a flag to indicate we're doing a structural change that needs full refresh
+ window._appsSuppressChangeDetection = true;
+
+ // Save to backend and trigger refresh
+ self.saveAppSettings(appType, settings, `Instance "${instanceName}" deleted successfully`);
+
+ // Force a small delay then clear suppression
+ setTimeout(() => {
+ window._appsSuppressChangeDetection = false;
+ }, 800);
+ };
+
+ if (window.HuntarrConfirm && window.HuntarrConfirm.show) {
+ window.HuntarrConfirm.show({
+ title: 'Delete Instance',
+ message: confirmMessage,
+ confirmLabel: 'Delete',
+ onConfirm: doDelete
+ });
+ } else {
+ if (!confirm(confirmMessage)) return;
+ doDelete();
+ }
+ },
});
})();
@@ -7567,7 +7567,7 @@ document.head.appendChild(styleEl);
* Separate from Client Management (clients.js). Attaches to window.SettingsForms.
* Load after settings/core.js and instance-editor.js.
*/
-(function() {
+(function () {
'use strict';
if (typeof window.SettingsForms === 'undefined') return;
@@ -7575,8 +7575,6 @@ document.head.appendChild(styleEl);
var CLIENT_TYPES = [
{ value: 'nzbhunt', label: 'NZB Hunt (Built-in)' },
- { value: 'nzbget', label: 'NZBGet' },
- { value: 'sabnzbd', label: 'SABnzbd' },
{ value: 'torhunt', label: 'Tor Hunt (Built-in)' },
{ value: 'qbittorrent', label: 'qBittorrent' }
];
@@ -7589,13 +7587,13 @@ document.head.appendChild(styleEl);
{ value: 'low', label: 'Low' }
];
- Forms.openClientEditor = function(isAdd, index, instance) {
+ Forms.openClientEditor = function (isAdd, index, instance) {
const inst = instance || {};
this._currentEditing = { appType: 'client', index: index, isAdd: isAdd, originalInstance: JSON.parse(JSON.stringify(inst)) };
- const typeRaw = (inst.type || 'nzbget').toLowerCase().trim();
- const typeVal = CLIENT_TYPES.some(function(o) { return o.value === typeRaw; }) ? typeRaw : 'nzbget';
- const clientDisplayName = (CLIENT_TYPES.find(function(o) { return o.value === typeVal; }) || { label: typeVal }).label;
+ const typeRaw = (inst.type || 'nzbhunt').toLowerCase().trim();
+ const typeVal = CLIENT_TYPES.some(function (o) { return o.value === typeRaw; }) ? typeRaw : 'nzbhunt';
+ const clientDisplayName = (CLIENT_TYPES.find(function (o) { return o.value === typeVal; }) || { label: typeVal }).label;
const titleEl = document.getElementById('instance-editor-title');
if (titleEl) {
@@ -7617,7 +7615,7 @@ document.head.appendChild(styleEl);
const enabledSelect = document.getElementById('editor-client-enabled');
const enableIcon = document.getElementById('client-enable-status-icon');
if (enabledSelect && enableIcon) {
- enabledSelect.addEventListener('change', function() {
+ enabledSelect.addEventListener('change', function () {
const isEnabled = enabledSelect.value === 'true';
enableIcon.className = isEnabled ? 'fas fa-check-circle' : 'fas fa-minus-circle';
enableIcon.style.color = isEnabled ? '#10b981' : '#ef4444';
@@ -7632,14 +7630,14 @@ document.head.appendChild(styleEl);
const apiKeyEl = document.getElementById('editor-client-apikey');
const usernameEl = document.getElementById('editor-client-username');
const passwordEl = document.getElementById('editor-client-password');
-
+
if (hostEl) hostEl.addEventListener('input', () => this.checkClientConnection());
if (portEl) portEl.addEventListener('input', () => this.checkClientConnection());
if (apiKeyEl) apiKeyEl.addEventListener('input', () => this.checkClientConnection());
if (usernameEl) usernameEl.addEventListener('input', () => this.checkClientConnection());
if (passwordEl) passwordEl.addEventListener('input', () => this.checkClientConnection());
}
-
+
// Initial connection check (skip for NZB Hunt and Tor Hunt - built-in, no status needed)
if (typeVal !== 'nzbhunt' && typeVal !== 'torhunt') {
this.checkClientConnection();
@@ -7650,19 +7648,19 @@ document.head.appendChild(styleEl);
}
};
- Forms.generateClientEditorHtml = function(instance) {
+ Forms.generateClientEditorHtml = function (instance) {
const name = (instance.name || '').replace(//g, '>').replace(/"/g, '"');
- const typeRaw = (instance.type || 'nzbget').toLowerCase().trim();
- const typeVal = CLIENT_TYPES.some(function(o) { return o.value === typeRaw; }) ? typeRaw : 'nzbget';
+ const typeRaw = (instance.type || 'nzbhunt').toLowerCase().trim();
+ const typeVal = CLIENT_TYPES.some(function (o) { return o.value === typeRaw; }) ? typeRaw : 'nzbhunt';
const isQBit = typeVal === 'qbittorrent';
const isTorHunt = typeVal === 'torhunt' || typeVal === 'tor_hunt';
const host = (instance.host || '').replace(//g, '>').replace(/"/g, '"');
- const defaultPort = typeVal === 'nzbget' ? '6789' : (isQBit ? '8080' : '8080');
+ const defaultPort = isQBit ? '8080' : '8080';
const port = instance.port !== undefined && instance.port !== '' ? String(instance.port) : defaultPort;
const username = (instance.username || '').replace(//g, '>').replace(/"/g, '"');
const enabled = instance.enabled !== false;
const isEdit = !!(instance.name && instance.name.trim());
-
+
const apiKeyPlaceholder = isEdit && (instance.api_key_last4 || '')
? ('Enter new key or leave blank to keep existing (••••' + (instance.api_key_last4 || '') + ')')
: 'Enter API key';
@@ -7677,10 +7675,10 @@ document.head.appendChild(styleEl);
let clientPriority = parseInt(instance.client_priority, 10);
if (isNaN(clientPriority) || clientPriority < 1 || clientPriority > 99) clientPriority = 50;
- const recentOptionsHtml = PRIORITY_OPTIONS.map(function(o) {
+ const recentOptionsHtml = PRIORITY_OPTIONS.map(function (o) {
return '
' + o.label + ' ';
}).join('');
- const olderOptionsHtml = PRIORITY_OPTIONS.map(function(o) {
+ const olderOptionsHtml = PRIORITY_OPTIONS.map(function (o) {
return '
' + o.label + ' ';
}).join('');
@@ -7753,7 +7751,7 @@ document.head.appendChild(styleEl);
${!isNzbHunt && !isTorHunt ? `
` : ''}
@@ -7765,7 +7763,7 @@ document.head.appendChild(styleEl);
Port
-
Port number for your download client (SABnzbd default: 8080, NZBGet default: 6789, qBittorrent default: 8080)
+
Port number for your download client (qBittorrent default: 8080)
API Key
@@ -7775,7 +7773,7 @@ document.head.appendChild(styleEl);
Username
-
Username for basic authentication (NZBGet typically requires this)
+
Username for basic authentication
Password
@@ -7812,7 +7810,7 @@ document.head.appendChild(styleEl);
`;
};
- Forms.saveClientFromEditor = function() {
+ Forms.saveClientFromEditor = function () {
if (!this._currentEditing || this._currentEditing.appType !== 'client') return;
const nameEl = document.getElementById('editor-client-name');
const hostEl = document.getElementById('editor-client-host');
@@ -7828,13 +7826,13 @@ document.head.appendChild(styleEl);
const type = (this._currentEditing && this._currentEditing.originalInstance && this._currentEditing.originalInstance.type)
? String(this._currentEditing.originalInstance.type).trim().toLowerCase()
- : 'nzbget';
+ : 'nzbhunt';
const isNzbHuntType = (type === 'nzbhunt' || type === 'nzb_hunt');
const isQBitType = (type === 'qbittorrent');
const isTorHuntType = (type === 'torhunt' || type === 'tor_hunt');
const name = (isNzbHuntType || isTorHuntType) ? (isNzbHuntType ? 'NZB Hunt' : 'Tor Hunt') : (nameEl ? nameEl.value.trim() : '');
const host = hostEl ? hostEl.value.trim() : '';
- const portDefault = type === 'nzbget' ? 6789 : 8080;
+ const portDefault = 8080;
let port = portDefault;
if (portEl && portEl.value.trim() !== '') {
const p = parseInt(portEl.value, 10);
@@ -7885,10 +7883,10 @@ document.head.appendChild(styleEl);
const method = isAdd ? 'POST' : 'PUT';
fetch(url, { method: method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) })
- .then(function(r) {
- return r.json().then(function(data) { return { ok: r.ok, data: data }; });
+ .then(function (r) {
+ return r.json().then(function (data) { return { ok: r.ok, data: data }; });
})
- .then(function(result) {
+ .then(function (result) {
if (!result.ok) {
var msg = (result.data && result.data.error) ? result.data.error : 'Save failed';
if (window.huntarrUI && window.huntarrUI.showNotification) {
@@ -7919,57 +7917,57 @@ document.head.appendChild(styleEl);
}
}
})
- .catch(function(err) {
+ .catch(function (err) {
if (window.huntarrUI && window.huntarrUI.showNotification) {
window.huntarrUI.showNotification(err.message || 'Failed to save client', 'error');
}
});
};
- Forms.checkClientConnection = function() {
+ Forms.checkClientConnection = function () {
const container = document.getElementById('client-connection-status-container');
const hostEl = document.getElementById('editor-client-host');
const portEl = document.getElementById('editor-client-port');
const apiKeyEl = document.getElementById('editor-client-apikey');
const usernameEl = document.getElementById('editor-client-username');
const passwordEl = document.getElementById('editor-client-password');
-
+
if (!container) return;
-
+
container.style.display = 'flex';
container.style.justifyContent = 'flex-end';
-
+
// Get client type
const type = (this._currentEditing && this._currentEditing.originalInstance && this._currentEditing.originalInstance.type)
? String(this._currentEditing.originalInstance.type).trim().toLowerCase()
- : 'nzbget';
-
+ : 'nzbhunt';
+
// NZB Hunt and Tor Hunt (built-in) - no connection status; managed in their own Settings
if (type === 'nzbhunt' || type === 'nzb_hunt' || type === 'torhunt' || type === 'tor_hunt') {
if (container) container.style.display = 'none';
return;
}
-
+
const host = hostEl ? hostEl.value.trim() : '';
const port = portEl ? portEl.value.trim() : '';
const apiKey = apiKeyEl ? apiKeyEl.value.trim() : '';
const username = usernameEl ? usernameEl.value.trim() : '';
const password = passwordEl ? passwordEl.value.trim() : '';
-
+
// Check if minimum requirements are met
if (!host || !port) {
container.innerHTML = '
Enter host and port ';
return;
}
-
+
// Show checking status
container.innerHTML = '
Checking... ';
-
+
// Test connection
fetch('./api/clients/test-connection', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
+ body: JSON.stringify({
type: type,
host: host,
port: parseInt(port, 10) || 8080,
@@ -7978,18 +7976,18 @@ document.head.appendChild(styleEl);
password: password
})
})
- .then(function(r) { return r.json().then(function(data) { return { ok: r.ok, data: data }; }); })
- .then(function(result) {
- const data = result.data || {};
- if (data.success === true) {
- container.innerHTML = '
Connected ';
- } else {
- container.innerHTML = '
' + (data.message || data.error || 'Connection failed') + ' ';
- }
- })
- .catch(function(err) {
- container.innerHTML = '
' + (err.message || 'Connection failed') + ' ';
- });
+ .then(function (r) { return r.json().then(function (data) { return { ok: r.ok, data: data }; }); })
+ .then(function (result) {
+ const data = result.data || {};
+ if (data.success === true) {
+ container.innerHTML = '
Connected ';
+ } else {
+ container.innerHTML = '
' + (data.message || data.error || 'Connection failed') + ' ';
+ }
+ })
+ .catch(function (err) {
+ container.innerHTML = '
' + (err.message || 'Connection failed') + ' ';
+ });
};
})();
@@ -8000,15 +7998,15 @@ document.head.appendChild(styleEl);
* Separate from Client Editor (client-editor.js). Attaches to window.SettingsForms.
* Load after client-editor.js so openClientEditor is available for grid clicks.
*/
-(function() {
+(function () {
'use strict';
if (typeof window.SettingsForms === 'undefined') return;
const Forms = window.SettingsForms;
- Forms.renderClientCard = function(client, index) {
+ Forms.renderClientCard = function (client, index) {
const name = (client.name || 'Unnamed').replace(//g, '>');
- const type = (client.type || 'nzbget').replace(/"/g, '"');
+ const type = (client.type || 'nzbhunt').replace(/"/g, '"');
const isNzbHunt = type === 'nzbhunt';
const isQBit = type === 'qbittorrent';
const isTorHunt = type === 'torhunt' || type === 'tor_hunt';
@@ -8016,7 +8014,7 @@ document.head.appendChild(styleEl);
const statusClass = enabled ? 'status-connected' : 'status-error';
const statusIcon = enabled ? 'fa-check-circle' : 'fa-minus-circle';
const priority = client.client_priority !== undefined && client.client_priority !== null ? Number(client.client_priority) : 50;
-
+
var bodyHtml;
if (isNzbHunt) {
bodyHtml = '
Built-in Client
' +
@@ -8032,7 +8030,7 @@ document.head.appendChild(styleEl);
bodyHtml = '
••••••••' + last4 + '
' +
'
' + (client.host || '').replace(/
';
}
-
+
return '
' +
'
';
};
- Forms.refreshClientsList = function() {
+ Forms.refreshClientsList = function () {
// Refresh NZB Hunt sidebar group visibility whenever client list changes
if (window.huntarrUI && typeof window.huntarrUI._refreshNzbHuntSidebarGroup === 'function') {
window.huntarrUI._refreshNzbHuntSidebarGroup();
@@ -8057,12 +8055,12 @@ document.head.appendChild(styleEl);
function _doRefreshClientsList(grid) {
fetch('./api/clients')
- .then(function(r) { return r.json(); })
- .then(function(data) {
+ .then(function (r) { return r.json(); })
+ .then(function (data) {
const list = (data && data.clients) ? data.clients : [];
window.SettingsForms._clientsList = list;
- var withIndex = list.map(function(c, i) { return { client: c, originalIndex: i }; });
- withIndex.sort(function(a, b) {
+ var withIndex = list.map(function (c, i) { return { client: c, originalIndex: i }; });
+ withIndex.sort(function (a, b) {
var pa = Number(a.client.client_priority) || 50;
var pb = Number(b.client.client_priority) || 50;
if (pa !== pb) return pa - pb;
@@ -8074,9 +8072,14 @@ document.head.appendChild(styleEl);
for (var i = 0; i < withIndex.length; i++) {
html += window.SettingsForms.renderClientCard(withIndex[i].client, withIndex[i].originalIndex);
}
- html += '
';
+ var hasNzbHunt = list.some(function (c) { return (c.type || '').toLowerCase() === 'nzbhunt'; });
+ var hasTorHunt = list.some(function (c) { var t = (c.type || '').toLowerCase(); return t === 'torhunt' || t === 'tor_hunt'; });
+ var allClientsAdded = hasNzbHunt && hasTorHunt;
+ if (!allClientsAdded) {
+ html += '
';
+ }
grid.innerHTML = html;
-
+
// Also refresh remote mappings if available
if (window.RemoteMappings && typeof window.RemoteMappings.refreshList === 'function') {
window.RemoteMappings.refreshList();
@@ -8084,17 +8087,17 @@ document.head.appendChild(styleEl);
// Dispatch event so UI can react to client list changes
document.dispatchEvent(new CustomEvent('huntarr:clients-list-updated', { detail: { clients: list } }));
})
- .catch(function() {
+ .catch(function () {
grid.innerHTML = '
';
});
}
- document.addEventListener('huntarr:instances-changed', function() {
+ document.addEventListener('huntarr:instances-changed', function () {
if (document.getElementById('settings-clients-content-wrapper') && window.huntarrUI && window.huntarrUI.currentSection === 'settings-clients') {
Forms.refreshClientsList();
}
});
- document.addEventListener('huntarr:tv-hunt-instances-changed', function() {
+ document.addEventListener('huntarr:tv-hunt-instances-changed', function () {
if (document.getElementById('settings-clients-content-wrapper') && window.huntarrUI && window.huntarrUI.currentSection === 'settings-clients') {
Forms.refreshClientsList();
}
@@ -12391,7 +12394,7 @@ document.head.appendChild(styleEl);
* Media Hunt Instance Editor – unified Movie + TV per-instance hunt settings.
* Part 1: MovieHuntInstanceEditor (movie mode). Uses media-hunt-instance-editor-* container IDs.
*/
-(function() {
+(function () {
'use strict';
var baseUrl = (typeof window !== 'undefined' && window.HUNTARR_BASE_URL) ? window.HUNTARR_BASE_URL.replace(/\/$/, '') : '';
@@ -12449,7 +12452,7 @@ document.head.appendChild(styleEl);
var upgradeTagGroupDisplay = (safe.upgrade_selection_method || 'cutoff') === 'tags' ? 'flex' : 'none';
var statefulBlockDisplay = safe.state_management_mode === 'disabled' ? 'none' : 'block';
- var exemptTagsHtml = (safe.exempt_tags || []).map(function(tag) {
+ var exemptTagsHtml = (safe.exempt_tags || []).map(function (tag) {
return '
' +
'× ' + escapeHtml(tag) + ' ';
}).join('');
@@ -12480,7 +12483,7 @@ document.head.appendChild(styleEl);
'
Stable identifier for this instance (assigned automatically; cannot be changed)
' +
'
Category Name ' +
'
' +
- '
For NZB Hunt this is automatic. SABNZBD and NZBGet require this exact category to be configured.
' +
+ '
For NZB Hunt this is automatic. External clients (e.g. qBittorrent) require this exact category to be configured.
' +
'
' +
'
' +
'
' +
@@ -12573,13 +12576,13 @@ document.head.appendChild(styleEl);
}
function collectFormData() {
- var get = function(id) { var el = document.getElementById(id); return el ? el.value : null; };
- var getNum = function(id, def) { var v = get(id); if (v === null || v === '') return def; var n = parseInt(v, 10); return isNaN(n) ? def : n; };
- var getCheck = function(id) { var el = document.getElementById(id); return el ? !!el.checked : false; };
+ var get = function (id) { var el = document.getElementById(id); return el ? el.value : null; };
+ var getNum = function (id, def) { var v = get(id); if (v === null || v === '') return def; var n = parseInt(v, 10); return isNaN(n) ? def : n; };
+ var getCheck = function (id) { var el = document.getElementById(id); return el ? !!el.checked : false; };
var tags = [];
var list = document.getElementById('mh-editor-exempt-tags-list');
if (list) {
- list.querySelectorAll('.exempt-tag-chip').forEach(function(chip) {
+ list.querySelectorAll('.exempt-tag-chip').forEach(function (chip) {
var t = chip.getAttribute('data-tag');
if (t) tags.push(t);
});
@@ -12641,8 +12644,8 @@ document.head.appendChild(styleEl);
if (saveBtn) { saveBtn.disabled = false; saveBtn.classList.add('enabled'); }
}
addBtn.addEventListener('click', addTag);
- input.addEventListener('keydown', function(e) { if (e.key === 'Enter') { e.preventDefault(); addTag(); } });
- list.addEventListener('click', function(e) {
+ input.addEventListener('keydown', function (e) { if (e.key === 'Enter') { e.preventDefault(); addTag(); } });
+ list.addEventListener('click', function (e) {
var remove = e.target.classList.contains('exempt-tag-remove') ? e.target : e.target.closest('.exempt-tag-remove');
if (remove) {
var chip = remove.closest('.exempt-tag-chip');
@@ -12668,11 +12671,11 @@ document.head.appendChild(styleEl);
container.addEventListener('change', markDirty);
var stateMode = document.getElementById('mh-editor-state-mode');
var upgradeMethod = document.getElementById('mh-editor-upgrade-method');
- if (stateMode) stateMode.addEventListener('change', function() {
+ if (stateMode) stateMode.addEventListener('change', function () {
var block = document.getElementById('mh-editor-stateful-block');
if (block) block.style.display = stateMode.value === 'disabled' ? 'none' : 'block';
});
- if (upgradeMethod) upgradeMethod.addEventListener('change', function() {
+ if (upgradeMethod) upgradeMethod.addEventListener('change', function () {
var group = container.querySelector('.editor-upgrade-tag-group');
if (group) group.style.display = upgradeMethod.value === 'tags' ? 'flex' : 'none';
var upgradeItemsSection = container.querySelector('.mh-editor-upgrade-items-tag-section');
@@ -12692,7 +12695,7 @@ document.head.appendChild(styleEl);
var statusPill = container ? container.querySelector('.mh-info-status-pill') : null;
var enabledIconEl = document.getElementById('mh-editor-enabled-icon');
if (enabledSelect && statusPill) {
- enabledSelect.addEventListener('change', function() {
+ enabledSelect.addEventListener('change', function () {
var on = enabledSelect.value === 'true';
statusPill.className = 'mh-info-status-pill ' + (on ? 'mh-info-status-enabled' : 'mh-info-status-disabled');
statusPill.innerHTML = on ? '
Enabled' : 'Disabled';
@@ -12709,33 +12712,33 @@ document.head.appendChild(styleEl);
if (!countEl || !nextEl || !instanceName) return;
var url = api('./api/stateful/summary?app_type=movie_hunt&instance_name=' + encodeURIComponent(instanceName));
fetch(url, { cache: 'no-store' })
- .then(function(r) { return r.json(); })
- .then(function(data) {
+ .then(function (r) { return r.json(); })
+ .then(function (data) {
countEl.textContent = (data && data.processed_count !== undefined) ? data.processed_count : 0;
nextEl.textContent = (data && data.next_reset_time) ? data.next_reset_time : 'N/A';
})
- .catch(function() {
+ .catch(function () {
countEl.textContent = '0';
nextEl.textContent = 'N/A';
});
}
- var addInstanceCardHtml = function(appType, iconClass, label) {
+ var addInstanceCardHtml = function (appType, iconClass, label) {
return '
' + (label || 'Add Instance') + '
';
};
window.MovieHuntInstanceEditor = {
- loadInstanceList: function() {
+ loadInstanceList: function () {
var grid = document.getElementById('movie-hunt-settings-instances-grid');
if (!grid) return;
grid.innerHTML = '
Loading...
';
fetch(api('./api/movie-hunt/instances'), { cache: 'no-store' })
- .then(function(r) { return r.json(); })
- .then(function(data) {
+ .then(function (r) { return r.json(); })
+ .then(function (data) {
var list = data.instances || [];
var currentId = (data.current_instance_id != null) ? parseInt(data.current_instance_id, 10) : (list[0] ? list[0].id : null);
grid.innerHTML = '';
- list.forEach(function(inst) {
+ list.forEach(function (inst) {
var enabled = inst.enabled !== false;
var statusClass = enabled ? 'status-connected' : 'status-disabled';
var statusIcon = enabled ? 'fa-check-circle' : 'fa-minus-circle';
@@ -12756,8 +12759,8 @@ document.head.appendChild(styleEl);
var addCard = document.createElement('div');
addCard.innerHTML = addInstanceCardHtml('media-hunt-instance-movie', 'fa-film', 'Add Movie Instance');
grid.appendChild(addCard.firstElementChild);
- grid.querySelectorAll('.btn-card.edit').forEach(function(btn) {
- btn.addEventListener('click', function(e) {
+ grid.querySelectorAll('.btn-card.edit').forEach(function (btn) {
+ btn.addEventListener('click', function (e) {
e.stopPropagation();
window.MovieHuntInstanceEditor.openEditor(
btn.getAttribute('data-id'),
@@ -12765,29 +12768,29 @@ document.head.appendChild(styleEl);
);
});
});
- grid.querySelectorAll('.btn-card.set-default').forEach(function(btn) {
- btn.addEventListener('click', function(e) {
+ grid.querySelectorAll('.btn-card.set-default').forEach(function (btn) {
+ btn.addEventListener('click', function (e) {
e.stopPropagation();
window.MovieHuntInstanceEditor.setDefault(btn.getAttribute('data-id'));
});
});
- grid.querySelectorAll('.btn-card.delete').forEach(function(btn) {
- btn.addEventListener('click', function(e) {
+ grid.querySelectorAll('.btn-card.delete').forEach(function (btn) {
+ btn.addEventListener('click', function (e) {
e.stopPropagation();
var name = btn.getAttribute('data-name') || ('Instance ' + btn.getAttribute('data-id'));
- var doDelete = function() { window.MovieHuntInstanceEditor.deleteInstance(btn.getAttribute('data-id')); };
+ var doDelete = function () { window.MovieHuntInstanceEditor.deleteInstance(btn.getAttribute('data-id')); };
if (window.HuntarrConfirm && window.HuntarrConfirm.show) {
window.HuntarrConfirm.show({ title: 'Delete Instance', message: 'Delete Movie Hunt instance "' + (name || '') + '"? All settings and collection data for this instance will be permanently removed.', confirmLabel: 'Delete', onConfirm: doDelete });
} else if (confirm('Delete "' + name + '"? This cannot be undone.')) { doDelete(); }
});
});
})
- .catch(function() {
+ .catch(function () {
grid.innerHTML = '
Failed to load instances.
';
});
},
- setDefault: function(instanceId) {
+ setDefault: function (instanceId) {
if (!instanceId) return;
var self = this;
fetch(api('./api/movie-hunt/instances/current'), {
@@ -12795,8 +12798,8 @@ document.head.appendChild(styleEl);
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ instance_id: parseInt(instanceId, 10) })
})
- .then(function(r) { return r.json(); })
- .then(function(data) {
+ .then(function (r) { return r.json(); })
+ .then(function (data) {
if (data.success) {
if (window.huntarrUI && window.huntarrUI.showNotification) window.huntarrUI.showNotification('Default instance updated', 'success');
self.loadInstanceList();
@@ -12804,17 +12807,17 @@ document.head.appendChild(styleEl);
window.huntarrUI.showNotification(data.error, 'error');
}
})
- .catch(function() {
+ .catch(function () {
if (window.huntarrUI && window.huntarrUI.showNotification) window.huntarrUI.showNotification('Failed to set default instance', 'error');
});
},
- deleteInstance: function(instanceId) {
+ deleteInstance: function (instanceId) {
if (!instanceId) return;
var self = this;
fetch(api('./api/movie-hunt/instances/' + instanceId), { method: 'DELETE' })
- .then(function(r) { return r.json(); })
- .then(function(data) {
+ .then(function (r) { return r.json(); })
+ .then(function (data) {
if (data.success) {
if (window.huntarrUI && window.huntarrUI.showNotification) window.huntarrUI.showNotification('Instance deleted', 'success');
self.loadInstanceList();
@@ -12822,21 +12825,21 @@ document.head.appendChild(styleEl);
window.huntarrUI.showNotification(data.error || 'Failed to delete', 'error');
}
})
- .catch(function() {
+ .catch(function () {
if (window.huntarrUI && window.huntarrUI.showNotification) window.huntarrUI.showNotification('Failed to delete instance', 'error');
});
},
- openEditor: function(instanceId, instanceName) {
+ openEditor: function (instanceId, instanceName) {
_currentInstanceId = instanceId;
_currentInstanceName = instanceName || ('Instance ' + instanceId);
_editorDirty = false;
var self = this;
fetch(api('./api/movie-hunt/instances/' + instanceId + '/settings'), { cache: 'no-store' })
- .then(function(r) {
- return r.json().then(function(data) { return { ok: r.ok, data: data }; });
+ .then(function (r) {
+ return r.json().then(function (data) { return { ok: r.ok, data: data }; });
})
- .then(function(result) {
+ .then(function (result) {
if (!result.ok || result.data.error) {
var msg = (result.data && result.data.error) ? result.data.error : 'Failed to load settings';
if (window.huntarrUI && window.huntarrUI.showNotification) {
@@ -12861,7 +12864,7 @@ document.head.appendChild(styleEl);
if (appIcon) appIcon.className = 'fas fa-film';
var backBtn = document.getElementById('media-hunt-instance-editor-back');
var saveBtn = document.getElementById('media-hunt-instance-editor-save');
- if (backBtn) backBtn.onclick = function() {
+ if (backBtn) backBtn.onclick = function () {
if (!_editorDirty) {
window.huntarrUI.switchSection('media-hunt-instances');
return;
@@ -12872,10 +12875,10 @@ document.head.appendChild(styleEl);
message: 'You have unsaved changes that will be lost if you leave.',
confirmLabel: 'Go Back',
cancelLabel: 'Leave',
- onConfirm: function() {
+ onConfirm: function () {
// Stay on the editor — modal just closes, user can save manually
},
- onCancel: function() { window.huntarrUI.switchSection('media-hunt-instances'); }
+ onCancel: function () { window.huntarrUI.switchSection('media-hunt-instances'); }
});
} else {
if (confirm('You have unsaved changes that will be lost. Leave anyway?')) {
@@ -12883,9 +12886,9 @@ document.head.appendChild(styleEl);
}
}
};
- if (saveBtn) saveBtn.onclick = function() { self.saveEditor(); };
+ if (saveBtn) saveBtn.onclick = function () { self.saveEditor(); };
var resetBtn = document.getElementById('mh-editor-reset-state');
- if (resetBtn) resetBtn.onclick = function() { self.resetState(instanceId); };
+ if (resetBtn) resetBtn.onclick = function () { self.resetState(instanceId); };
// Debug Manager: Reset Media Collection
self.setupResetCollectionModal(instanceId, _currentInstanceName);
@@ -12894,14 +12897,14 @@ document.head.appendChild(styleEl);
window.huntarrUI.switchSection('movie-hunt-instance-editor');
}
})
- .catch(function(err) {
+ .catch(function (err) {
if (window.huntarrUI && window.huntarrUI.showNotification) {
window.huntarrUI.showNotification('Failed to load settings: ' + (err.message || 'Request failed'), 'error');
}
});
},
- saveEditor: function() {
+ saveEditor: function () {
if (!_currentInstanceId) return;
var payload = collectFormData();
var saveBtn = document.getElementById('media-hunt-instance-editor-save');
@@ -12912,8 +12915,8 @@ document.head.appendChild(styleEl);
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
})
- .then(function(r) { return r.json(); })
- .then(function(data) {
+ .then(function (r) { return r.json(); })
+ .then(function (data) {
if (data.error) {
if (window.huntarrUI && window.huntarrUI.showNotification) window.huntarrUI.showNotification(data.error, 'error');
if (saveBtn) { saveBtn.disabled = false; saveBtn.innerHTML = '
Save'; saveBtn.classList.add('enabled'); }
@@ -12923,33 +12926,33 @@ document.head.appendChild(styleEl);
if (saveBtn) {
saveBtn.innerHTML = '
Saved!';
saveBtn.classList.remove('enabled');
- setTimeout(function() {
+ setTimeout(function () {
saveBtn.innerHTML = '
Save';
saveBtn.disabled = true;
}, 2000);
}
})
- .catch(function() {
+ .catch(function () {
if (window.huntarrUI && window.huntarrUI.showNotification) window.huntarrUI.showNotification('Failed to save settings', 'error');
if (saveBtn) { saveBtn.disabled = false; saveBtn.innerHTML = '
Save'; saveBtn.classList.add('enabled'); }
});
},
- resetState: function(instanceId) {
+ resetState: function (instanceId) {
if (!instanceId) return;
function doReset() {
fetch(api('./api/movie-hunt/instances/' + instanceId + '/reset-state'), { method: 'POST' })
- .then(function(r) { return r.json(); })
- .then(function(data) {
- if (data.error && window.huntarrUI && window.huntarrUI.showNotification) {
- window.huntarrUI.showNotification(data.error, 'error');
- } else if (window.huntarrUI && window.huntarrUI.showNotification) {
- window.huntarrUI.showNotification('State reset.', 'success');
- }
- })
- .catch(function() {
- if (window.huntarrUI && window.huntarrUI.showNotification) window.huntarrUI.showNotification('Reset request failed', 'error');
- });
+ .then(function (r) { return r.json(); })
+ .then(function (data) {
+ if (data.error && window.huntarrUI && window.huntarrUI.showNotification) {
+ window.huntarrUI.showNotification(data.error, 'error');
+ } else if (window.huntarrUI && window.huntarrUI.showNotification) {
+ window.huntarrUI.showNotification('State reset.', 'success');
+ }
+ })
+ .catch(function () {
+ if (window.huntarrUI && window.huntarrUI.showNotification) window.huntarrUI.showNotification('Reset request failed', 'error');
+ });
}
if (window.HuntarrConfirm && window.HuntarrConfirm.show) {
window.HuntarrConfirm.show({
@@ -12964,7 +12967,7 @@ document.head.appendChild(styleEl);
}
},
- setupResetCollectionModal: function(instanceId, instanceName) {
+ setupResetCollectionModal: function (instanceId, instanceName) {
var resetBtn = document.getElementById('mh-editor-reset-collection');
var modal = document.getElementById('mh-reset-collection-modal');
var backdrop = document.getElementById('mh-reset-collection-backdrop');
@@ -12995,14 +12998,14 @@ document.head.appendChild(styleEl);
// Enable/disable confirm button based on input match
if (input && confirmBtn) {
- input.addEventListener('input', function() {
+ input.addEventListener('input', function () {
var val = (input.value || '').trim();
var match = val === expectedName;
confirmBtn.disabled = !match;
confirmBtn.style.opacity = match ? '1' : '0.5';
if (errorEl) { errorEl.style.display = 'none'; }
});
- input.addEventListener('keydown', function(e) {
+ input.addEventListener('keydown', function (e) {
if (e.key === 'Enter' && !confirmBtn.disabled) {
confirmBtn.click();
}
@@ -13010,7 +13013,7 @@ document.head.appendChild(styleEl);
}
if (confirmBtn) {
- confirmBtn.onclick = function() {
+ confirmBtn.onclick = function () {
var val = (input ? input.value : '').trim();
if (val !== expectedName) {
if (errorEl) {
@@ -13021,7 +13024,7 @@ document.head.appendChild(styleEl);
}
confirmBtn.disabled = true;
confirmBtn.innerHTML = '
Deleting...';
- self.resetCollection(instanceId, function(success) {
+ self.resetCollection(instanceId, function (success) {
if (success) {
closeModal();
} else {
@@ -13033,12 +13036,12 @@ document.head.appendChild(styleEl);
}
},
- resetCollection: function(instanceId, callback) {
+ resetCollection: function (instanceId, callback) {
fetch(api('./api/movie-hunt/instances/' + instanceId + '/reset-collection'), {
method: 'DELETE'
})
- .then(function(r) { return r.json(); })
- .then(function(data) {
+ .then(function (r) { return r.json(); })
+ .then(function (data) {
if (data.success) {
if (window.huntarrUI && window.huntarrUI.showNotification) {
window.huntarrUI.showNotification(data.message || 'Media collection has been reset.', 'success');
@@ -13052,7 +13055,7 @@ document.head.appendChild(styleEl);
if (callback) callback(false);
}
})
- .catch(function(err) {
+ .catch(function (err) {
var msg = (err && err.message) ? err.message : 'Request failed.';
if (window.huntarrUI && window.huntarrUI.showNotification) {
window.huntarrUI.showNotification(msg, 'error');
@@ -13067,7 +13070,7 @@ document.head.appendChild(styleEl);
* Media Hunt Instance Editor – Part 2: TVHuntInstanceEditor (TV mode).
* Uses same media-hunt-instance-editor-* container IDs.
*/
-(function() {
+(function () {
'use strict';
var baseUrl = (typeof window !== 'undefined' && window.HUNTARR_BASE_URL) ? window.HUNTARR_BASE_URL.replace(/\/$/, '') : '';
@@ -13111,7 +13114,7 @@ document.head.appendChild(styleEl);
var infoStatusClass = safe.enabled ? 'th-info-status-enabled' : 'th-info-status-disabled';
var infoStatusText = safe.enabled ? 'Enabled' : 'Disabled';
- var exemptTagsHtml = (safe.exempt_tags || []).map(function(tag) {
+ var exemptTagsHtml = (safe.exempt_tags || []).map(function (tag) {
return '
' +
'× ' + escapeHtml(tag) + ' ';
}).join('');
@@ -13124,7 +13127,7 @@ document.head.appendChild(styleEl);
'
Enable Status Enabled Disabled
Enable or disable this instance
' +
'
' +
'
' +
- '
' +
+ '
' +
'
' +
// SEARCH SETTINGS
'
' +
@@ -13169,12 +13172,12 @@ document.head.appendChild(styleEl);
}
function collectFormData() {
- var get = function(id) { var el = document.getElementById(id); return el ? el.value : null; };
- var getNum = function(id, def) { var v = get(id); if (v === null || v === '') return def; var n = parseInt(v, 10); return isNaN(n) ? def : n; };
- var getCheck = function(id) { var el = document.getElementById(id); return el ? !!el.checked : false; };
+ var get = function (id) { var el = document.getElementById(id); return el ? el.value : null; };
+ var getNum = function (id, def) { var v = get(id); if (v === null || v === '') return def; var n = parseInt(v, 10); return isNaN(n) ? def : n; };
+ var getCheck = function (id) { var el = document.getElementById(id); return el ? !!el.checked : false; };
var tags = [];
var list = document.getElementById('th-editor-exempt-tags-list');
- if (list) list.querySelectorAll('.exempt-tag-chip').forEach(function(chip) { var t = chip.getAttribute('data-tag'); if (t) tags.push(t); });
+ if (list) list.querySelectorAll('.exempt-tag-chip').forEach(function (chip) { var t = chip.getAttribute('data-tag'); if (t) tags.push(t); });
var enabledVal = get('th-editor-enabled');
return {
enabled: enabledVal === 'true',
@@ -13219,8 +13222,8 @@ document.head.appendChild(styleEl);
if (saveBtn) { saveBtn.disabled = false; saveBtn.classList.add('enabled'); }
}
addBtn.addEventListener('click', addTag);
- input.addEventListener('keydown', function(e) { if (e.key === 'Enter') { e.preventDefault(); addTag(); } });
- list.addEventListener('click', function(e) {
+ input.addEventListener('keydown', function (e) { if (e.key === 'Enter') { e.preventDefault(); addTag(); } });
+ list.addEventListener('click', function (e) {
var remove = e.target.classList.contains('exempt-tag-remove') ? e.target : e.target.closest('.exempt-tag-remove');
if (remove) {
var chip = remove.closest('.exempt-tag-chip');
@@ -13242,11 +13245,11 @@ document.head.appendChild(styleEl);
container.addEventListener('change', markDirty);
var stateMode = document.getElementById('th-editor-state-mode');
var upgradeMethod = document.getElementById('th-editor-upgrade-method');
- if (stateMode) stateMode.addEventListener('change', function() {
+ if (stateMode) stateMode.addEventListener('change', function () {
var block = document.getElementById('th-editor-stateful-block');
if (block) block.style.display = stateMode.value === 'disabled' ? 'none' : 'block';
});
- if (upgradeMethod) upgradeMethod.addEventListener('change', function() {
+ if (upgradeMethod) upgradeMethod.addEventListener('change', function () {
var group = container.querySelector('.editor-upgrade-tag-group');
if (group) group.style.display = upgradeMethod.value === 'tags' ? 'flex' : 'none';
});
@@ -13263,7 +13266,7 @@ document.head.appendChild(styleEl);
var enabledSelect = document.getElementById('th-editor-enabled');
var statusPill = container ? container.querySelector('.th-info-status-pill') : null;
if (enabledSelect && statusPill) {
- enabledSelect.addEventListener('change', function() {
+ enabledSelect.addEventListener('change', function () {
var on = enabledSelect.value === 'true';
statusPill.className = 'th-info-status-pill ' + (on ? 'th-info-status-enabled' : 'th-info-status-disabled');
statusPill.innerHTML = on ? '
Enabled' : 'Disabled';
@@ -13278,7 +13281,7 @@ document.head.appendChild(styleEl);
function renderTVInstanceCards(grid, list, currentId) {
grid.innerHTML = '';
currentId = (currentId != null) ? parseInt(currentId, 10) : (list && list[0] ? list[0].id : null);
- (list || []).forEach(function(inst) {
+ (list || []).forEach(function (inst) {
var enabled = inst.enabled !== false;
var statusClass = enabled ? 'status-connected' : 'status-disabled';
var statusIcon = enabled ? 'fa-check-circle' : 'fa-minus-circle';
@@ -13299,8 +13302,8 @@ document.head.appendChild(styleEl);
var addCard = document.createElement('div');
addCard.innerHTML = addInstanceCardHtml('media-hunt-instance-tv', 'fa-tv', 'Add TV Instance');
grid.appendChild(addCard.firstElementChild);
- grid.querySelectorAll('.btn-card.edit').forEach(function(btn) {
- btn.addEventListener('click', function(e) {
+ grid.querySelectorAll('.btn-card.edit').forEach(function (btn) {
+ btn.addEventListener('click', function (e) {
e.stopPropagation();
window.TVHuntInstanceEditor.openEditor(
btn.getAttribute('data-id'),
@@ -13308,17 +13311,17 @@ document.head.appendChild(styleEl);
);
});
});
- grid.querySelectorAll('.btn-card.set-default').forEach(function(btn) {
- btn.addEventListener('click', function(e) {
+ grid.querySelectorAll('.btn-card.set-default').forEach(function (btn) {
+ btn.addEventListener('click', function (e) {
e.stopPropagation();
window.TVHuntInstanceEditor.setDefault(btn.getAttribute('data-id'));
});
});
- grid.querySelectorAll('.btn-card.delete').forEach(function(btn) {
- btn.addEventListener('click', function(e) {
+ grid.querySelectorAll('.btn-card.delete').forEach(function (btn) {
+ btn.addEventListener('click', function (e) {
e.stopPropagation();
var name = btn.getAttribute('data-name') || ('Instance ' + btn.getAttribute('data-id'));
- var doDelete = function() { window.TVHuntInstanceEditor.deleteInstance(btn.getAttribute('data-id')); };
+ var doDelete = function () { window.TVHuntInstanceEditor.deleteInstance(btn.getAttribute('data-id')); };
if (window.HuntarrConfirm && window.HuntarrConfirm.show) {
window.HuntarrConfirm.show({ title: 'Delete Instance', message: 'Delete TV Hunt instance "' + (name || '') + '"? All settings and collection data for this instance will be permanently removed.', confirmLabel: 'Delete', onConfirm: doDelete });
} else if (confirm('Delete "' + name + '"? This cannot be undone.')) { doDelete(); }
@@ -13327,17 +13330,17 @@ document.head.appendChild(styleEl);
}
window.TVHuntInstanceEditor = {
- loadInstanceList: function() {
+ loadInstanceList: function () {
var grid = document.getElementById('tv-hunt-settings-instances-grid');
if (!grid) return;
grid.innerHTML = '
Loading...
';
var url = api('./api/tv-hunt/instances') + '?t=' + (Date.now ? Date.now() : new Date().getTime());
fetch(url, { cache: 'no-store', credentials: 'same-origin' })
- .then(function(r) {
- if (!r.ok) return r.json().then(function(data) { return { instances: data.instances || [], error: data.error }; });
+ .then(function (r) {
+ if (!r.ok) return r.json().then(function (data) { return { instances: data.instances || [], error: data.error }; });
return r.json();
})
- .then(function(data) {
+ .then(function (data) {
var list = (data && data.instances) ? data.instances : [];
var currentId = (data && data.current_instance_id != null) ? data.current_instance_id : null;
var err = data && data.error;
@@ -13346,7 +13349,7 @@ document.head.appendChild(styleEl);
window.huntarrUI.showNotification(err, 'error');
}
})
- .catch(function() {
+ .catch(function () {
var errDiv = document.createElement('div');
errDiv.style.cssText = 'color: #f87171; margin-bottom: 12px;';
errDiv.textContent = 'Failed to load instances. You can still add a new TV instance below.';
@@ -13356,7 +13359,7 @@ document.head.appendChild(styleEl);
});
},
- setDefault: function(instanceId) {
+ setDefault: function (instanceId) {
if (!instanceId) return;
var self = this;
fetch(api('./api/tv-hunt/instances/current'), {
@@ -13364,8 +13367,8 @@ document.head.appendChild(styleEl);
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ instance_id: parseInt(instanceId, 10) })
})
- .then(function(r) { return r.json(); })
- .then(function(data) {
+ .then(function (r) { return r.json(); })
+ .then(function (data) {
if (data.success) {
if (window.huntarrUI && window.huntarrUI.showNotification) window.huntarrUI.showNotification('Default instance updated', 'success');
self.loadInstanceList();
@@ -13373,17 +13376,17 @@ document.head.appendChild(styleEl);
window.huntarrUI.showNotification(data.error, 'error');
}
})
- .catch(function() {
+ .catch(function () {
if (window.huntarrUI && window.huntarrUI.showNotification) window.huntarrUI.showNotification('Failed to set default instance', 'error');
});
},
- deleteInstance: function(instanceId) {
+ deleteInstance: function (instanceId) {
if (!instanceId) return;
var self = this;
fetch(api('./api/tv-hunt/instances/' + instanceId), { method: 'DELETE' })
- .then(function(r) { return r.json(); })
- .then(function(data) {
+ .then(function (r) { return r.json(); })
+ .then(function (data) {
if (data.success) {
if (window.huntarrUI && window.huntarrUI.showNotification) window.huntarrUI.showNotification('Instance deleted', 'success');
self.loadInstanceList();
@@ -13391,67 +13394,67 @@ document.head.appendChild(styleEl);
window.huntarrUI.showNotification(data.error || 'Failed to delete', 'error');
}
})
- .catch(function() {
+ .catch(function () {
if (window.huntarrUI && window.huntarrUI.showNotification) window.huntarrUI.showNotification('Failed to delete instance', 'error');
});
},
- openEditor: function(instanceId, instanceName) {
+ openEditor: function (instanceId, instanceName) {
_currentInstanceId = instanceId;
_currentInstanceName = instanceName || ('Instance ' + instanceId);
_editorDirty = false;
var self = this;
fetch(api('./api/tv-hunt/instances/' + instanceId + '/settings'), { cache: 'no-store' })
- .then(function(r) { return r.json().then(function(data) { return { ok: r.ok, data: data }; }); })
- .then(function(result) {
- if (!result.ok || result.data.error) {
- if (window.huntarrUI) window.huntarrUI.showNotification(result.data.error || 'Failed to load settings', 'error');
- return;
- }
- var contentEl = document.getElementById('media-hunt-instance-editor-content');
- if (contentEl) {
- contentEl.innerHTML = buildEditorHtml(result.data);
- setupExemptTagsListeners(contentEl);
- setupChangeDetection(contentEl);
- }
- var breadcrumb = document.getElementById('media-hunt-instance-editor-instance-name');
- if (breadcrumb) breadcrumb.textContent = _currentInstanceName;
- var appNameEl = document.getElementById('media-hunt-instance-editor-app-name');
- if (appNameEl) appNameEl.textContent = 'TV Hunt';
- var appIcon = document.getElementById('media-hunt-instance-editor-app-icon');
- if (appIcon) appIcon.className = 'fas fa-tv';
+ .then(function (r) { return r.json().then(function (data) { return { ok: r.ok, data: data }; }); })
+ .then(function (result) {
+ if (!result.ok || result.data.error) {
+ if (window.huntarrUI) window.huntarrUI.showNotification(result.data.error || 'Failed to load settings', 'error');
+ return;
+ }
+ var contentEl = document.getElementById('media-hunt-instance-editor-content');
+ if (contentEl) {
+ contentEl.innerHTML = buildEditorHtml(result.data);
+ setupExemptTagsListeners(contentEl);
+ setupChangeDetection(contentEl);
+ }
+ var breadcrumb = document.getElementById('media-hunt-instance-editor-instance-name');
+ if (breadcrumb) breadcrumb.textContent = _currentInstanceName;
+ var appNameEl = document.getElementById('media-hunt-instance-editor-app-name');
+ if (appNameEl) appNameEl.textContent = 'TV Hunt';
+ var appIcon = document.getElementById('media-hunt-instance-editor-app-icon');
+ if (appIcon) appIcon.className = 'fas fa-tv';
- var backBtn = document.getElementById('media-hunt-instance-editor-back');
- var saveBtn = document.getElementById('media-hunt-instance-editor-save');
- if (backBtn) backBtn.onclick = function() {
- if (!_editorDirty) { window.huntarrUI.switchSection('media-hunt-instances'); return; }
- window.HuntarrConfirm.show({
- title: 'Unsaved Changes',
- message: 'You have unsaved changes that will be lost if you leave.',
- confirmLabel: 'Go Back',
- cancelLabel: 'Leave',
- onConfirm: function() {},
- onCancel: function() { window.huntarrUI.switchSection('media-hunt-instances'); }
- });
- };
- if (saveBtn) saveBtn.onclick = function() { self.saveEditor(); };
+ var backBtn = document.getElementById('media-hunt-instance-editor-back');
+ var saveBtn = document.getElementById('media-hunt-instance-editor-save');
+ if (backBtn) backBtn.onclick = function () {
+ if (!_editorDirty) { window.huntarrUI.switchSection('media-hunt-instances'); return; }
+ window.HuntarrConfirm.show({
+ title: 'Unsaved Changes',
+ message: 'You have unsaved changes that will be lost if you leave.',
+ confirmLabel: 'Go Back',
+ cancelLabel: 'Leave',
+ onConfirm: function () { },
+ onCancel: function () { window.huntarrUI.switchSection('media-hunt-instances'); }
+ });
+ };
+ if (saveBtn) saveBtn.onclick = function () { self.saveEditor(); };
- var resetBtn = document.getElementById('th-editor-reset-state');
- if (resetBtn) resetBtn.onclick = function() { self.resetState(instanceId); };
+ var resetBtn = document.getElementById('th-editor-reset-state');
+ if (resetBtn) resetBtn.onclick = function () { self.resetState(instanceId); };
- var resetCollBtn = document.getElementById('th-editor-reset-collection');
- if (resetCollBtn) resetCollBtn.onclick = function() { self.resetCollection(instanceId); };
+ var resetCollBtn = document.getElementById('th-editor-reset-collection');
+ if (resetCollBtn) resetCollBtn.onclick = function () { self.resetCollection(instanceId); };
- if (window.huntarrUI && window.huntarrUI.switchSection) {
- window.huntarrUI.switchSection('tv-hunt-instance-editor');
- }
- })
- .catch(function(err) {
- if (window.huntarrUI) window.huntarrUI.showNotification('Failed to load settings: ' + (err.message || ''), 'error');
- });
+ if (window.huntarrUI && window.huntarrUI.switchSection) {
+ window.huntarrUI.switchSection('tv-hunt-instance-editor');
+ }
+ })
+ .catch(function (err) {
+ if (window.huntarrUI) window.huntarrUI.showNotification('Failed to load settings: ' + (err.message || ''), 'error');
+ });
},
- saveEditor: function() {
+ saveEditor: function () {
if (!_currentInstanceId) return;
var payload = collectFormData();
var saveBtn = document.getElementById('media-hunt-instance-editor-save');
@@ -13461,56 +13464,56 @@ document.head.appendChild(styleEl);
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
})
- .then(function(r) { return r.json(); })
- .then(function(data) {
- if (data.error) {
- if (window.huntarrUI) window.huntarrUI.showNotification(data.error, 'error');
+ .then(function (r) { return r.json(); })
+ .then(function (data) {
+ if (data.error) {
+ if (window.huntarrUI) window.huntarrUI.showNotification(data.error, 'error');
+ if (saveBtn) { saveBtn.disabled = false; saveBtn.innerHTML = '
Save'; saveBtn.classList.add('enabled'); }
+ return;
+ }
+ _editorDirty = false;
+ if (saveBtn) {
+ saveBtn.innerHTML = '
Saved!';
+ saveBtn.classList.remove('enabled');
+ setTimeout(function () { saveBtn.innerHTML = '
Save'; saveBtn.disabled = true; }, 2000);
+ }
+ })
+ .catch(function () {
+ if (window.huntarrUI) window.huntarrUI.showNotification('Failed to save settings', 'error');
if (saveBtn) { saveBtn.disabled = false; saveBtn.innerHTML = '
Save'; saveBtn.classList.add('enabled'); }
- return;
- }
- _editorDirty = false;
- if (saveBtn) {
- saveBtn.innerHTML = '
Saved!';
- saveBtn.classList.remove('enabled');
- setTimeout(function() { saveBtn.innerHTML = '
Save'; saveBtn.disabled = true; }, 2000);
- }
- })
- .catch(function() {
- if (window.huntarrUI) window.huntarrUI.showNotification('Failed to save settings', 'error');
- if (saveBtn) { saveBtn.disabled = false; saveBtn.innerHTML = '
Save'; saveBtn.classList.add('enabled'); }
- });
+ });
},
- resetState: function(instanceId) {
+ resetState: function (instanceId) {
window.HuntarrConfirm.show({
title: 'Reset State',
message: 'Reset processed state for this TV Hunt instance?',
confirmLabel: 'Reset',
- onConfirm: function() {
+ onConfirm: function () {
fetch(api('./api/tv-hunt/instances/' + instanceId + '/reset-state'), { method: 'POST' })
- .then(function(r) { return r.json(); })
- .then(function(data) {
- if (data.error) { window.huntarrUI.showNotification(data.error, 'error'); }
- else { window.huntarrUI.showNotification('State reset.', 'success'); }
- })
- .catch(function() { window.huntarrUI.showNotification('Reset request failed', 'error'); });
+ .then(function (r) { return r.json(); })
+ .then(function (data) {
+ if (data.error) { window.huntarrUI.showNotification(data.error, 'error'); }
+ else { window.huntarrUI.showNotification('State reset.', 'success'); }
+ })
+ .catch(function () { window.huntarrUI.showNotification('Reset request failed', 'error'); });
}
});
},
- resetCollection: function(instanceId) {
+ resetCollection: function (instanceId) {
window.HuntarrConfirm.show({
title: 'Reset TV Collection',
message: 'This will permanently delete ALL TV series from this instance\'s collection. This cannot be undone.',
confirmLabel: 'Delete All',
- onConfirm: function() {
+ onConfirm: function () {
fetch(api('./api/tv-hunt/instances/' + instanceId + '/reset-collection'), { method: 'DELETE' })
- .then(function(r) { return r.json(); })
- .then(function(data) {
- if (data.success) { window.huntarrUI.showNotification(data.message || 'TV collection reset.', 'success'); }
- else { window.huntarrUI.showNotification(data.error || 'Failed to reset.', 'error'); }
- })
- .catch(function() { window.huntarrUI.showNotification('Request failed.', 'error'); });
+ .then(function (r) { return r.json(); })
+ .then(function (data) {
+ if (data.success) { window.huntarrUI.showNotification(data.message || 'TV collection reset.', 'success'); }
+ else { window.huntarrUI.showNotification(data.error || 'Failed to reset.', 'error'); }
+ })
+ .catch(function () { window.huntarrUI.showNotification('Request failed.', 'error'); });
}
});
}