mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 08:49:13 +00:00
feat: add support for proxies to use with IRC and Indexers (#1421)
* feat: add support for proxies * fix(http): release handler * fix(migrations): define proxy early * fix(migrations): pg proxy * fix(proxy): list update delete * fix(proxy): remove log and imports * feat(irc): use proxy * feat(irc): tests * fix(web): update imports for ProxyForms.tsx * fix(database): migration * feat(proxy): test * feat(proxy): validate proxy type * feat(proxy): validate and test * feat(proxy): improve validate and test * feat(proxy): fix db schema * feat(proxy): add db tests * feat(proxy): handle http errors * fix(http): imports * feat(proxy): use proxy for indexer downloads * feat(proxy): indexerforms select proxy * feat(proxy): handle torrent download * feat(proxy): skip if disabled * feat(proxy): imports * feat(proxy): implement in Feeds * feat(proxy): update helper text indexer proxy * feat(proxy): add internal cache
This commit is contained in:
parent
472d327308
commit
bc0f4cc055
59 changed files with 2533 additions and 371 deletions
|
@ -16,14 +16,15 @@ import { classNames, sleep } from "@utils";
|
|||
import { DEBUG } from "@components/debug";
|
||||
import { APIClient } from "@api/APIClient";
|
||||
import { FeedKeys, IndexerKeys, ReleaseKeys } from "@api/query_keys";
|
||||
import { IndexersSchemaQueryOptions } from "@api/queries";
|
||||
import { IndexersSchemaQueryOptions, ProxiesQueryOptions } from "@api/queries";
|
||||
import { SlideOver } from "@components/panels";
|
||||
import Toast from "@components/notifications/Toast";
|
||||
import { PasswordFieldWide, SwitchGroupWide, TextFieldWide } from "@components/inputs";
|
||||
import { PasswordFieldWide, SwitchButton, SwitchGroupWide, TextFieldWide } from "@components/inputs";
|
||||
import { SelectFieldBasic, SelectFieldCreatable } from "@components/inputs/select_wide";
|
||||
import { FeedDownloadTypeOptions } from "@domain/constants";
|
||||
import { DocsLink } from "@components/ExternalLink";
|
||||
import * as common from "@components/inputs/common";
|
||||
import { SelectField } from "@forms/settings/IrcForms";
|
||||
|
||||
// const isRequired = (message: string) => (value?: string | undefined) => (!!value ? undefined : message);
|
||||
|
||||
|
@ -254,7 +255,7 @@ type SelectValue = {
|
|||
value: string;
|
||||
};
|
||||
|
||||
interface AddProps {
|
||||
export interface AddProps {
|
||||
isOpen: boolean;
|
||||
toggle: () => void;
|
||||
}
|
||||
|
@ -718,6 +719,8 @@ interface IndexerUpdateInitialValues {
|
|||
identifier_external: string;
|
||||
implementation: string;
|
||||
base_url: string;
|
||||
use_proxy?: boolean;
|
||||
proxy_id?: number;
|
||||
settings: {
|
||||
api_key?: string;
|
||||
api_user?: string;
|
||||
|
@ -735,6 +738,8 @@ interface UpdateProps {
|
|||
export function IndexerUpdateForm({ isOpen, toggle, indexer }: UpdateProps) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const proxies = useQuery(ProxiesQueryOptions());
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: (indexer: Indexer) => APIClient.indexers.update(indexer),
|
||||
onSuccess: () => {
|
||||
|
@ -813,6 +818,8 @@ export function IndexerUpdateForm({ isOpen, toggle, indexer }: UpdateProps) {
|
|||
identifier_external: indexer.identifier_external,
|
||||
implementation: indexer.implementation,
|
||||
base_url: indexer.base_url,
|
||||
use_proxy: indexer.use_proxy,
|
||||
proxy_id: indexer.proxy_id,
|
||||
settings: indexer.settings?.reduce(
|
||||
(o: Record<string, string>, obj: IndexerSetting) => ({
|
||||
...o,
|
||||
|
@ -833,7 +840,7 @@ export function IndexerUpdateForm({ isOpen, toggle, indexer }: UpdateProps) {
|
|||
initialValues={initialValues}
|
||||
extraButtons={(values) => <TestApiButton values={values as FormikValues} show={indexer.implementation === "irc" && indexer.supports.includes("api")} />}
|
||||
>
|
||||
{() => (
|
||||
{(values) => (
|
||||
<div className="py-2 space-y-6 sm:py-0 sm:space-y-0 divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<div className="space-y-1 p-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||
<label
|
||||
|
@ -863,14 +870,15 @@ export function IndexerUpdateForm({ isOpen, toggle, indexer }: UpdateProps) {
|
|||
tooltip={
|
||||
<div>
|
||||
<p>External Identifier for use with ARRs to get features like seed limits working.</p>
|
||||
<br />
|
||||
<p>This needs to match the indexer name in your ARR. If using Prowlarr it will likely be "{indexer.name} (Prowlarr)"</p>
|
||||
<br />
|
||||
<DocsLink href="https://autobrr.com/configuration/indexers#setup" />
|
||||
<br/>
|
||||
<p>This needs to match the indexer name in your ARR. If using Prowlarr it will likely be
|
||||
"{indexer.name} (Prowlarr)"</p>
|
||||
<br/>
|
||||
<DocsLink href="https://autobrr.com/configuration/indexers#setup"/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<SwitchGroupWide name="enabled" label="Enabled" />
|
||||
<SwitchGroupWide name="enabled" label="Enabled"/>
|
||||
|
||||
{indexer.implementation == "irc" && (
|
||||
<SelectFieldCreatable
|
||||
|
@ -882,6 +890,31 @@ export function IndexerUpdateForm({ isOpen, toggle, indexer }: UpdateProps) {
|
|||
)}
|
||||
|
||||
{renderSettingFields(indexer.settings)}
|
||||
|
||||
<div className="border-t border-gray-200 dark:border-gray-700 py-4">
|
||||
<div className="flex justify-between px-4">
|
||||
<div className="space-y-1">
|
||||
<DialogTitle className="text-lg font-medium text-gray-900 dark:text-white">
|
||||
Proxy
|
||||
</DialogTitle>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
Set a proxy to be used for downloads of .torrent files and feeds.
|
||||
</p>
|
||||
</div>
|
||||
<SwitchButton name="use_proxy" />
|
||||
</div>
|
||||
|
||||
{values.use_proxy === true && (
|
||||
<div className="py-4 pt-6">
|
||||
<SelectField<number>
|
||||
name="proxy_id"
|
||||
label="Select proxy"
|
||||
placeholder="Select a proxy"
|
||||
options={proxies.data ? proxies.data.map((p) => ({ label: p.name, value: p.id })) : []}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</SlideOver>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { XMarkIcon } from "@heroicons/react/24/solid";
|
||||
import type { FieldProps } from "formik";
|
||||
|
@ -16,11 +16,12 @@ import { DialogTitle } from "@headlessui/react";
|
|||
import { IrcAuthMechanismTypeOptions, OptionBasicTyped } from "@domain/constants";
|
||||
import { APIClient } from "@api/APIClient";
|
||||
import { IrcKeys } from "@api/query_keys";
|
||||
import { NumberFieldWide, PasswordFieldWide, SwitchGroupWide, TextFieldWide } from "@components/inputs";
|
||||
import { NumberFieldWide, PasswordFieldWide, SwitchButton, SwitchGroupWide, TextFieldWide } from "@components/inputs";
|
||||
import { SlideOver } from "@components/panels";
|
||||
import Toast from "@components/notifications/Toast";
|
||||
import * as common from "@components/inputs/common";
|
||||
import { classNames } from "@utils";
|
||||
import { ProxiesQueryOptions } from "@api/queries";
|
||||
|
||||
interface ChannelsFieldArrayProps {
|
||||
channels: IrcChannel[];
|
||||
|
@ -270,6 +271,8 @@ interface IrcNetworkUpdateFormValues {
|
|||
bouncer_addr: string;
|
||||
bot_mode: boolean;
|
||||
channels: Array<IrcChannel>;
|
||||
use_proxy: boolean;
|
||||
proxy_id: number;
|
||||
}
|
||||
|
||||
interface IrcNetworkUpdateFormProps {
|
||||
|
@ -285,6 +288,8 @@ export function IrcNetworkUpdateForm({
|
|||
}: IrcNetworkUpdateFormProps) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const proxies = useQuery(ProxiesQueryOptions());
|
||||
|
||||
const updateMutation = useMutation({
|
||||
mutationFn: (network: IrcNetwork) => APIClient.irc.updateNetwork(network),
|
||||
onSuccess: () => {
|
||||
|
@ -325,7 +330,9 @@ export function IrcNetworkUpdateForm({
|
|||
use_bouncer: network.use_bouncer,
|
||||
bouncer_addr: network.bouncer_addr,
|
||||
bot_mode: network.bot_mode,
|
||||
channels: network.channels
|
||||
channels: network.channels,
|
||||
use_proxy: network.use_proxy,
|
||||
proxy_id: network.proxy_id,
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -348,7 +355,7 @@ export function IrcNetworkUpdateForm({
|
|||
required={true}
|
||||
/>
|
||||
|
||||
<SwitchGroupWide name="enabled" label="Enabled" />
|
||||
<SwitchGroupWide name="enabled" label="Enabled"/>
|
||||
<TextFieldWide
|
||||
name="server"
|
||||
label="Server"
|
||||
|
@ -362,7 +369,7 @@ export function IrcNetworkUpdateForm({
|
|||
required={true}
|
||||
/>
|
||||
|
||||
<SwitchGroupWide name="tls" label="TLS" />
|
||||
<SwitchGroupWide name="tls" label="TLS"/>
|
||||
|
||||
<PasswordFieldWide
|
||||
name="pass"
|
||||
|
@ -377,7 +384,7 @@ export function IrcNetworkUpdateForm({
|
|||
required={true}
|
||||
/>
|
||||
|
||||
<SwitchGroupWide name="use_bouncer" label="Bouncer (BNC)" />
|
||||
<SwitchGroupWide name="use_bouncer" label="Bouncer (BNC)"/>
|
||||
{values.use_bouncer && (
|
||||
<TextFieldWide
|
||||
name="bouncer_addr"
|
||||
|
@ -386,7 +393,32 @@ export function IrcNetworkUpdateForm({
|
|||
/>
|
||||
)}
|
||||
|
||||
<SwitchGroupWide name="bot_mode" label="IRCv3 Bot Mode" />
|
||||
<SwitchGroupWide name="bot_mode" label="IRCv3 Bot Mode"/>
|
||||
|
||||
<div className="border-t border-gray-200 dark:border-gray-700 py-4">
|
||||
<div className="flex justify-between px-4">
|
||||
<div className="space-y-1">
|
||||
<DialogTitle className="text-lg font-medium text-gray-900 dark:text-white">
|
||||
Proxy
|
||||
</DialogTitle>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
Set a proxy to be used for connecting to the irc server.
|
||||
</p>
|
||||
</div>
|
||||
<SwitchButton name="use_proxy"/>
|
||||
</div>
|
||||
|
||||
{values.use_proxy === true && (
|
||||
<div className="py-4 pt-6">
|
||||
<SelectField<number>
|
||||
name="proxy_id"
|
||||
label="Select proxy"
|
||||
placeholder="Select a proxy"
|
||||
options={proxies.data ? proxies.data.map((p) => ({ label: p.name, value: p.id })) : []}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="border-t border-gray-200 dark:border-gray-700 py-5">
|
||||
<div className="px-4 space-y-1 mb-8">
|
||||
|
@ -416,17 +448,17 @@ export function IrcNetworkUpdateForm({
|
|||
/>
|
||||
</div>
|
||||
|
||||
<PasswordFieldWide name="invite_command" label="Invite command" />
|
||||
<PasswordFieldWide name="invite_command" label="Invite command"/>
|
||||
|
||||
<div className="border-t border-gray-200 dark:border-gray-700 py-5">
|
||||
<div className="px-4 space-y-1 mb-8">
|
||||
<DialogTitle className="text-lg font-medium text-gray-900 dark:text-white">Channels</DialogTitle>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
Channels are added when you setup IRC indexers. Do not edit unless you know what you are doing.
|
||||
Channels are added when you setup IRC indexers. Do not edit unless you know what you are doing.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ChannelsFieldArray channels={values.channels} />
|
||||
<ChannelsFieldArray channels={values.channels}/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
@ -438,9 +470,10 @@ interface SelectFieldProps<T> {
|
|||
name: string;
|
||||
label: string;
|
||||
options: OptionBasicTyped<T>[]
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
function SelectField<T>({ name, label, options }: SelectFieldProps<T>) {
|
||||
export function SelectField<T>({ name, label, options, placeholder }: SelectFieldProps<T>) {
|
||||
return (
|
||||
<div className="flex items-center justify-between space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||
<div>
|
||||
|
@ -454,9 +487,9 @@ function SelectField<T>({ name, label, options }: SelectFieldProps<T>) {
|
|||
<div className="sm:col-span-2">
|
||||
<Field name={name} type="select">
|
||||
{({
|
||||
field,
|
||||
form: { setFieldValue, resetForm }
|
||||
}: FieldProps) => (
|
||||
field,
|
||||
form: { setFieldValue }
|
||||
}: FieldProps) => (
|
||||
<Select
|
||||
{...field}
|
||||
id={name}
|
||||
|
@ -470,7 +503,7 @@ function SelectField<T>({ name, label, options }: SelectFieldProps<T>) {
|
|||
IndicatorSeparator: common.IndicatorSeparator,
|
||||
DropdownIndicator: common.DropdownIndicator
|
||||
}}
|
||||
placeholder="Choose a type"
|
||||
placeholder={placeholder ?? "Choose a type"}
|
||||
styles={{
|
||||
singleValue: (base) => ({
|
||||
...base,
|
||||
|
@ -487,14 +520,18 @@ function SelectField<T>({ name, label, options }: SelectFieldProps<T>) {
|
|||
})}
|
||||
value={field?.value && options.find(o => o.value == field?.value)}
|
||||
onChange={(option) => {
|
||||
resetForm();
|
||||
// resetForm();
|
||||
|
||||
// const opt = option as SelectOption;
|
||||
// setFieldValue("name", option?.label ?? "")
|
||||
setFieldValue(
|
||||
field.name,
|
||||
option.value ?? ""
|
||||
);
|
||||
if (option !== null) {
|
||||
// const opt = option as SelectOption;
|
||||
// setFieldValue("name", option?.label ?? "")
|
||||
setFieldValue(
|
||||
field.name,
|
||||
option.value ?? ""
|
||||
);
|
||||
} else {
|
||||
setFieldValue(field.name, undefined);
|
||||
}
|
||||
}}
|
||||
options={options}
|
||||
/>
|
||||
|
|
265
web/src/forms/settings/ProxyForms.tsx
Normal file
265
web/src/forms/settings/ProxyForms.tsx
Normal file
|
@ -0,0 +1,265 @@
|
|||
import { Fragment } from "react";
|
||||
import { Form, Formik, FormikValues } from "formik";
|
||||
import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild } from "@headlessui/react";
|
||||
import { XMarkIcon } from "@heroicons/react/24/solid";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { toast } from "react-hot-toast";
|
||||
|
||||
import { AddProps } from "@forms/settings/IndexerForms";
|
||||
import { DEBUG } from "@components/debug.tsx";
|
||||
import { PasswordFieldWide, SwitchGroupWide, TextFieldWide } from "@components/inputs";
|
||||
import { SelectFieldBasic } from "@components/inputs/select_wide";
|
||||
import { ProxyTypeOptions } from "@domain/constants";
|
||||
import { APIClient } from "@api/APIClient";
|
||||
import { ProxyKeys } from "@api/query_keys";
|
||||
import Toast from "@components/notifications/Toast";
|
||||
import { SlideOver } from "@components/panels";
|
||||
|
||||
export function ProxyAddForm({ isOpen, toggle }: AddProps) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const createMutation = useMutation({
|
||||
mutationFn: (req: ProxyCreate) => APIClient.proxy.store(req),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ProxyKeys.lists() });
|
||||
|
||||
toast.custom((t) => <Toast type="success" body="Proxy added!" t={t} />);
|
||||
toggle();
|
||||
},
|
||||
onError: () => {
|
||||
toast.custom((t) => <Toast type="error" body="Proxy could not be added" t={t} />);
|
||||
}
|
||||
});
|
||||
|
||||
const onSubmit = (formData: FormikValues) => {
|
||||
createMutation.mutate(formData as ProxyCreate);
|
||||
}
|
||||
|
||||
const testMutation = useMutation({
|
||||
mutationFn: (data: Proxy) => APIClient.proxy.test(data),
|
||||
onError: (err) => {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
const testProxy = (data: unknown) => testMutation.mutate(data as Proxy);
|
||||
|
||||
const initialValues: ProxyCreate = {
|
||||
enabled: true,
|
||||
name: "Proxy",
|
||||
type: "SOCKS5",
|
||||
addr: "socks5://ip:port",
|
||||
user: "",
|
||||
pass: "",
|
||||
}
|
||||
|
||||
return (
|
||||
<Transition show={isOpen} as={Fragment}>
|
||||
<Dialog
|
||||
as="div"
|
||||
static
|
||||
className="fixed inset-0 overflow-hidden"
|
||||
open={isOpen}
|
||||
onClose={toggle}
|
||||
>
|
||||
<div className="absolute inset-0 overflow-hidden">
|
||||
<DialogPanel className="absolute inset-y-0 right-0 pl-10 max-w-full flex sm:pl-16">
|
||||
<TransitionChild
|
||||
as={Fragment}
|
||||
enter="transform transition ease-in-out duration-500 sm:duration-700"
|
||||
enterFrom="translate-x-full"
|
||||
enterTo="translate-x-0"
|
||||
leave="transform transition ease-in-out duration-500 sm:duration-700"
|
||||
leaveFrom="translate-x-0"
|
||||
leaveTo="translate-x-full"
|
||||
>
|
||||
<div className="w-screen max-w-2xl dark:border-gray-700 border-l">
|
||||
<Formik
|
||||
enableReinitialize={true}
|
||||
initialValues={initialValues}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{({ values }) => (
|
||||
<Form className="h-full flex flex-col bg-white dark:bg-gray-800 shadow-xl overflow-y-auto">
|
||||
<div className="flex-1">
|
||||
<div className="px-4 py-6 bg-gray-50 dark:bg-gray-900 sm:px-6">
|
||||
<div className="flex items-start justify-between space-x-3">
|
||||
<div className="space-y-1">
|
||||
<DialogTitle className="text-lg font-medium text-gray-900 dark:text-white">
|
||||
Add proxy
|
||||
</DialogTitle>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-200">
|
||||
Add proxy to be used with Indexers or IRC.
|
||||
</p>
|
||||
</div>
|
||||
<div className="h-7 flex items-center">
|
||||
<button
|
||||
type="button"
|
||||
className="bg-white dark:bg-gray-700 rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
onClick={toggle}
|
||||
>
|
||||
<span className="sr-only">Close panel</span>
|
||||
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="py-6 space-y-4 divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<SwitchGroupWide name="enabled" label="Enabled" />
|
||||
<TextFieldWide name="name" label="Name" defaultValue="" required={true} />
|
||||
|
||||
<SelectFieldBasic
|
||||
name="type"
|
||||
label="Proxy type"
|
||||
options={ProxyTypeOptions}
|
||||
tooltip={<span>Proxy type. Commonly SOCKS5.</span>}
|
||||
help="Usually SOCKS5"
|
||||
/>
|
||||
|
||||
<TextFieldWide name="addr" label="Addr" required={true} help="Addr: scheme://ip:port or scheme://domain" autoComplete="off"/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<TextFieldWide name="user" label="User" help="auth: username" autoComplete="off" />
|
||||
<PasswordFieldWide name="pass" label="Pass" help="auth: password" autoComplete="off"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="flex-shrink-0 px-4 border-t border-gray-200 dark:border-gray-700 py-5 sm:px-6">
|
||||
<div className="space-x-3 flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
className="bg-white dark:bg-gray-700 py-2 px-4 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-blue-500"
|
||||
onClick={() => testProxy(values)}
|
||||
>
|
||||
Test
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="bg-white dark:bg-gray-700 py-2 px-4 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-blue-500"
|
||||
onClick={toggle}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 dark:bg-blue-600 hover:bg-blue-700 dark:hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-blue-500"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DEBUG values={values}/>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
|
||||
</TransitionChild>
|
||||
</DialogPanel>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
interface UpdateFormProps<T> {
|
||||
isOpen: boolean;
|
||||
toggle: () => void;
|
||||
data: T;
|
||||
}
|
||||
|
||||
export function ProxyUpdateForm({ isOpen, toggle, data }: UpdateFormProps<Proxy>) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const updateMutation = useMutation({
|
||||
mutationFn: (req: Proxy) => APIClient.proxy.update(req),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ProxyKeys.lists() });
|
||||
|
||||
toast.custom((t) => <Toast type="success" body={`Proxy ${data.name} updated!`} t={t} />);
|
||||
toggle();
|
||||
},
|
||||
onError: () => {
|
||||
toast.custom((t) => <Toast type="error" body="Proxy could not be updated" t={t} />);
|
||||
}
|
||||
});
|
||||
|
||||
const onSubmit = (formData: unknown) => {
|
||||
updateMutation.mutate(formData as Proxy);
|
||||
}
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: (proxyId: number) => APIClient.proxy.delete(proxyId),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ProxyKeys.lists() });
|
||||
|
||||
toast.custom((t) => <Toast type="success" body={`Proxy ${data.name} was deleted.`} t={t}/>);
|
||||
}
|
||||
});
|
||||
|
||||
const deleteFn = () => deleteMutation.mutate(data.id);
|
||||
|
||||
const testMutation = useMutation({
|
||||
mutationFn: (data: Proxy) => APIClient.proxy.test(data),
|
||||
onError: (err) => {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
const testProxy = (data: unknown) => testMutation.mutate(data as Proxy);
|
||||
|
||||
const initialValues: Proxy = {
|
||||
id: data.id,
|
||||
enabled: data.enabled,
|
||||
name: data.name,
|
||||
type: data.type,
|
||||
addr: data.addr,
|
||||
user: data.user,
|
||||
pass: data.pass,
|
||||
}
|
||||
|
||||
return (
|
||||
<SlideOver<Proxy>
|
||||
title="Proxy"
|
||||
initialValues={initialValues}
|
||||
onSubmit={onSubmit}
|
||||
deleteAction={deleteFn}
|
||||
testFn={testProxy}
|
||||
isOpen={isOpen}
|
||||
toggle={toggle}
|
||||
type="UPDATE"
|
||||
>
|
||||
{() => (
|
||||
<div>
|
||||
<div className="py-6 space-y-4 divide-y divide-gray-200 dark:divide-gray-700">
|
||||
|
||||
<SwitchGroupWide name="enabled" label="Enabled"/>
|
||||
|
||||
<TextFieldWide name="name" label="Name" defaultValue="" required={true}/>
|
||||
|
||||
<SelectFieldBasic
|
||||
name="type"
|
||||
label="Proxy type"
|
||||
required={true}
|
||||
options={ProxyTypeOptions}
|
||||
tooltip={<span>Proxy type. Commonly SOCKS5.</span>}
|
||||
help="Usually SOCKS5"
|
||||
/>
|
||||
|
||||
<TextFieldWide name="addr" label="Addr" required={true} help="Addr: scheme://ip:port or scheme://domain" autoComplete="off"/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<TextFieldWide name="user" label="User" help="auth: username" autoComplete="off"/>
|
||||
<PasswordFieldWide name="pass" label="Pass" help="auth: password" autoComplete="off"/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</SlideOver>
|
||||
);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue