diff --git a/client/src/components/settings/FlareSolverrSettings.tsx b/client/src/components/settings/FlareSolverrSettings.tsx new file mode 100644 index 0000000..35c32ab --- /dev/null +++ b/client/src/components/settings/FlareSolverrSettings.tsx @@ -0,0 +1,182 @@ +import customSonner from '@/components/CustomSonner'; +import { Button } from '@/components/ui/button'; +import { Checkbox } from '@/components/ui/checkbox'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { rpc } from '@/lib/rpc'; +import type { DbUserSettings } from '@server/db/app/app-schema'; +import { Loader2 } from 'lucide-react'; +import { useState } from 'react'; +import type { Dispatch, SetStateAction } from 'react'; + +type FlareSolverrSettingsProps = { + enabled: boolean; + serverUrl: string; + timeoutSeconds: number; + setData: Dispatch>; +}; + +export default function FlareSolverrSettings({ + enabled, + serverUrl, + timeoutSeconds, + setData, +}: FlareSolverrSettingsProps) { + const [isTestingConnection, setIsTestingConnection] = useState(false); + + const handleTestConnection = async () => { + if (!enabled) { + customSonner({ variant: 'error', text: 'FlareSolverr is disabled' }); + return; + } + + if (!serverUrl) { + customSonner({ variant: 'error', text: 'FlareSolverr URL is required' }); + return; + } + + if (!isValidUrl(serverUrl)) { + customSonner({ + variant: 'error', + text: 'Invalid FlareSolverr URL. It should include the protocol.', + }); + return; + } + + try { + setIsTestingConnection(true); + const response = await rpc.api.flaresolverr.verify.$post({ + json: { + flaresolverrUrl: serverUrl, + timeoutSeconds, + }, + }); + const payload = await response.json(); + + if (!response.ok || !payload?.success) { + customSonner({ + variant: 'error', + text: payload?.message ?? 'Failed to test FlareSolverr connection', + }); + return; + } + + customSonner({ text: 'FlareSolverr connection successful' }); + } catch (error) { + const description = + error instanceof Error ? error.message : String(error); + customSonner({ + variant: 'error', + text: 'Failed to test FlareSolverr connection', + description, + }); + } finally { + setIsTestingConnection(false); + } + }; + + return ( +
+
+

+ FlareSolverr Settings +

+
+ +
+
+
+ + setData((data) => { + if (!data) return data; + return { + ...data, + flaresolverrEnabled: checked === true, + }; + }) + } + /> + +
+
+ +
+
+

Server URL

+ + setData((data) => { + if (!data) return data; + return { + ...data, + flaresolverrUrl: event.target.value, + }; + }) + } + /> +
+
+ +
+
+ +
+

Timeout

+ + setData((data) => { + if (!data) return data; + return { + ...data, + flaresolverrTimeoutSeconds: parseTimeoutSeconds( + event.target.value, + ), + }; + }) + } + /> +
+
+
+ ); +} + +// Helpers + +function isValidUrl(value: string): boolean { + try { + return Boolean(new URL(value)); + } catch { + return false; + } +} + +function parseTimeoutSeconds(value: string): number { + const parsed = Number.parseInt(value, 10); + if (!Number.isFinite(parsed)) return 60; + return Math.min(Math.max(parsed, 1), 300); +} diff --git a/client/src/routes/dashboard/settings.tsx b/client/src/routes/dashboard/settings.tsx index 1d15f8c..8c274b2 100644 --- a/client/src/routes/dashboard/settings.tsx +++ b/client/src/routes/dashboard/settings.tsx @@ -1,5 +1,6 @@ import customSonner from '@/components/CustomSonner'; import GeneralSettings from '@/components/settings/GeneralSettings'; +import FlareSolverrSettings from '@/components/settings/FlareSolverrSettings'; import JackettSettings from '@/components/settings/JackettSettings'; import NotificationSettings from '@/components/settings/NotificationSettings'; import SettingsMenu from '@/components/settings/SettingsMenu'; @@ -36,7 +37,7 @@ export default function Settings() { if (typeof next === 'function') { const updater = next as ( - prevState: DbUserSettings | null | undefined + prevState: DbUserSettings | null | undefined, ) => DbUserSettings | null | undefined; const updated = updater(resolvedCurrentData); return updated ?? undefined; @@ -45,7 +46,7 @@ export default function Settings() { return next ?? undefined; }); }, - [settingsData] + [settingsData], ); const handleSave = async () => { @@ -66,11 +67,11 @@ export default function Settings() { if (isLoadingSettings) { return ( <> -
+
-
- +
+
); @@ -91,32 +92,40 @@ export default function Settings() { syncInterval={data?.syncInterval ?? 0} setData={setData} /> - + - + - + - + + +