mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 08:49:13 +00:00
260 lines
8.1 KiB
TypeScript
260 lines
8.1 KiB
TypeScript
/*
|
|
* Copyright (c) 2021 - 2024, Ludvig Lundgren and the autobrr contributors.
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
import { useState } from "react";
|
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
import { toast } from "react-hot-toast";
|
|
import { useFormikContext } from "formik";
|
|
|
|
import { APIClient } from "@api/APIClient";
|
|
import { FeedKeys } from "@api/query_keys";
|
|
import Toast from "@components/notifications/Toast";
|
|
import { SlideOver } from "@components/panels";
|
|
import { NumberFieldWide, PasswordFieldWide, SwitchGroupWide, TextFieldWide } from "@components/inputs";
|
|
import { SelectFieldBasic } from "@components/inputs/select_wide";
|
|
import { componentMapType } from "./DownloadClientForms";
|
|
import { sleep } from "@utils";
|
|
import { ImplementationBadges } from "@screens/settings/Indexer";
|
|
import { FeedDownloadTypeOptions } from "@domain/constants";
|
|
|
|
|
|
interface UpdateProps {
|
|
isOpen: boolean;
|
|
toggle: () => void;
|
|
feed: Feed;
|
|
}
|
|
|
|
interface InitialValues {
|
|
id: number;
|
|
indexer: IndexerMinimal;
|
|
enabled: boolean;
|
|
type: FeedType;
|
|
name: string;
|
|
url: string;
|
|
api_key: string;
|
|
cookie: string;
|
|
interval: number;
|
|
timeout: number;
|
|
max_age: number;
|
|
settings: FeedSettings;
|
|
}
|
|
|
|
export function FeedUpdateForm({ isOpen, toggle, feed }: UpdateProps) {
|
|
const [isTesting, setIsTesting] = useState(false);
|
|
const [isTestSuccessful, setIsSuccessfulTest] = useState(false);
|
|
const [isTestError, setIsErrorTest] = useState(false);
|
|
|
|
const queryClient = useQueryClient();
|
|
|
|
const mutation = useMutation({
|
|
mutationFn: (feed: Feed) => APIClient.feeds.update(feed),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: FeedKeys.lists() });
|
|
|
|
toast.custom((t) => <Toast type="success" body={`${feed.name} was updated successfully`} t={t} />);
|
|
toggle();
|
|
}
|
|
});
|
|
|
|
const onSubmit = (formData: unknown) => mutation.mutate(formData as Feed);
|
|
|
|
const deleteMutation = useMutation({
|
|
mutationFn: (feedID: number) => APIClient.feeds.delete(feedID),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: FeedKeys.lists() });
|
|
|
|
toast.custom((t) => <Toast type="success" body={`${feed.name} was deleted.`} t={t} />);
|
|
}
|
|
});
|
|
|
|
const deleteAction = () => deleteMutation.mutate(feed.id);
|
|
|
|
const testFeedMutation = useMutation({
|
|
mutationFn: (feed: Feed) => APIClient.feeds.test(feed),
|
|
onMutate: () => {
|
|
setIsTesting(true);
|
|
setIsErrorTest(false);
|
|
setIsSuccessfulTest(false);
|
|
},
|
|
onSuccess: () => {
|
|
toast.custom((t) => <Toast type="success" body={`${feed.name} test OK!`} t={t} />);
|
|
|
|
sleep(1000)
|
|
.then(() => {
|
|
setIsTesting(false);
|
|
setIsSuccessfulTest(true);
|
|
})
|
|
.then(() => {
|
|
sleep(2500).then(() => {
|
|
setIsSuccessfulTest(false);
|
|
});
|
|
});
|
|
},
|
|
onError: () => {
|
|
setIsTesting(false);
|
|
setIsErrorTest(true);
|
|
sleep(2500).then(() => {
|
|
setIsErrorTest(false);
|
|
});
|
|
}
|
|
});
|
|
|
|
const testFeed = (data: unknown) => testFeedMutation.mutate(data as Feed);
|
|
|
|
const initialValues: InitialValues = {
|
|
id: feed.id,
|
|
indexer: feed.indexer,
|
|
enabled: feed.enabled,
|
|
type: feed.type,
|
|
name: feed.name,
|
|
url: feed.url,
|
|
api_key: feed.api_key,
|
|
cookie: feed.cookie || "",
|
|
interval: feed.interval,
|
|
timeout: feed.timeout,
|
|
max_age: feed.max_age,
|
|
settings: feed.settings
|
|
};
|
|
|
|
return (
|
|
<SlideOver<InitialValues>
|
|
type="UPDATE"
|
|
title="Feed"
|
|
isOpen={isOpen}
|
|
toggle={toggle}
|
|
onSubmit={onSubmit}
|
|
deleteAction={deleteAction}
|
|
initialValues={initialValues}
|
|
testFn={testFeed}
|
|
isTesting={isTesting}
|
|
isTestSuccessful={isTestSuccessful}
|
|
isTestError={isTestError}
|
|
>
|
|
{(values) => (
|
|
<div>
|
|
<TextFieldWide name="name" label="Name" required={true} />
|
|
|
|
<div className="space-y-4 divide-y divide-gray-200 dark:divide-gray-700">
|
|
<div
|
|
className="py-4 flex items-center justify-between space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-4">
|
|
<div>
|
|
<label
|
|
htmlFor="type"
|
|
className="block text-sm font-medium text-gray-900 dark:text-white"
|
|
>
|
|
Type
|
|
</label>
|
|
</div>
|
|
<div className="flex justify-end sm:col-span-2">
|
|
{ImplementationBadges[feed.type.toLowerCase()]}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
|
|
<SwitchGroupWide name="enabled" label="Enabled" />
|
|
</div>
|
|
</div>
|
|
{componentMap[values.type]}
|
|
</div>
|
|
)}
|
|
</SlideOver>
|
|
);
|
|
}
|
|
|
|
function WarningLabel() {
|
|
return (
|
|
<div className="px-4 py-1">
|
|
<span className="w-full block px-2 py-2 bg-red-300 dark:bg-red-400 text-red-900 dark:text-red-900 text-sm rounded">
|
|
<span className="font-semibold">
|
|
Warning: Indexers might ban you for too low interval!
|
|
</span>
|
|
<span className="ml-1">
|
|
Read the indexer rules.
|
|
</span>
|
|
</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function FormFieldsTorznab() {
|
|
const {
|
|
values: { interval }
|
|
} = useFormikContext<InitialValues>();
|
|
|
|
return (
|
|
<div className="border-t border-gray-200 dark:border-gray-700 py-5">
|
|
<TextFieldWide
|
|
name="url"
|
|
label="URL"
|
|
help="Torznab url"
|
|
/>
|
|
|
|
<SelectFieldBasic name="settings.download_type" label="Download type" options={FeedDownloadTypeOptions} />
|
|
|
|
<PasswordFieldWide name="api_key" label="API key" />
|
|
|
|
{interval < 15 && <WarningLabel />}
|
|
<NumberFieldWide name="interval" label="Refresh interval" help="Minutes. Recommended 15-30. Too low and risk ban."/>
|
|
|
|
<NumberFieldWide name="timeout" label="Refresh timeout" help="Seconds to wait before cancelling refresh."/>
|
|
<NumberFieldWide name="max_age" label="Max age" help="Enter the maximum age of feed content in seconds. It is recommended to set this to '0' to disable the age filter, ensuring all items in the feed are processed."/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function FormFieldsNewznab() {
|
|
const {
|
|
values: { interval }
|
|
} = useFormikContext<InitialValues>();
|
|
|
|
return (
|
|
<div className="border-t border-gray-200 dark:border-gray-700 py-5">
|
|
<TextFieldWide
|
|
name="url"
|
|
label="URL"
|
|
help="Newznab url"
|
|
/>
|
|
|
|
<PasswordFieldWide name="api_key" label="API key" />
|
|
|
|
{interval < 15 && <WarningLabel />}
|
|
<NumberFieldWide name="interval" label="Refresh interval" help="Minutes. Recommended 15-30. Too low and risk ban."/>
|
|
|
|
<NumberFieldWide name="timeout" label="Refresh timeout" help="Seconds to wait before cancelling refresh."/>
|
|
<NumberFieldWide name="max_age" label="Max age" help="Enter the maximum age of feed content in seconds. It is recommended to set this to '0' to disable the age filter, ensuring all items in the feed are processed."/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function FormFieldsRSS() {
|
|
const {
|
|
values: { interval }
|
|
} = useFormikContext<InitialValues>();
|
|
|
|
return (
|
|
<div className="border-t border-gray-200 dark:border-gray-700 py-5">
|
|
<TextFieldWide
|
|
name="url"
|
|
label="URL"
|
|
help="RSS url"
|
|
/>
|
|
|
|
<SelectFieldBasic name="settings.download_type" label="Download type" options={FeedDownloadTypeOptions} />
|
|
|
|
{interval < 15 && <WarningLabel />}
|
|
<NumberFieldWide name="interval" label="Refresh interval" help="Minutes. Recommended 15-30. Too low and risk ban."/>
|
|
<NumberFieldWide name="timeout" label="Refresh timeout" help="Seconds to wait before cancelling refresh."/>
|
|
<NumberFieldWide name="max_age" label="Max age" help="Enter the maximum age of feed content in seconds. It is recommended to set this to '0' to disable the age filter, ensuring all items in the feed are processed."/>
|
|
|
|
<PasswordFieldWide name="cookie" label="Cookie" help="Not commonly used" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const componentMap: componentMapType = {
|
|
TORZNAB: <FormFieldsTorznab />,
|
|
NEWZNAB: <FormFieldsNewznab />,
|
|
RSS: <FormFieldsRSS />
|
|
};
|