Files
ReadMeABook/src/app/admin/settings/tabs/IndexersTab/useIndexersSettings.ts
T
kikootwo 20798b3dc0 Add RDT-Client support and Prowlarr prompt
Introduce RDT-Client integration and related UI/behavior changes.

- Add RDTClientService extending QBittorrentService with RDT-specific behavior (stale-torrent deletion, postProcess cleanup, no-op categories).
- Register 'rdtclient' in supported client types, display names, and protocol mapping; create RDT client factory in DownloadClientManager.
- Add RDT-Client card to DownloadClientManagement UI and placeholder URL in DownloadClientModal.
- Update qbittorrent service: omit per-torrent savepath/sequential options (favor category/automatic management), make several methods protected, and clean up related comments.
- Make organize-files.processor treat rdtclient as a special-case for cleanup (remove local torrent entries after organize).
- Add prowlarr service singleton invalidation and call it when Prowlarr settings are updated so background jobs pick up new credentials.
- Add confirmation flow when changing Prowlarr URL/API key: new useIndexersSettings logic to detect credential changes, prompt ConfirmModal from IndexersTab, and optionally clear configured indexers on confirmed change.

These changes ensure Real-Debrid-backed qBittorrent-compatible clients are supported correctly and that switching Prowlarr credentials is handled safely.
2026-02-17 17:03:21 -05:00

144 lines
4.1 KiB
TypeScript

/**
* Component: Indexers Settings Tab - Custom Hook
* Documentation: documentation/settings-pages.md
*/
'use client';
import { useState, useCallback } from 'react';
import { fetchWithAuth } from '@/lib/utils/api';
import type { TestResult } from '../../lib/types';
interface UseIndexersSettingsProps {
prowlarrUrl: string;
prowlarrApiKey: string;
originalProwlarrUrl: string;
originalProwlarrApiKey: string;
configuredIndexersCount: number;
onValidationChange: (isValid: boolean) => void;
onRefreshIndexers?: () => Promise<void>;
onClearIndexers: () => void;
}
export function useIndexersSettings({
prowlarrUrl,
prowlarrApiKey,
originalProwlarrUrl,
originalProwlarrApiKey,
configuredIndexersCount,
onValidationChange,
onRefreshIndexers,
onClearIndexers,
}: UseIndexersSettingsProps) {
const [testing, setTesting] = useState(false);
const [testResult, setTestResult] = useState<TestResult | null>(null);
const [showConnectionChangeConfirm, setShowConnectionChangeConfirm] = useState(false);
/**
* Detect if the Prowlarr URL or API key has changed from the saved values.
* A masked API key (starting with dots) means the user hasn't touched it.
*/
const hasConnectionChanged = useCallback((): boolean => {
const urlChanged = prowlarrUrl.trim() !== originalProwlarrUrl.trim();
const apiKeyChanged = !prowlarrApiKey.startsWith('••••') &&
prowlarrApiKey !== originalProwlarrApiKey;
return urlChanged || apiKeyChanged;
}, [prowlarrUrl, prowlarrApiKey, originalProwlarrUrl, originalProwlarrApiKey]);
/**
* Execute the actual Prowlarr connection test
*/
const executeTest = async (shouldClearIndexers: boolean) => {
setTesting(true);
setTestResult(null);
try {
const response = await fetchWithAuth('/api/admin/settings/test-prowlarr', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
url: prowlarrUrl,
apiKey: prowlarrApiKey,
}),
});
const data = await response.json();
if (data.success) {
onValidationChange(true);
if (shouldClearIndexers) {
onClearIndexers();
setTestResult({
success: true,
message: `Connected to Prowlarr. Found ${data.indexers?.length || 0} indexers. Previous indexer configurations have been removed — please re-add indexers from the new instance.`,
});
} else {
setTestResult({
success: true,
message: `Connected to Prowlarr. Found ${data.indexers?.length || 0} indexers`,
});
// Refresh indexers from database if callback provided
if (onRefreshIndexers) {
await onRefreshIndexers();
}
}
} else {
onValidationChange(false);
setTestResult({
success: false,
message: data.error || 'Connection failed',
});
}
} catch (error) {
onValidationChange(false);
const errorMsg = error instanceof Error ? error.message : 'Failed to test connection';
setTestResult({
success: false,
message: errorMsg,
});
} finally {
setTesting(false);
}
};
/**
* Handle test connection click — shows confirmation if credentials changed
* and there are existing configured indexers.
*/
const testConnection = async () => {
if (hasConnectionChanged() && configuredIndexersCount > 0) {
setShowConnectionChangeConfirm(true);
return;
}
await executeTest(false);
};
/**
* User confirmed the credential change — proceed with test and clear indexers on success
*/
const confirmConnectionChange = async () => {
setShowConnectionChangeConfirm(false);
await executeTest(true);
};
/**
* User cancelled the credential change confirmation
*/
const cancelConnectionChange = () => {
setShowConnectionChangeConfirm(false);
};
return {
testing,
testResult,
testConnection,
showConnectionChangeConfirm,
confirmConnectionChange,
cancelConnectionChange,
configuredIndexersCount,
};
}