Files
ReadMeABook/src/app/setup/steps/ProwlarrStep.tsx
T
kikootwo 411b5f88a4 Add per-indexer ratio-based seeding policy
Introduce a per-indexer ratioLimit alongside seedingTimeMinutes to control torrent cleanup. Updates include: documentation (scheduler and settings pages), types and API (saved indexer config now includes ratioLimit), setup and management UI (new TorrentSeedingFields component, modal wiring, validation and handlers), and processor logic (cleanup-seeded-torrents now requires AND-semantics between time and ratio; 0 disables a criterion, both 0 = never cleaned, undefined client ratio with ratioLimit>0 = not met). Tests were added/updated to cover ratio-only, time+ratio, missing-ratio, and UI interactions. Default behavior: ratioLimit defaults to 0 (no ratio requirement).
2026-05-18 15:07:50 -04:00

160 lines
5.0 KiB
TypeScript

/**
* Component: Setup Wizard Prowlarr Step
* Documentation: documentation/setup-wizard.md
*/
'use client';
import { useState } from 'react';
import { Button } from '@/components/ui/Button';
import { Input } from '@/components/ui/Input';
import { IndexerManagement } from '@/components/admin/indexers/IndexerManagement';
interface ProwlarrStepProps {
prowlarrUrl: string;
prowlarrApiKey: string;
prowlarrIndexers: SelectedIndexer[];
onUpdate: (field: string, value: any) => void;
onNext: () => void;
onBack: () => void;
}
interface SelectedIndexer {
id: number;
name: string;
protocol: string;
priority: number;
seedingTimeMinutes?: number; // Torrents only
ratioLimit?: number; // Torrents only (0 = no ratio requirement)
removeAfterProcessing?: boolean; // Usenet only
rssEnabled: boolean;
audiobookCategories: number[]; // Categories for audiobook searches
ebookCategories: number[]; // Categories for ebook searches
}
export function ProwlarrStep({
prowlarrUrl,
prowlarrApiKey,
prowlarrIndexers,
onUpdate,
onNext,
onBack,
}: ProwlarrStepProps) {
const [configuredIndexers, setConfiguredIndexers] = useState<SelectedIndexer[]>(prowlarrIndexers);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
// Update both local and parent state when indexers change
const handleIndexersChange = (indexers: SelectedIndexer[]) => {
setConfiguredIndexers(indexers);
onUpdate('prowlarrIndexers', indexers);
};
const handleNext = () => {
setErrorMessage(null);
if (!prowlarrUrl || !prowlarrApiKey) {
setErrorMessage('Please enter Prowlarr URL and API key');
return;
}
if (configuredIndexers.length === 0) {
setErrorMessage('Please add at least one indexer');
return;
}
onNext();
};
return (
<div className="space-y-6">
<div>
<h2 className="text-2xl font-bold text-gray-900 dark:text-gray-100">
Configure Prowlarr
</h2>
<p className="text-gray-600 dark:text-gray-400 mt-2">
Connect to Prowlarr to search for audiobooks across multiple indexers.
</p>
</div>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Prowlarr URL
</label>
<Input
type="url"
placeholder="http://localhost:9696"
value={prowlarrUrl}
onChange={(e) => onUpdate('prowlarrUrl', e.target.value)}
/>
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
The URL where Prowlarr is running (include port)
</p>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
API Key
</label>
<Input
type="password"
placeholder="Enter your Prowlarr API key"
value={prowlarrApiKey}
onChange={(e) => onUpdate('prowlarrApiKey', e.target.value)}
/>
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
Find this in Prowlarr Settings General Security API Key
</p>
</div>
{errorMessage && (
<div className="rounded-lg p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800">
<div className="flex gap-3">
<svg
className="w-6 h-6 flex-shrink-0 text-red-600 dark:text-red-400"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
clipRule="evenodd"
/>
</svg>
<div>
<h3 className="text-sm font-medium text-red-800 dark:text-red-200">
Error
</h3>
<p className="text-sm mt-1 text-red-700 dark:text-red-300">
{errorMessage}
</p>
</div>
</div>
</div>
)}
{/* Indexer Management Component */}
<div className="border-t border-gray-200 dark:border-gray-700 pt-6">
<IndexerManagement
prowlarrUrl={prowlarrUrl}
prowlarrApiKey={prowlarrApiKey}
mode="wizard"
initialIndexers={configuredIndexers}
onIndexersChange={handleIndexersChange}
/>
</div>
</div>
{/* Navigation Buttons */}
<div className="flex justify-between pt-6 border-t border-gray-200 dark:border-gray-700">
<Button onClick={onBack} variant="outline">
Back
</Button>
<Button onClick={handleNext} variant="primary">
Next
</Button>
</div>
</div>
);
}