/** * Client Editor (Movie Hunt) - full-page editor for adding/editing a download client. * Separate from Client Management (clients.js). Attaches to window.SettingsForms. * Load after settings/core.js and instance-editor.js. */ (function() { 'use strict'; if (typeof window.SettingsForms === 'undefined') return; const Forms = window.SettingsForms; var CLIENT_TYPES = [ { value: 'nzbhunt', label: 'NZB Hunt (Built-in)' }, { value: 'nzbget', label: 'NZBGet' }, { value: 'sabnzbd', label: 'SABnzbd' } ]; var PRIORITY_OPTIONS = [ { value: 'last', label: 'Last' }, { value: 'first', label: 'First' }, { value: 'default', label: 'Default' }, { value: 'high', label: 'High' }, { value: 'low', label: 'Low' } ]; 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 titleEl = document.getElementById('instance-editor-title'); if (titleEl) { titleEl.innerHTML = '' + String(clientDisplayName).replace(//g, '>') + ' Connection Settings'; } const contentEl = document.getElementById('instance-editor-content'); if (contentEl) contentEl.innerHTML = this.generateClientEditorHtml(inst); const saveBtn = document.getElementById('instance-editor-save'); const backBtn = document.getElementById('instance-editor-back'); if (saveBtn) { saveBtn.onclick = () => this.saveClientFromEditor(); saveBtn.disabled = false; saveBtn.classList.add('enabled'); } if (backBtn) backBtn.onclick = () => this.cancelInstanceEditor(); const enabledSelect = document.getElementById('editor-client-enabled'); const enableIcon = document.getElementById('client-enable-status-icon'); if (enabledSelect && enableIcon) { 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'; }); } // Add event listeners for real-time connection status checking // For NZB Hunt, only check on initial load (no host/port fields) if (typeVal !== 'nzbhunt') { 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 (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 - built-in, no status needed) if (typeVal !== 'nzbhunt') { this.checkClientConnection(); } if (window.huntarrUI && window.huntarrUI.switchSection) { window.huntarrUI.switchSection('instance-editor'); } }; 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 host = (instance.host || '').replace(//g, '>').replace(/"/g, '"'); const defaultPort = typeVal === 'nzbget' ? '6789' : '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'; const pwdPlaceholder = isEdit && (instance.password_last4 || '') ? ('Enter new password or leave blank to keep existing (••••' + (instance.password_last4 || '') + ')') : 'Password (if required)'; const isTvContext = !!(window._mediaHuntInstanceEditorMode === 'tv'); const catDefault = isTvContext ? 'tv' : 'movies'; const category = (instance.category || catDefault).replace(//g, '>').replace(/"/g, '"'); const recentPriority = (instance.recent_priority || 'default').toLowerCase(); const olderPriority = (instance.older_priority || 'default').toLowerCase(); let clientPriority = parseInt(instance.client_priority, 10); if (isNaN(clientPriority) || clientPriority < 1 || clientPriority > 99) clientPriority = 50; const recentOptionsHtml = PRIORITY_OPTIONS.map(function(o) { return ''; }).join(''); const olderOptionsHtml = PRIORITY_OPTIONS.map(function(o) { return ''; }).join(''); const isNzbHunt = typeVal === 'nzbhunt'; const hideForNzbHunt = isNzbHunt ? ' style="display: none;"' : ''; const nzbHuntGridClass = isNzbHunt ? ' editor-grid-nzbhunt' : ''; return `
${isNzbHunt ? 'NZB Hunt (Built-in)' : 'Connection Settings'}
${isNzbHunt ? `
Built-in Download Client

NZB Hunt is Huntarr's integrated usenet download client. No external host, port, or API keys needed — it uses the usenet servers configured in NZB Hunt → Settings → Servers.

` : ''}

Enable or disable this download client

${!isNzbHunt ? `

A friendly name to identify this client

` : ''}

Hostname or IP address of your download client

Port number for your download client (SABnzbd default: 8080, NZBGet default: 6789)

API key from your download client settings. ${isEdit ? 'Leave blank to keep existing.' : ''}

Username for basic authentication (NZBGet typically requires this)

${isEdit ? 'Leave blank to keep existing password' : 'Password for authentication (if required)'}

${!isNzbHunt ? `
Additional Configurations

Adding a category specific to ${isTvContext ? 'TV' : 'Movie'} Hunt avoids conflicts with unrelated downloads. Using a category is optional, but strongly recommended.

` : ''}

Priority to use when grabbing movies that aired within the last 21 days.

Priority to use when grabbing movies that aired over 21 days ago.

Download Client Priority from 1 (Highest) to 99 (Lowest). Default: 50. Round-Robin is used for clients with the same priority.

`; }; 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'); const portEl = document.getElementById('editor-client-port'); const enabledEl = document.getElementById('editor-client-enabled'); const apiKeyEl = document.getElementById('editor-client-apikey'); const usernameEl = document.getElementById('editor-client-username'); const passwordEl = document.getElementById('editor-client-password'); const categoryEl = document.getElementById('editor-client-category'); const recentPriorityEl = document.getElementById('editor-client-recent-priority'); const olderPriorityEl = document.getElementById('editor-client-older-priority'); const clientPriorityEl = document.getElementById('editor-client-priority'); const type = (this._currentEditing && this._currentEditing.originalInstance && this._currentEditing.originalInstance.type) ? String(this._currentEditing.originalInstance.type).trim().toLowerCase() : 'nzbget'; const isNzbHuntType = (type === 'nzbhunt' || type === 'nzb_hunt'); const name = isNzbHuntType ? 'NZB Hunt' : (nameEl ? nameEl.value.trim() : ''); const host = hostEl ? hostEl.value.trim() : ''; const portDefault = type === 'nzbget' ? 6789 : 8080; let port = portDefault; if (portEl && portEl.value.trim() !== '') { const p = parseInt(portEl.value, 10); if (!isNaN(p)) port = p; } const enabled = enabledEl ? enabledEl.value === 'true' : true; const apiKey = apiKeyEl ? apiKeyEl.value.trim() : ''; const username = usernameEl ? usernameEl.value.trim() : ''; const password = passwordEl ? passwordEl.value.trim() : ''; const isTvMode = !!(window._mediaHuntInstanceEditorMode === 'tv'); const defaultCategory = isTvMode ? 'tv' : 'movies'; let category = (categoryEl && !isNzbHuntType) ? categoryEl.value.trim() : defaultCategory; if (isNzbHuntType) { const orig = this._currentEditing && this._currentEditing.originalInstance; category = (orig && orig.category) ? String(orig.category).trim() : ''; } const recentPriority = recentPriorityEl ? (recentPriorityEl.value || 'default').toLowerCase() : 'default'; const olderPriority = olderPriorityEl ? (olderPriorityEl.value || 'default').toLowerCase() : 'default'; let clientPriority = 50; if (clientPriorityEl && clientPriorityEl.value.trim() !== '') { const p = parseInt(clientPriorityEl.value, 10); if (!isNaN(p) && p >= 1 && p <= 99) clientPriority = p; } const body = { name: isNzbHuntType ? 'NZB Hunt' : (name || 'Unnamed'), type: type, host: isNzbHuntType ? 'internal' : host, port: isNzbHuntType ? 0 : port, enabled: enabled, category: category || defaultCategory, recent_priority: recentPriority, older_priority: olderPriority, client_priority: clientPriority }; if (!isNzbHuntType) { if (apiKey) body.api_key = apiKey; if (username) body.username = username; if (password) body.password = password; } const isAdd = this._currentEditing.isAdd; const index = this._currentEditing.index; const url = isAdd ? './api/clients' : './api/clients/' + index; 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(result) { if (!result.ok) { var msg = (result.data && result.data.error) ? result.data.error : 'Save failed'; if (window.huntarrUI && window.huntarrUI.showNotification) { window.huntarrUI.showNotification(msg, 'error'); } return; } var data = result.data; if (window.SettingsForms && window.SettingsForms.refreshClientsList) { window.SettingsForms.refreshClientsList(); } // Refresh NZB Hunt sidebar group visibility (may have added/removed NZB Hunt client) if (window.huntarrUI && typeof window.huntarrUI._refreshNzbHuntSidebarGroup === 'function') { window.huntarrUI._refreshNzbHuntSidebarGroup(); } if (window.huntarrUI && window.huntarrUI.showNotification) { window.huntarrUI.showNotification(isAdd ? 'Client added.' : 'Client updated.', 'success'); } // Don't auto-navigate back to collection after saving a client. // The user may want to add more clients. The wizard banner on the // clients page handles the "continue" flow when the user is ready. if (window.SettingsForms && window.SettingsForms._currentEditing) { window.SettingsForms._currentEditing.isAdd = false; if (data && data.index !== undefined) { window.SettingsForms._currentEditing.index = data.index; } else if (!isAdd) { window.SettingsForms._currentEditing.index = index; } } }) .catch(function(err) { if (window.huntarrUI && window.huntarrUI.showNotification) { window.huntarrUI.showNotification(err.message || 'Failed to save client', 'error'); } }); }; 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'; // NZB Hunt (built-in) - no connection status; it's built-in and managed in NZB Hunt Settings if (type === 'nzbhunt' || type === 'nzb_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({ type: type, host: host, port: parseInt(port, 10) || 8080, api_key: apiKey, username: username, 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') + ''; }); }; })();