Skip to content

Commit 6f8158d

Browse files
committed
Add Prowlarr integration with settings management and UI enhancements
- Introduced Prowlarr section in the main UI and sidebar for navigation. - Implemented settings form for Prowlarr, including manual save functionality and auto-detection for connection status. - Updated settings manager to recognize Prowlarr as a known app type. - Enhanced SettingsForms to handle Prowlarr-specific settings and ensure proper state management. - Added necessary API routes and blueprints for Prowlarr integration. - Improved user experience with dynamic UI updates and error handling for Prowlarr settings.
1 parent 7f7e64f commit 6f8158d

File tree

10 files changed

+412
-7
lines changed

10 files changed

+412
-7
lines changed

frontend/static/js/new-main.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,21 @@ let huntarrUI = {
681681

682682
// Initialize notifications settings if not already done
683683
this.initializeNotifications();
684+
} else if (section === 'prowlarr' && document.getElementById('prowlarrSection')) {
685+
document.getElementById('prowlarrSection').classList.add('active');
686+
document.getElementById('prowlarrSection').style.display = 'block';
687+
if (document.getElementById('settingsProwlarrNav')) document.getElementById('settingsProwlarrNav').classList.add('active');
688+
newTitle = 'Prowlarr';
689+
this.currentSection = 'prowlarr';
690+
691+
// Switch to Settings sidebar for prowlarr
692+
this.showSettingsSidebar();
693+
694+
// Set localStorage to maintain Settings sidebar preference
695+
localStorage.setItem('huntarr-settings-sidebar', 'true');
696+
697+
// Initialize prowlarr settings if not already done
698+
this.initializeProwlarr();
684699
} else if (section === 'user' && document.getElementById('userSection')) {
685700
document.getElementById('userSection').classList.add('active');
686701
document.getElementById('userSection').style.display = 'block';
@@ -1086,6 +1101,7 @@ let huntarrUI = {
10861101
window.swaparrSettings = data.swaparr;
10871102
this.populateSettingsForm('swaparr', data.swaparr);
10881103
}
1104+
if (data.prowlarr) this.populateSettingsForm('prowlarr', data.prowlarr);
10891105
if (data.general) this.populateSettingsForm('general', data.general);
10901106

10911107
// Update duration displays (like sleep durations)
@@ -4317,6 +4333,53 @@ let huntarrUI = {
43174333
});
43184334
},
43194335

4336+
initializeProwlarr: function() {
4337+
console.log('[huntarrUI] initializeProwlarr called');
4338+
4339+
// Check if prowlarr is already initialized
4340+
const prowlarrContainer = document.getElementById('prowlarrContainer');
4341+
if (!prowlarrContainer) {
4342+
console.error('[huntarrUI] prowlarrContainer element not found!');
4343+
return;
4344+
}
4345+
4346+
console.log('[huntarrUI] prowlarrContainer found:', prowlarrContainer);
4347+
console.log('[huntarrUI] Current container content:', prowlarrContainer.innerHTML.trim());
4348+
4349+
// Check if prowlarr is actually initialized (ignore HTML comments)
4350+
const currentContent = prowlarrContainer.innerHTML.trim();
4351+
if (currentContent !== '' && !currentContent.includes('<!-- Prowlarr content will be loaded here -->')) {
4352+
console.log('[huntarrUI] Prowlarr already initialized, skipping');
4353+
return; // Already initialized
4354+
}
4355+
4356+
console.log('[huntarrUI] Loading prowlarr settings from API...');
4357+
4358+
// Load settings from API and generate the prowlarr form
4359+
fetch('./api/settings')
4360+
.then(response => response.json())
4361+
.then(settings => {
4362+
console.log('[huntarrUI] Loaded settings for prowlarr:', settings);
4363+
console.log('[huntarrUI] Prowlarr settings:', settings.prowlarr);
4364+
console.log('[huntarrUI] SettingsForms available:', typeof SettingsForms !== 'undefined');
4365+
console.log('[huntarrUI] generateProwlarrForm available:', typeof SettingsForms !== 'undefined' && SettingsForms.generateProwlarrForm);
4366+
4367+
// Generate the prowlarr form
4368+
if (typeof SettingsForms !== 'undefined' && SettingsForms.generateProwlarrForm) {
4369+
console.log('[huntarrUI] Calling SettingsForms.generateProwlarrForm...');
4370+
SettingsForms.generateProwlarrForm(prowlarrContainer, settings.prowlarr || {});
4371+
console.log('[huntarrUI] Prowlarr form generated successfully');
4372+
} else {
4373+
console.error('[huntarrUI] SettingsForms.generateProwlarrForm not available');
4374+
prowlarrContainer.innerHTML = '<p>Error: Prowlarr forms not loaded</p>';
4375+
}
4376+
})
4377+
.catch(error => {
4378+
console.error('[huntarrUI] Error loading prowlarr settings:', error);
4379+
prowlarrContainer.innerHTML = '<p>Error loading prowlarr settings</p>';
4380+
});
4381+
},
4382+
43204383
initializeUser: function() {
43214384
console.log('[huntarrUI] initializeUser called');
43224385

frontend/static/js/settings_forms.js

Lines changed: 143 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3403,7 +3403,7 @@ const SettingsForms = {
34033403
const appType = container.getAttribute('data-app-type') || 'general';
34043404

34053405
// Skip auto-save for all forms - they now use manual save
3406-
const manualSaveApps = ['swaparr', 'general', 'notifications', 'sonarr', 'radarr', 'lidarr', 'readarr', 'whisparr', 'eros'];
3406+
const manualSaveApps = ['swaparr', 'general', 'notifications', 'sonarr', 'radarr', 'lidarr', 'readarr', 'whisparr', 'eros', 'prowlarr'];
34073407
if (manualSaveApps.includes(appType)) {
34083408
console.log(`[SettingsForms] Skipping auto-save setup for ${appType} - using manual save`);
34093409
return;
@@ -4111,6 +4111,15 @@ const SettingsForms = {
41114111
settings.quality_based_removal = getInputValue('#swaparr_quality_based_removal', false);
41124112
settings.blocked_quality_patterns = this.getTagsFromContainer('swaparr_quality_patterns_tags');
41134113
}
4114+
else if (appType === 'prowlarr') {
4115+
// Prowlarr doesn't use instances array, store directly
4116+
settings.instances = [];
4117+
4118+
settings.enabled = getInputValue('#prowlarr-enabled-0', true);
4119+
settings.name = getInputValue('#prowlarr-name-0', 'Prowlarr');
4120+
settings.api_url = getInputValue('#prowlarr-url-0', '');
4121+
settings.api_key = getInputValue('#prowlarr-key-0', '');
4122+
}
41144123
}
41154124

41164125
console.log('Collected settings for', appType, settings);
@@ -5142,7 +5151,7 @@ const SettingsForms = {
51425151

51435152
// Check connection status for an instance
51445153
checkConnectionStatus: function(app, instanceIndex) {
5145-
const supportedApps = ['radarr', 'sonarr', 'lidarr', 'readarr', 'whisparr', 'eros'];
5154+
const supportedApps = ['radarr', 'sonarr', 'lidarr', 'readarr', 'whisparr', 'eros', 'prowlarr'];
51465155
if (!supportedApps.includes(app)) return;
51475156

51485157
const urlInput = document.getElementById(`${app}-url-${instanceIndex}`);
@@ -5640,7 +5649,7 @@ const SettingsForms = {
56405649
// Only remove if all sections have no unsaved changes
56415650
if (!window.swaparrUnsavedChanges && !window.settingsUnsavedChanges && !window.notificationsUnsavedChanges &&
56425651
!window.sonarrUnsavedChanges && !window.radarrUnsavedChanges && !window.lidarrUnsavedChanges &&
5643-
!window.readarrUnsavedChanges && !window.whisparrUnsavedChanges && !window.erosUnsavedChanges) {
5652+
!window.readarrUnsavedChanges && !window.whisparrUnsavedChanges && !window.erosUnsavedChanges && !window.prowlarrUnsavedChanges) {
56445653
// Remove beforeunload event listener
56455654
if (window.huntarrBeforeUnloadListener) {
56465655
window.removeEventListener('beforeunload', window.huntarrBeforeUnloadListener);
@@ -5712,7 +5721,7 @@ const SettingsForms = {
57125721
}
57135722

57145723
// Check each app instance for unsaved changes
5715-
const appTypes = ['sonarr', 'radarr', 'lidarr', 'readarr', 'whisparr', 'eros'];
5724+
const appTypes = ['sonarr', 'radarr', 'lidarr', 'readarr', 'whisparr', 'eros', 'prowlarr'];
57165725
for (const appType of appTypes) {
57175726
const unsavedChangesVar = `${appType}UnsavedChanges`;
57185727
if (window[unsavedChangesVar]) {
@@ -5738,6 +5747,136 @@ const SettingsForms = {
57385747
}
57395748

57405749
return true; // No unsaved changes, can proceed
5750+
},
5751+
5752+
// Generate Prowlarr settings form
5753+
generateProwlarrForm: function(container, settings = {}) {
5754+
// Ensure settings is a valid object
5755+
if (!settings || typeof settings !== 'object') {
5756+
settings = {};
5757+
}
5758+
5759+
// Add data-app-type attribute to container
5760+
container.setAttribute('data-app-type', 'prowlarr');
5761+
5762+
// Add save button at the top
5763+
let prowlarrSaveButtonHtml = `
5764+
<div style="margin-bottom: 20px;">
5765+
<button type="button" id="prowlarr-save-button" disabled style="
5766+
background: #6b7280;
5767+
color: #9ca3af;
5768+
border: 1px solid #4b5563;
5769+
padding: 8px 16px;
5770+
border-radius: 6px;
5771+
font-size: 14px;
5772+
font-weight: 500;
5773+
cursor: not-allowed;
5774+
display: flex;
5775+
align-items: center;
5776+
gap: 8px;
5777+
transition: all 0.2s ease;
5778+
">
5779+
<i class="fas fa-save"></i>
5780+
Save Changes
5781+
</button>
5782+
</div>
5783+
`;
5784+
5785+
// Create the Prowlarr configuration container
5786+
let prowlarrHtml = `
5787+
<div class="settings-group" style="
5788+
background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #334155 100%);
5789+
border: 2px solid rgba(90, 109, 137, 0.3);
5790+
border-radius: 12px;
5791+
padding: 20px;
5792+
margin: 15px 0 25px 0;
5793+
box-shadow: 0 4px 12px rgba(90, 109, 137, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.1);
5794+
">
5795+
<h3>Prowlarr Configuration</h3>
5796+
<div class="prowlarr-container">
5797+
<div class="prowlarr-item" data-instance-id="0">
5798+
<div class="prowlarr-header">
5799+
<h4>Prowlarr</h4>
5800+
<div class="prowlarr-actions">
5801+
<span class="connection-status" id="prowlarr-status-0" style="margin-left: 10px; font-weight: bold; font-size: 0.9em;"></span>
5802+
</div>
5803+
</div>
5804+
<div class="prowlarr-content">
5805+
<div class="setting-item">
5806+
<label for="prowlarr-enabled-0">Enabled:</label>
5807+
<label class="toggle-switch" style="width:40px; height:20px; display:inline-block; position:relative;">
5808+
<input type="checkbox" id="prowlarr-enabled-0" name="enabled" ${settings.enabled !== false ? 'checked' : ''}>
5809+
<span class="toggle-slider" style="position:absolute; cursor:pointer; top:0; left:0; right:0; bottom:0; background-color:#3d4353; border-radius:20px; transition:0.4s;"></span>
5810+
</label>
5811+
<p class="setting-help">Enable or disable Prowlarr integration</p>
5812+
</div>
5813+
<div class="setting-item">
5814+
<label for="prowlarr-name-0">Name:</label>
5815+
<input type="text" id="prowlarr-name-0" name="name" value="${settings.name || 'Prowlarr'}" placeholder="Friendly name for Prowlarr">
5816+
<p class="setting-help">Friendly name for this Prowlarr instance</p>
5817+
</div>
5818+
<div class="setting-item">
5819+
<label for="prowlarr-url-0">URL:</label>
5820+
<input type="text" id="prowlarr-url-0" name="api_url" value="${settings.api_url || ''}" placeholder="Base URL for Prowlarr (e.g., http://localhost:9696)" data-instance-index="0">
5821+
<p class="setting-help">Base URL for Prowlarr (e.g., http://localhost:9696)</p>
5822+
</div>
5823+
<div class="setting-item">
5824+
<label for="prowlarr-key-0">API Key:</label>
5825+
<input type="text" id="prowlarr-key-0" name="api_key" value="${settings.api_key || ''}" placeholder="API key for Prowlarr" data-instance-index="0">
5826+
<p class="setting-help">API key for Prowlarr</p>
5827+
</div>
5828+
</div>
5829+
</div>
5830+
</div>
5831+
</div>
5832+
`;
5833+
5834+
// Set the content with save button at the top
5835+
container.innerHTML = prowlarrSaveButtonHtml + prowlarrHtml;
5836+
5837+
// Setup auto-detection (like Sonarr)
5838+
this.setupProwlarrAutoDetection(container);
5839+
5840+
// Set up manual save functionality for Prowlarr
5841+
this.setupAppManualSave(container, 'prowlarr', settings);
5842+
},
5843+
5844+
// Setup auto-detection for Prowlarr (similar to Sonarr)
5845+
setupProwlarrAutoDetection: function(container) {
5846+
console.log('[SettingsForms] Setting up Prowlarr auto-detection');
5847+
5848+
// Find URL and API key inputs
5849+
const urlInput = container.querySelector('#prowlarr-url-0');
5850+
const apiKeyInput = container.querySelector('#prowlarr-key-0');
5851+
5852+
if (urlInput) {
5853+
urlInput.addEventListener('input', () => {
5854+
setTimeout(() => {
5855+
this.checkConnectionStatus('prowlarr', 0);
5856+
}, 1000); // 1 second delay to prevent spam while typing
5857+
});
5858+
urlInput.addEventListener('blur', () => {
5859+
this.checkConnectionStatus('prowlarr', 0);
5860+
});
5861+
}
5862+
5863+
if (apiKeyInput) {
5864+
apiKeyInput.addEventListener('input', () => {
5865+
setTimeout(() => {
5866+
this.checkConnectionStatus('prowlarr', 0);
5867+
}, 1000); // 1 second delay to prevent spam while typing
5868+
});
5869+
apiKeyInput.addEventListener('blur', () => {
5870+
this.checkConnectionStatus('prowlarr', 0);
5871+
});
5872+
}
5873+
5874+
// Initial status check if URL and key are provided
5875+
if (urlInput && apiKeyInput && urlInput.value.trim() && apiKeyInput.value.trim()) {
5876+
setTimeout(() => {
5877+
this.checkConnectionStatus('prowlarr', 0);
5878+
}, 500);
5879+
}
57415880
}
57425881
};
57435882

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<section id="prowlarrSection" class="content-section">
2+
<div id="prowlarrContainer" class="settings-container">
3+
<!-- Prowlarr content will be loaded here -->
4+
</div>
5+
</section>

frontend/templates/components/sidebar.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,12 @@ <h1>Huntarr</h1>
203203
</div>
204204
<span>Notifications</span>
205205
</a>
206+
<a href="./#prowlarr" class="nav-item" id="settingsProwlarrNav">
207+
<div class="nav-icon-wrapper">
208+
<i class="fas fa-search"></i>
209+
</div>
210+
<span>Prowlarr</span>
211+
</a>
206212
<a href="./user" class="nav-item" id="settingsUserNav">
207213
<div class="nav-icon-wrapper">
208214
<i class="fas fa-user"></i>

frontend/templates/index.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@
5858
<!-- Notifications Section -->
5959
{% include 'components/notifications_section.html' %}
6060

61+
<!-- Prowlarr Section -->
62+
{% include 'components/prowlarr_section.html' %}
63+
6164
<!-- User Section -->
6265
{% include 'components/user_section.html' %}
6366

src/primary/apps/blueprints.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from src.primary.apps.eros_routes import eros_bp
1414
from src.primary.apps.swaparr_routes import swaparr_bp
1515
from src.primary.apps.requestarr_routes import requestarr_bp
16+
from src.primary.apps.prowlarr_routes import prowlarr_bp
1617

1718
__all__ = [
1819
"sonarr_bp",
@@ -22,5 +23,6 @@
2223
"whisparr_bp",
2324
"eros_bp",
2425
"swaparr_bp",
25-
"requestarr_bp"
26+
"requestarr_bp",
27+
"prowlarr_bp"
2628
]

0 commit comments

Comments
 (0)