mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 08:49:13 +00:00
feat(indexers): test API from settings (#829)
* refactor(indexers): test api clients * feat(indexers): test api connection * fix(indexers): api client tests * refactor: indexer api clients * feat: add Toasts for indexer api tests * fix: failing red tests
This commit is contained in:
parent
fb9dcc23a0
commit
f3cfeed8cd
19 changed files with 475 additions and 191 deletions
|
@ -135,7 +135,8 @@ export const APIClient = {
|
|||
getSchema: () => appClient.Get<IndexerDefinition[]>("api/indexer/schema"),
|
||||
create: (indexer: Indexer) => appClient.Post<Indexer>("api/indexer", indexer),
|
||||
update: (indexer: Indexer) => appClient.Put("api/indexer", indexer),
|
||||
delete: (id: number) => appClient.Delete(`api/indexer/${id}`)
|
||||
delete: (id: number) => appClient.Delete(`api/indexer/${id}`),
|
||||
testApi: (req: IndexerTestApiReq) => appClient.Post<IndexerTestApiReq>(`api/indexer/${req.id}/api/test`, req)
|
||||
},
|
||||
irc: {
|
||||
getNetworks: () => appClient.Get<IrcNetworkWithHealth[]>("api/irc"),
|
||||
|
|
|
@ -22,6 +22,7 @@ interface SlideOverProps<DataType> {
|
|||
isTesting?: boolean;
|
||||
isTestSuccessful?: boolean;
|
||||
isTestError?: boolean;
|
||||
extraButtons?: (values: DataType) => React.ReactNode;
|
||||
}
|
||||
|
||||
function SlideOver<DataType>({
|
||||
|
@ -37,7 +38,8 @@ function SlideOver<DataType>({
|
|||
testFn,
|
||||
isTesting,
|
||||
isTestSuccessful,
|
||||
isTestError
|
||||
isTestError,
|
||||
extraButtons
|
||||
}: SlideOverProps<DataType>): React.ReactElement {
|
||||
const cancelModalButtonRef = useRef<HTMLInputElement | null>(null);
|
||||
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false);
|
||||
|
@ -125,6 +127,10 @@ function SlideOver<DataType>({
|
|||
</button>
|
||||
)}
|
||||
<div>
|
||||
{!!values && extraButtons !== undefined && (
|
||||
extraButtons(values)
|
||||
)}
|
||||
|
||||
{testFn && (
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Fragment, useState } from "react";
|
||||
import React, { Fragment, useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { useMutation, useQuery } from "react-query";
|
||||
import Select, { components, ControlProps, InputProps, MenuProps, OptionProps } from "react-select";
|
||||
|
@ -8,7 +8,7 @@ import { Field, Form, Formik, FormikValues } from "formik";
|
|||
import { XMarkIcon } from "@heroicons/react/24/solid";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
|
||||
import { sleep } from "../../utils";
|
||||
import { classNames, sleep } from "../../utils";
|
||||
import { queryClient } from "../../App";
|
||||
import DEBUG from "../../components/debug";
|
||||
import { APIClient } from "../../api/APIClient";
|
||||
|
@ -576,6 +576,129 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
|
|||
);
|
||||
}
|
||||
|
||||
interface TestApiButtonProps {
|
||||
values: FormikValues;
|
||||
}
|
||||
|
||||
function TestApiButton({ values }: TestApiButtonProps) {
|
||||
const [isTesting, setIsTesting] = useState(false);
|
||||
const [isSuccessfulTest, setIsSuccessfulTest] = useState(false);
|
||||
const [isErrorTest, setIsErrorTest] = useState(false);
|
||||
|
||||
if (!values.settings.api_key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const testApiMutation = useMutation(
|
||||
(req: IndexerTestApiReq) => APIClient.indexers.testApi(req),
|
||||
{
|
||||
onMutate: () => {
|
||||
setIsTesting(true);
|
||||
setIsErrorTest(false);
|
||||
setIsSuccessfulTest(false);
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast.custom((t) => <Toast type="success" body="API test successful!" t={t} />);
|
||||
|
||||
sleep(1000)
|
||||
.then(() => {
|
||||
setIsTesting(false);
|
||||
setIsSuccessfulTest(true);
|
||||
})
|
||||
.then(() => {
|
||||
sleep(2500).then(() => {
|
||||
setIsSuccessfulTest(false);
|
||||
});
|
||||
});
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast.custom((t) => <Toast type="error" body={error.message} t={t} />);
|
||||
|
||||
setIsTesting(false);
|
||||
setIsErrorTest(true);
|
||||
sleep(2500).then(() => {
|
||||
setIsErrorTest(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const testApi = () => {
|
||||
const req: IndexerTestApiReq = {
|
||||
id: values.id,
|
||||
api_key: values.settings.api_key
|
||||
};
|
||||
|
||||
if (values.settings.api_user) {
|
||||
req.api_user = values.settings.api_user;
|
||||
}
|
||||
|
||||
testApiMutation.mutate(req);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={classNames(
|
||||
isSuccessfulTest
|
||||
? "text-green-500 border-green-500 bg-green-50"
|
||||
: isErrorTest
|
||||
? "text-red-500 border-red-500 bg-red-50"
|
||||
: "border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 hover:bg-gray-50 focus:border-rose-700 active:bg-rose-700",
|
||||
isTesting ? "cursor-not-allowed" : "",
|
||||
"mr-2 float-left items-center px-4 py-2 border font-medium rounded-md shadow-sm text-sm transition ease-in-out duration-150 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-blue-500"
|
||||
)}
|
||||
disabled={isTesting}
|
||||
onClick={testApi}
|
||||
>
|
||||
{isTesting ? (
|
||||
<svg
|
||||
className="animate-spin h-5 w-5 text-green-500"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
className="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="4"
|
||||
></circle>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
) : isSuccessfulTest ? (
|
||||
"OK!"
|
||||
) : isErrorTest ? (
|
||||
"ERROR"
|
||||
) : (
|
||||
"Test API"
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
interface IndexerUpdateInitialValues {
|
||||
id: number;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
identifier: string;
|
||||
implementation: string;
|
||||
base_url: string;
|
||||
settings: {
|
||||
api_key?: string;
|
||||
api_user?: string;
|
||||
authkey?: string;
|
||||
torrent_pass?: string;
|
||||
}
|
||||
}
|
||||
|
||||
interface UpdateProps {
|
||||
isOpen: boolean;
|
||||
toggle: () => void;
|
||||
|
@ -635,10 +758,10 @@ export function IndexerUpdateForm({ isOpen, toggle, indexer }: UpdateProps) {
|
|||
);
|
||||
};
|
||||
|
||||
const initialValues = {
|
||||
const initialValues: IndexerUpdateInitialValues = {
|
||||
id: indexer.id,
|
||||
name: indexer.name,
|
||||
enabled: indexer.enabled,
|
||||
enabled: indexer.enabled || false,
|
||||
identifier: indexer.identifier,
|
||||
implementation: indexer.implementation,
|
||||
base_url: indexer.base_url,
|
||||
|
@ -660,6 +783,7 @@ export function IndexerUpdateForm({ isOpen, toggle, indexer }: UpdateProps) {
|
|||
deleteAction={deleteAction}
|
||||
onSubmit={onSubmit}
|
||||
initialValues={initialValues}
|
||||
extraButtons={(values) => <TestApiButton values={values as FormikValues} />}
|
||||
>
|
||||
{() => (
|
||||
<div className="py-2 space-y-6 sm:py-0 sm:space-y-0 divide-y divide-gray-200 dark:divide-gray-700">
|
||||
|
|
7
web/src/types/Indexer.d.ts
vendored
7
web/src/types/Indexer.d.ts
vendored
|
@ -78,3 +78,10 @@ interface IndexerParseMatch {
|
|||
torrentUrl: string;
|
||||
encode: string[];
|
||||
}
|
||||
|
||||
interface IndexerTestApiReq {
|
||||
id?: number;
|
||||
identifier?: string;
|
||||
api_user?: string;
|
||||
api_key: string;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue