mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 16:59:12 +00:00
Refactor(web): Replace final-form with Formik and cleanup (#46)
* refactor: begin to replace final-form * refactor: replace final-form with formik n cleanup
This commit is contained in:
parent
c4d580eb03
commit
5e29564f03
66 changed files with 1523 additions and 3409 deletions
|
@ -1,415 +0,0 @@
|
|||
import { Fragment, useEffect } from "react";
|
||||
import { useMutation } from "react-query";
|
||||
import { Action, DownloadClient, Filter } from "../../domain/interfaces";
|
||||
import { queryClient } from "../../App";
|
||||
import { sleep } from "../../utils/utils";
|
||||
import { CheckIcon, SelectorIcon, XIcon } from "@heroicons/react/solid";
|
||||
import { Dialog, Listbox, Transition } from "@headlessui/react";
|
||||
import { classNames } from "../../styles/utils";
|
||||
import { Field, Form } from "react-final-form";
|
||||
import DEBUG from "../../components/debug";
|
||||
import APIClient from "../../api/APIClient";
|
||||
import { ActionTypeOptions } from "../../domain/constants";
|
||||
import { SwitchGroup, TextFieldWide } from "../../components/inputs";
|
||||
import { AlertWarning } from "../../components/alerts";
|
||||
import {
|
||||
NumberFieldWide,
|
||||
RadioFieldsetWide,
|
||||
} from "../../components/inputs/wide";
|
||||
|
||||
import { toast } from 'react-hot-toast'
|
||||
import Toast from '../../components/notifications/Toast';
|
||||
|
||||
interface DownloadClientSelectProps {
|
||||
name: string;
|
||||
clients: DownloadClient[];
|
||||
values: any;
|
||||
}
|
||||
|
||||
export function DownloadClientSelect({
|
||||
name,
|
||||
clients,
|
||||
values,
|
||||
}: DownloadClientSelectProps) {
|
||||
return (
|
||||
<div className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
|
||||
<Field
|
||||
name={name}
|
||||
type="select"
|
||||
render={({ input }) => (
|
||||
<Listbox value={input.value} onChange={input.onChange}>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Listbox.Label className="block text-sm font-medium text-gray-700">
|
||||
Client
|
||||
</Listbox.Label>
|
||||
<div className="mt-1 relative">
|
||||
<Listbox.Button className="bg-white relative w-full border border-gray-300 rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-default focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
|
||||
<span className="block truncate">
|
||||
{input.value
|
||||
? clients.find((c) => c.id === input.value)!.name
|
||||
: "Choose a client"}
|
||||
</span>
|
||||
{/*<span className="block truncate">Choose a client</span>*/}
|
||||
<span className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
|
||||
<SelectorIcon
|
||||
className="h-5 w-5 text-gray-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
</Listbox.Button>
|
||||
|
||||
<Transition
|
||||
show={open}
|
||||
as={Fragment}
|
||||
leave="transition ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Listbox.Options
|
||||
static
|
||||
className="absolute z-10 mt-1 w-full bg-white shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm"
|
||||
>
|
||||
{clients
|
||||
.filter((c) => c.type === values.type)
|
||||
.map((client: any) => (
|
||||
<Listbox.Option
|
||||
key={client.id}
|
||||
className={({ active }) =>
|
||||
classNames(
|
||||
active
|
||||
? "text-white bg-indigo-600"
|
||||
: "text-gray-900",
|
||||
"cursor-default select-none relative py-2 pl-3 pr-9"
|
||||
)
|
||||
}
|
||||
value={client.id}
|
||||
>
|
||||
{({ selected, active }) => (
|
||||
<>
|
||||
<span
|
||||
className={classNames(
|
||||
selected ? "font-semibold" : "font-normal",
|
||||
"block truncate"
|
||||
)}
|
||||
>
|
||||
{client.name}
|
||||
</span>
|
||||
|
||||
{selected ? (
|
||||
<span
|
||||
className={classNames(
|
||||
active ? "text-white" : "text-indigo-600",
|
||||
"absolute inset-y-0 right-0 flex items-center pr-4"
|
||||
)}
|
||||
>
|
||||
<CheckIcon
|
||||
className="h-5 w-5"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox.Options>
|
||||
</Transition>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Listbox>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface props {
|
||||
filter: Filter;
|
||||
isOpen: boolean;
|
||||
toggle: any;
|
||||
clients: DownloadClient[];
|
||||
}
|
||||
|
||||
function FilterActionAddForm({ filter, isOpen, toggle, clients }: props) {
|
||||
const mutation = useMutation(
|
||||
(action: Action) => APIClient.actions.create(action),
|
||||
{
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(["filter", filter.id]);
|
||||
toast.custom((t) => <Toast type="success" body="Action was added" t={t} />)
|
||||
|
||||
sleep(500).then(() => toggle());
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// console.log("render add action form", clients)
|
||||
}, []);
|
||||
|
||||
const onSubmit = (data: any) => {
|
||||
// TODO clear data depending on type
|
||||
mutation.mutate(data);
|
||||
};
|
||||
|
||||
const TypeForm = (values: any) => {
|
||||
switch (values.type) {
|
||||
case "TEST":
|
||||
return (
|
||||
<AlertWarning
|
||||
title="Notice"
|
||||
text="The test action does nothing except to show if the filter works."
|
||||
/>
|
||||
);
|
||||
case "WATCH_FOLDER":
|
||||
return (
|
||||
<div>
|
||||
<TextFieldWide
|
||||
name="watch_folder"
|
||||
label="Watch dir"
|
||||
placeholder="Watch directory eg. /home/user/watch_folder"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
case "EXEC":
|
||||
return (
|
||||
<div>
|
||||
<TextFieldWide
|
||||
name="exec_cmd"
|
||||
label="Program"
|
||||
placeholder="Path to program eg. /bin/test"
|
||||
/>
|
||||
|
||||
<TextFieldWide
|
||||
name="exec_args"
|
||||
label="Arguments"
|
||||
placeholder="Arguments eg. --test"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
case "QBITTORRENT":
|
||||
return (
|
||||
<div>
|
||||
<DownloadClientSelect
|
||||
name="client_id"
|
||||
clients={clients}
|
||||
values={values}
|
||||
/>
|
||||
|
||||
<TextFieldWide name="category" label="Category" placeholder="" />
|
||||
<TextFieldWide
|
||||
name="tags"
|
||||
label="Tags"
|
||||
placeholder="Comma separated eg. 4k,remux"
|
||||
/>
|
||||
<TextFieldWide name="save_path" label="Save path" />
|
||||
|
||||
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
|
||||
<SwitchGroup name="paused" label="Add paused" />
|
||||
</div>
|
||||
|
||||
<div className="divide-y divide-gray-200 pt-8 space-y-6 sm:pt-10 sm:space-y-5">
|
||||
<div className="px-4">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900">
|
||||
Limit speeds
|
||||
</h3>
|
||||
<p className="mt-1 max-w-2xl text-sm text-gray-500">
|
||||
Limit download and upload speed for torrents in this filter.
|
||||
In KB/s.
|
||||
</p>
|
||||
</div>
|
||||
<NumberFieldWide
|
||||
name="limit_download_speed"
|
||||
label="Limit download speed"
|
||||
/>
|
||||
<NumberFieldWide
|
||||
name="limit_upload_speed"
|
||||
label="Limit upload speed"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
case "DELUGE_V1":
|
||||
case "DELUGE_V2":
|
||||
return (
|
||||
<div>
|
||||
<DownloadClientSelect
|
||||
name="client_id"
|
||||
clients={clients}
|
||||
values={values}
|
||||
/>
|
||||
|
||||
<TextFieldWide name="label" label="Label" />
|
||||
<TextFieldWide name="save_path" label="Save path" />
|
||||
|
||||
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
|
||||
<SwitchGroup name="paused" label="Add paused" />
|
||||
</div>
|
||||
|
||||
<div className="divide-y divide-gray-200 pt-8 space-y-6 sm:pt-10 sm:space-y-5">
|
||||
<div className="px-4">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900">
|
||||
Limit speeds
|
||||
</h3>
|
||||
<p className="mt-1 max-w-2xl text-sm text-gray-500">
|
||||
Limit download and upload speed for torrents in this filter.
|
||||
In KB/s.
|
||||
</p>
|
||||
</div>
|
||||
<NumberFieldWide
|
||||
name="limit_download_speed"
|
||||
label="Limit download speed"
|
||||
/>
|
||||
<NumberFieldWide
|
||||
name="limit_upload_speed"
|
||||
label="Limit upload speed"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
case "RADARR":
|
||||
case "SONARR":
|
||||
case "LIDARR":
|
||||
return (
|
||||
<div>
|
||||
<DownloadClientSelect
|
||||
name="client_id"
|
||||
clients={clients}
|
||||
values={values}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<AlertWarning
|
||||
title="Notice"
|
||||
text="The test action does nothing except to show if the filter works."
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Transition.Root 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">
|
||||
<Dialog.Overlay className="absolute inset-0" />
|
||||
|
||||
<div className="fixed inset-y-0 right-0 pl-10 max-w-full flex sm:pl-16">
|
||||
<Transition.Child
|
||||
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">
|
||||
<Form
|
||||
initialValues={{
|
||||
name: "",
|
||||
enabled: false,
|
||||
type: "TEST",
|
||||
watch_folder: "",
|
||||
exec_cmd: "",
|
||||
exec_args: "",
|
||||
category: "",
|
||||
tags: "",
|
||||
label: "",
|
||||
save_path: "",
|
||||
paused: false,
|
||||
ignore_rules: false,
|
||||
limit_upload_speed: 0,
|
||||
limit_download_speed: 0,
|
||||
filter_id: filter.id,
|
||||
client_id: null,
|
||||
}}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{({ handleSubmit, values }) => {
|
||||
return (
|
||||
<form
|
||||
className="h-full flex flex-col bg-white shadow-xl overflow-y-scroll"
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<div className="flex-1">
|
||||
<div className="px-4 py-6 bg-gray-50 sm:px-6">
|
||||
<div className="flex items-start justify-between space-x-3">
|
||||
<div className="space-y-1">
|
||||
<Dialog.Title className="text-lg font-medium text-gray-900">
|
||||
Add action
|
||||
</Dialog.Title>
|
||||
<p className="text-sm text-gray-500">
|
||||
Add filter action.
|
||||
</p>
|
||||
</div>
|
||||
<div className="h-7 flex items-center">
|
||||
<button
|
||||
type="button"
|
||||
className="bg-white rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
onClick={toggle}
|
||||
>
|
||||
<span className="sr-only">Close panel</span>
|
||||
<XIcon
|
||||
className="h-6 w-6"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
|
||||
<TextFieldWide name="name" label="Action name" />
|
||||
<RadioFieldsetWide
|
||||
name="type"
|
||||
legend="Type"
|
||||
options={ActionTypeOptions}
|
||||
/>
|
||||
|
||||
{TypeForm(values)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-shrink-0 px-4 border-t border-gray-200 py-5 sm:px-6">
|
||||
<div className="space-x-3 flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
className="bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-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-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DEBUG values={values} />
|
||||
</form>
|
||||
);
|
||||
}}
|
||||
</Form>
|
||||
</div>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
);
|
||||
}
|
||||
|
||||
export default FilterActionAddForm;
|
|
@ -1,318 +0,0 @@
|
|||
import { Fragment, useEffect } from "react";
|
||||
import { useMutation } from "react-query";
|
||||
import { Action, DownloadClient, Filter } from "../../domain/interfaces";
|
||||
import { queryClient } from "../../App";
|
||||
import { sleep } from "../../utils/utils";
|
||||
import { XIcon } from "@heroicons/react/solid";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { Form } from "react-final-form";
|
||||
import DEBUG from "../../components/debug";
|
||||
import APIClient from "../../api/APIClient";
|
||||
import { ActionTypeOptions } from "../../domain/constants";
|
||||
import { AlertWarning } from "../../components/alerts";
|
||||
import { SwitchGroup, TextFieldWide } from "../../components/inputs";
|
||||
import {
|
||||
NumberFieldWide,
|
||||
RadioFieldsetWide,
|
||||
} from "../../components/inputs/wide";
|
||||
import { DownloadClientSelect } from "./FilterActionAddForm";
|
||||
|
||||
import { toast } from 'react-hot-toast'
|
||||
import Toast from '../../components/notifications/Toast';
|
||||
|
||||
|
||||
interface props {
|
||||
filter: Filter;
|
||||
isOpen: boolean;
|
||||
toggle: any;
|
||||
clients: DownloadClient[];
|
||||
action: Action;
|
||||
}
|
||||
|
||||
function FilterActionUpdateForm({
|
||||
filter,
|
||||
isOpen,
|
||||
toggle,
|
||||
clients,
|
||||
action,
|
||||
}: props) {
|
||||
const mutation = useMutation(
|
||||
(action: Action) => APIClient.actions.update(action),
|
||||
{
|
||||
onSuccess: () => {
|
||||
// console.log("add action");
|
||||
queryClient.invalidateQueries(["filter", filter.id]);
|
||||
toast.custom((t) => <Toast type="success" body={`${filter.name} was updated successfully`} t={t} />)
|
||||
|
||||
sleep(1500);
|
||||
|
||||
toggle();
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// console.log("render add action form", clients)
|
||||
}, [clients]);
|
||||
|
||||
const onSubmit = (data: any) => {
|
||||
// TODO clear data depending on type
|
||||
|
||||
console.log(data);
|
||||
mutation.mutate(data);
|
||||
};
|
||||
|
||||
const TypeForm = (values: any) => {
|
||||
switch (values.type) {
|
||||
case "TEST":
|
||||
return (
|
||||
<AlertWarning
|
||||
title="Notice"
|
||||
text="The test action does nothing except to show if the filter works."
|
||||
/>
|
||||
);
|
||||
case "WATCH_FOLDER":
|
||||
return (
|
||||
<div>
|
||||
<TextFieldWide
|
||||
name="watch_folder"
|
||||
label="Watch dir"
|
||||
placeholder="Watch directory eg. /home/user/watch_folder"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
case "EXEC":
|
||||
return (
|
||||
<div>
|
||||
<TextFieldWide
|
||||
name="exec_cmd"
|
||||
label="Program"
|
||||
placeholder="Path to program eg. /bin/test"
|
||||
/>
|
||||
|
||||
<TextFieldWide
|
||||
name="exec_args"
|
||||
label="Arguments"
|
||||
placeholder="Arguments eg. --test"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
case "QBITTORRENT":
|
||||
return (
|
||||
<div>
|
||||
<DownloadClientSelect
|
||||
name="client_id"
|
||||
clients={clients}
|
||||
values={values}
|
||||
/>
|
||||
|
||||
<TextFieldWide name="category" label="Category" placeholder="" />
|
||||
<TextFieldWide
|
||||
name="tags"
|
||||
label="Tags"
|
||||
placeholder="Comma separated eg. 4k,remux"
|
||||
/>
|
||||
<TextFieldWide name="save_path" label="Save path" />
|
||||
|
||||
<div className="divide-y divide-gray-200 pt-8 space-y-6 sm:pt-10 sm:space-y-5">
|
||||
<div className="px-4">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900">
|
||||
Limit speeds
|
||||
</h3>
|
||||
<p className="mt-1 max-w-2xl text-sm text-gray-500">
|
||||
Limit download and upload speed for torrents in this filter.
|
||||
In KB/s.
|
||||
</p>
|
||||
</div>
|
||||
<NumberFieldWide
|
||||
name="limit_download_speed"
|
||||
label="Limit download speed"
|
||||
/>
|
||||
<NumberFieldWide
|
||||
name="limit_upload_speed"
|
||||
label="Limit upload speed"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-6">
|
||||
<SwitchGroup name="paused" label="Add paused" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
case "DELUGE_V1":
|
||||
case "DELUGE_V2":
|
||||
return (
|
||||
<div>
|
||||
<DownloadClientSelect
|
||||
name="client_id"
|
||||
clients={clients}
|
||||
values={values}
|
||||
/>
|
||||
|
||||
<TextFieldWide name="label" label="Label" />
|
||||
<TextFieldWide name="save_path" label="Save path" />
|
||||
|
||||
<div className="divide-y divide-gray-200 pt-8 space-y-6 sm:pt-10 sm:space-y-5">
|
||||
<div className="px-4">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900">
|
||||
Limit speeds
|
||||
</h3>
|
||||
<p className="mt-1 max-w-2xl text-sm text-gray-500">
|
||||
Limit download and upload speed for torrents in this filter.
|
||||
In KB/s.
|
||||
</p>
|
||||
</div>
|
||||
<NumberFieldWide
|
||||
name="limit_download_speed"
|
||||
label="Limit download speed"
|
||||
/>
|
||||
<NumberFieldWide
|
||||
name="limit_upload_speed"
|
||||
label="Limit upload speed"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
case "RADARR":
|
||||
case "SONARR":
|
||||
case "LIDARR":
|
||||
return (
|
||||
<div>
|
||||
<DownloadClientSelect
|
||||
name="client_id"
|
||||
clients={clients}
|
||||
values={values}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<AlertWarning
|
||||
title="Notice"
|
||||
text="The test action does nothing except to show if the filter works."
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Transition.Root 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">
|
||||
<Dialog.Overlay className="absolute inset-0" />
|
||||
|
||||
<div className="fixed inset-y-0 right-0 pl-10 max-w-full flex sm:pl-16">
|
||||
<Transition.Child
|
||||
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">
|
||||
<Form
|
||||
initialValues={{
|
||||
name: "",
|
||||
enabled: false,
|
||||
type: "TEST",
|
||||
watch_folder: "",
|
||||
exec_cmd: "",
|
||||
exec_args: "",
|
||||
category: "",
|
||||
tags: "",
|
||||
label: "",
|
||||
save_path: "",
|
||||
paused: false,
|
||||
ignore_rules: false,
|
||||
limit_upload_speed: 0,
|
||||
limit_download_speed: 0,
|
||||
filter_id: filter.id,
|
||||
client_id: null,
|
||||
}}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{({ handleSubmit, values }) => {
|
||||
return (
|
||||
<form
|
||||
className="h-full flex flex-col bg-white shadow-xl overflow-y-scroll"
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<div className="flex-1">
|
||||
<div className="px-4 py-6 bg-gray-50 sm:px-6">
|
||||
<div className="flex items-start justify-between space-x-3">
|
||||
<div className="space-y-1">
|
||||
<Dialog.Title className="text-lg font-medium text-gray-900">
|
||||
Update action
|
||||
</Dialog.Title>
|
||||
<p className="text-sm text-gray-500">
|
||||
Add filter action.
|
||||
</p>
|
||||
</div>
|
||||
<div className="h-7 flex items-center">
|
||||
<button
|
||||
type="button"
|
||||
className="bg-white rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
onClick={toggle}
|
||||
>
|
||||
<span className="sr-only">Close panel</span>
|
||||
<XIcon
|
||||
className="h-6 w-6"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
|
||||
<TextFieldWide name="name" label="Action name" />
|
||||
<RadioFieldsetWide
|
||||
name="type"
|
||||
legend="Type"
|
||||
options={ActionTypeOptions}
|
||||
/>
|
||||
|
||||
{TypeForm(values)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-shrink-0 px-4 border-t border-gray-200 py-5 sm:px-6">
|
||||
<div className="space-x-3 flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
className="bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-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-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DEBUG values={values} />
|
||||
</form>
|
||||
);
|
||||
}}
|
||||
</Form>
|
||||
</div>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
);
|
||||
}
|
||||
|
||||
export default FilterActionUpdateForm;
|
|
@ -4,12 +4,12 @@ import { Filter } from "../../domain/interfaces";
|
|||
import { queryClient } from "../../App";
|
||||
import { XIcon } from "@heroicons/react/solid";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { Field, Form } from "react-final-form";
|
||||
import DEBUG from "../../components/debug";
|
||||
import APIClient from "../../api/APIClient";
|
||||
|
||||
import { toast } from 'react-hot-toast'
|
||||
import Toast from '../../components/notifications/Toast';
|
||||
import { Field, FieldProps, Form, Formik } from "formik";
|
||||
|
||||
function FilterAddForm({ isOpen, toggle }: any) {
|
||||
const mutation = useMutation((filter: Filter) => APIClient.filters.create(filter), {
|
||||
|
@ -25,7 +25,7 @@ function FilterAddForm({ isOpen, toggle }: any) {
|
|||
// console.log("render add action form")
|
||||
}, []);
|
||||
|
||||
const onSubmit = (data: any) => {
|
||||
const handleSubmit = (data: any) => {
|
||||
mutation.mutate(data)
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,7 @@ function FilterAddForm({ isOpen, toggle }: any) {
|
|||
>
|
||||
<div className="w-screen max-w-2xl border-l dark:border-gray-700">
|
||||
|
||||
<Form
|
||||
<Formik
|
||||
initialValues={{
|
||||
name: "",
|
||||
enabled: false,
|
||||
|
@ -66,12 +66,11 @@ function FilterAddForm({ isOpen, toggle }: any) {
|
|||
sources: [],
|
||||
containers: []
|
||||
}}
|
||||
onSubmit={handleSubmit}
|
||||
validate={validate}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{({ handleSubmit, values }) => {
|
||||
return (
|
||||
<form className="h-full flex flex-col bg-white dark:bg-gray-800 shadow-xl overflow-y-scroll" onSubmit={handleSubmit}>
|
||||
{({ values }) => (
|
||||
<Form className="h-full flex flex-col bg-white dark:bg-gray-800 shadow-xl overflow-y-scroll">
|
||||
<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">
|
||||
|
@ -107,20 +106,25 @@ function FilterAddForm({ isOpen, toggle }: any) {
|
|||
</label>
|
||||
</div>
|
||||
<Field name="name">
|
||||
{({ input, meta }) => (
|
||||
{({
|
||||
field,
|
||||
meta,
|
||||
}: FieldProps ) => (
|
||||
<div className="sm:col-span-2">
|
||||
<input
|
||||
{...field}
|
||||
id="name"
|
||||
type="text"
|
||||
{...input}
|
||||
className="block w-full shadow-sm dark:bg-gray-800 border-gray-300 dark:border-gray-700 sm:text-sm dark:text-white focus:ring-indigo-500 dark:focus:ring-blue-500 focus:border-indigo-500 dark:focus:border-blue-500 rounded-md"
|
||||
/>
|
||||
|
||||
{meta.touched && meta.error &&
|
||||
<span className="block mt-2 text-red-500">{meta.error}</span>}
|
||||
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -143,12 +147,10 @@ function FilterAddForm({ isOpen, toggle }: any) {
|
|||
</div>
|
||||
</div>
|
||||
<DEBUG values={values} />
|
||||
</form>
|
||||
)
|
||||
}}
|
||||
</Form>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
export { default as FilterAddForm } from "./filters/FilterAddForm";
|
||||
export { default as FilterActionAddForm } from "./filters/FilterActionAddForm";
|
||||
export { default as FilterActionUpdateForm } from "./filters/FilterActionUpdateForm";
|
||||
|
||||
export { DownloadClientAddForm, DownloadClientUpdateForm } from "./settings/DownloadClientForms";
|
||||
export { IndexerAddForm, IndexerUpdateForm } from "./settings/IndexerForms";
|
||||
|
|
|
@ -6,20 +6,47 @@ import {
|
|||
} from "../../domain/interfaces";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { XIcon } from "@heroicons/react/solid";
|
||||
import { classNames } from "../../styles/utils";
|
||||
import { Form, useField } from "react-final-form";
|
||||
import { sleep, classNames } from "../../utils";
|
||||
|
||||
import { Form, Formik, useFormikContext } from "formik";
|
||||
import DEBUG from "../../components/debug";
|
||||
import { SwitchGroup, TextFieldWide } from "../../components/inputs";
|
||||
import { queryClient } from "../../App";
|
||||
import APIClient from "../../api/APIClient";
|
||||
import { sleep } from "../../utils/utils";
|
||||
import { DownloadClientTypeOptions } from "../../domain/constants";
|
||||
import { NumberFieldWide, PasswordFieldWide, RadioFieldsetWide } from "../../components/inputs/wide";
|
||||
|
||||
import { toast } from 'react-hot-toast'
|
||||
import Toast from '../../components/notifications/Toast';
|
||||
import { useToggle } from "../../hooks/hooks";
|
||||
import { DeleteModal } from "../../components/modals";
|
||||
import { NumberFieldWide, PasswordFieldWide, SwitchGroupWide, TextFieldWide } from "../../components/inputs/input_wide";
|
||||
import { RadioFieldsetWide } from "../../components/inputs/radio";
|
||||
|
||||
interface InitialValuesSettings {
|
||||
basic?: {
|
||||
auth: boolean;
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
rules?: {
|
||||
enabled?: boolean;
|
||||
ignore_slow_torrents?: boolean;
|
||||
download_speed_threshold?: number;
|
||||
max_active_downloads?: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface InitialValues {
|
||||
name: string;
|
||||
type: DOWNLOAD_CLIENT_TYPES;
|
||||
enabled: boolean;
|
||||
host: string;
|
||||
port: number;
|
||||
ssl: boolean;
|
||||
username: string;
|
||||
password: string;
|
||||
settings: InitialValuesSettings;
|
||||
}
|
||||
|
||||
|
||||
function FormFieldsDefault() {
|
||||
return (
|
||||
|
@ -29,7 +56,7 @@ function FormFieldsDefault() {
|
|||
<NumberFieldWide name="port" label="Port" />
|
||||
|
||||
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
|
||||
<SwitchGroup name="ssl" label="SSL" />
|
||||
<SwitchGroupWide name="ssl" label="SSL" />
|
||||
</div>
|
||||
|
||||
<TextFieldWide name="username" label="Username" />
|
||||
|
@ -39,7 +66,10 @@ function FormFieldsDefault() {
|
|||
}
|
||||
|
||||
function FormFieldsArr() {
|
||||
const { input } = useField("settings.basic.auth");
|
||||
const {
|
||||
values: { settings },
|
||||
} = useFormikContext<InitialValues>();
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<TextFieldWide name="host" label="Host" help="Full url like http(s)://domain.ltd/" />
|
||||
|
@ -47,10 +77,10 @@ function FormFieldsArr() {
|
|||
<PasswordFieldWide name="settings.apikey" label="API key" />
|
||||
|
||||
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
|
||||
<SwitchGroup name="settings.basic.auth" label="Basic auth" />
|
||||
<SwitchGroupWide name="settings.basic.auth" label="Basic auth" />
|
||||
</div>
|
||||
|
||||
{input.value === true && (
|
||||
{settings.basic?.auth === true && (
|
||||
<Fragment>
|
||||
<TextFieldWide name="settings.basic.username" label="Username" />
|
||||
<PasswordFieldWide name="settings.basic.password" label="Password" />
|
||||
|
@ -71,7 +101,9 @@ export const componentMap: any = {
|
|||
|
||||
|
||||
function FormFieldsRulesBasic() {
|
||||
const { input: enabled } = useField("settings.rules.enabled");
|
||||
const {
|
||||
values: { settings },
|
||||
} = useFormikContext<InitialValues>();
|
||||
|
||||
return (
|
||||
<div className="border-t border-gray-200 dark:border-gray-700 py-5">
|
||||
|
@ -84,10 +116,10 @@ function FormFieldsRulesBasic() {
|
|||
</div>
|
||||
|
||||
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
|
||||
<SwitchGroup name="settings.rules.enabled" label="Enabled" />
|
||||
<SwitchGroupWide name="settings.rules.enabled" label="Enabled" />
|
||||
</div>
|
||||
|
||||
{enabled.value === true && (
|
||||
{settings && settings.rules?.enabled === true && (
|
||||
<Fragment>
|
||||
<NumberFieldWide name="settings.rules.max_active_downloads" label="Max active downloads" />
|
||||
</Fragment>
|
||||
|
@ -97,8 +129,9 @@ function FormFieldsRulesBasic() {
|
|||
}
|
||||
|
||||
function FormFieldsRules() {
|
||||
const { input } = useField("settings.rules.ignore_slow_torrents");
|
||||
const { input: enabled } = useField("settings.rules.enabled");
|
||||
const {
|
||||
values: { settings },
|
||||
} = useFormikContext<InitialValues>();
|
||||
|
||||
return (
|
||||
<div className="border-t border-gray-200 dark:border-gray-700 py-5">
|
||||
|
@ -111,17 +144,17 @@ function FormFieldsRules() {
|
|||
</div>
|
||||
|
||||
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
|
||||
<SwitchGroup name="settings.rules.enabled" label="Enabled" />
|
||||
<SwitchGroupWide name="settings.rules.enabled" label="Enabled" />
|
||||
</div>
|
||||
|
||||
{enabled.value === true && (
|
||||
{settings.rules?.enabled === true && (
|
||||
<Fragment>
|
||||
<NumberFieldWide name="settings.rules.max_active_downloads" label="Max active downloads" />
|
||||
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
|
||||
<SwitchGroup name="settings.rules.ignore_slow_torrents" label="Ignore slow torrents" />
|
||||
<SwitchGroupWide name="settings.rules.ignore_slow_torrents" label="Ignore slow torrents" />
|
||||
</div>
|
||||
|
||||
{input.value === true && (
|
||||
{settings.rules?.ignore_slow_torrents === true && (
|
||||
<Fragment>
|
||||
<NumberFieldWide name="settings.rules.download_speed_threshold" label="Download speed threshold" placeholder="in KB/s" help="If download speed is below this when max active downloads is hit, download anyways. KB/s" />
|
||||
</Fragment>
|
||||
|
@ -138,6 +171,100 @@ export const rulesComponentMap: any = {
|
|||
QBITTORRENT: <FormFieldsRules />,
|
||||
};
|
||||
|
||||
interface formButtonsProps {
|
||||
isSuccessfulTest: boolean;
|
||||
isErrorTest: boolean;
|
||||
isTesting: boolean;
|
||||
cancelFn: any;
|
||||
testFn: any;
|
||||
values: any;
|
||||
type: "CREATE" | "UPDATE";
|
||||
toggleDeleteModal?: any;
|
||||
}
|
||||
|
||||
function DownloadClientFormButtons({ type, isSuccessfulTest, isErrorTest, isTesting, cancelFn, testFn, values, toggleDeleteModal }: formButtonsProps) {
|
||||
|
||||
const test = () => {
|
||||
testFn(values)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex-shrink-0 px-4 border-t border-gray-200 dark:border-gray-700 py-5 sm:px-6">
|
||||
<div className={classNames(type === "CREATE" ? "justify-end" : "justify-between", "space-x-3 flex")}>
|
||||
{type === "UPDATE" && (
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center justify-center px-4 py-2 border border-transparent font-medium rounded-md text-red-700 bg-red-100 hover:bg-red-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:text-sm"
|
||||
onClick={toggleDeleteModal}
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
)}
|
||||
<div className="flex">
|
||||
<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-400 bg-white dark:bg-gray-700 hover:bg-gray-50 focus:border-rose-700 active:bg-rose-700",
|
||||
isTesting ? "cursor-not-allowed" : "",
|
||||
"mr-2 inline-flex 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-indigo-500 dark:focus:ring-blue-500"
|
||||
)}
|
||||
disabled={isTesting}
|
||||
// onClick={() => testClient(values)}
|
||||
onClick={test}
|
||||
>
|
||||
{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"
|
||||
)}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="mr-4 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-400 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-blue-500"
|
||||
onClick={cancelFn}
|
||||
>
|
||||
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-indigo-600 dark:bg-blue-600 hover:bg-indigo-700 dark:hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-blue-500"
|
||||
>
|
||||
{type === "CREATE" ? "Create" : "Save"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function DownloadClientAddForm({ isOpen, toggle }: any) {
|
||||
const [isTesting, setIsTesting] = useState(false);
|
||||
const [isSuccessfulTest, setIsSuccessfulTest] = useState(false);
|
||||
|
@ -197,6 +324,18 @@ export function DownloadClientAddForm({ isOpen, toggle }: any) {
|
|||
testClientMutation.mutate(data);
|
||||
};
|
||||
|
||||
let initialValues: InitialValues = {
|
||||
name: "",
|
||||
type: DOWNLOAD_CLIENT_TYPES.qBittorrent,
|
||||
enabled: true,
|
||||
host: "",
|
||||
port: 10000,
|
||||
ssl: false,
|
||||
username: "",
|
||||
password: "",
|
||||
settings: {}
|
||||
}
|
||||
|
||||
return (
|
||||
<Transition.Root show={isOpen} as={Fragment}>
|
||||
<Dialog
|
||||
|
@ -220,137 +359,75 @@ export function DownloadClientAddForm({ isOpen, toggle }: any) {
|
|||
leaveTo="translate-x-full"
|
||||
>
|
||||
<div className="w-screen max-w-2xl border-l dark:border-gray-700">
|
||||
<Form
|
||||
initialValues={{
|
||||
name: "",
|
||||
type: DOWNLOAD_CLIENT_TYPES.qBittorrent,
|
||||
enabled: true,
|
||||
host: "",
|
||||
port: 10000,
|
||||
ssl: false,
|
||||
username: "",
|
||||
password: "",
|
||||
}}
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{({ handleSubmit, values }) => {
|
||||
return (
|
||||
<form
|
||||
className="h-full flex flex-col bg-white dark:bg-gray-800 shadow-xl overflow-y-scroll"
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<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">
|
||||
<Dialog.Title className="text-lg font-medium text-gray-900 dark:text-white">
|
||||
Add client
|
||||
</Dialog.Title>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
Add download client.
|
||||
</p>
|
||||
</div>
|
||||
<div className="h-7 flex items-center">
|
||||
<button
|
||||
type="button"
|
||||
className="bg-white dark:bg-gray-800 rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-blue-500"
|
||||
onClick={toggle}
|
||||
>
|
||||
<span className="sr-only">Close panel</span>
|
||||
<XIcon
|
||||
className="h-6 w-6"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
{({ handleSubmit, values }) => (
|
||||
<Form
|
||||
className="h-full flex flex-col bg-white dark:bg-gray-800 shadow-xl overflow-y-scroll"
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<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">
|
||||
<Dialog.Title className="text-lg font-medium text-gray-900 dark:text-white">
|
||||
Add client
|
||||
</Dialog.Title>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
Add download client.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y dark:divide-gray-700">
|
||||
<TextFieldWide name="name" label="Name" />
|
||||
|
||||
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200 dark:divide-gray-700">
|
||||
<SwitchGroup name="enabled" label="Enabled" />
|
||||
<div className="h-7 flex items-center">
|
||||
<button
|
||||
type="button"
|
||||
className="bg-white dark:bg-gray-800 rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-blue-500"
|
||||
onClick={toggle}
|
||||
>
|
||||
<span className="sr-only">Close panel</span>
|
||||
<XIcon
|
||||
className="h-6 w-6"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<RadioFieldsetWide
|
||||
name="type"
|
||||
legend="Type"
|
||||
options={DownloadClientTypeOptions}
|
||||
/>
|
||||
|
||||
<div>{componentMap[values.type]}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{rulesComponentMap[values.type]}
|
||||
<div className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y dark:divide-gray-700">
|
||||
<TextFieldWide name="name" label="Name" />
|
||||
|
||||
<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={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-400 bg-white dark:bg-gray-700 hover:bg-gray-50 focus:border-rose-700 active:bg-rose-700",
|
||||
isTesting ? "cursor-not-allowed" : "",
|
||||
"mr-2 inline-flex 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-indigo-500 dark:focus:ring-blue-500"
|
||||
)}
|
||||
disabled={isTesting}
|
||||
onClick={() => testClient(values)}
|
||||
>
|
||||
{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"
|
||||
)}
|
||||
</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-400 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-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-indigo-600 dark:bg-blue-600 hover:bg-indigo-700 dark:hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-blue-500"
|
||||
>
|
||||
Create
|
||||
</button>
|
||||
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200 dark:divide-gray-700">
|
||||
<SwitchGroupWide name="enabled" label="Enabled" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DEBUG values={values} />
|
||||
</form>
|
||||
);
|
||||
}}
|
||||
</Form>
|
||||
<RadioFieldsetWide
|
||||
name="type"
|
||||
legend="Type"
|
||||
options={DownloadClientTypeOptions}
|
||||
/>
|
||||
|
||||
<div>{componentMap[values.type]}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{rulesComponentMap[values.type]}
|
||||
|
||||
<DownloadClientFormButtons
|
||||
type="CREATE"
|
||||
isTesting={isTesting}
|
||||
isSuccessfulTest={isSuccessfulTest}
|
||||
isErrorTest={isErrorTest}
|
||||
cancelFn={toggle}
|
||||
testFn={testClient}
|
||||
values={values}
|
||||
/>
|
||||
|
||||
<DEBUG values={values} />
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
|
@ -433,6 +510,19 @@ export function DownloadClientUpdateForm({ client, isOpen, toggle }: any) {
|
|||
testClientMutation.mutate(data);
|
||||
};
|
||||
|
||||
let initialValues = {
|
||||
id: client.id,
|
||||
name: client.name,
|
||||
type: client.type,
|
||||
enabled: client.enabled,
|
||||
host: client.host,
|
||||
port: client.port,
|
||||
ssl: client.ssl,
|
||||
username: client.username,
|
||||
password: client.password,
|
||||
settings: client.settings,
|
||||
}
|
||||
|
||||
return (
|
||||
<Transition.Root show={isOpen} as={Fragment}>
|
||||
<Dialog
|
||||
|
@ -465,24 +555,13 @@ export function DownloadClientUpdateForm({ client, isOpen, toggle }: any) {
|
|||
leaveTo="translate-x-full"
|
||||
>
|
||||
<div className="w-screen max-w-2xl border-l dark:border-gray-700">
|
||||
<Form
|
||||
initialValues={{
|
||||
id: client.id,
|
||||
name: client.name,
|
||||
type: client.type,
|
||||
enabled: client.enabled,
|
||||
host: client.host,
|
||||
port: client.port,
|
||||
ssl: client.ssl,
|
||||
username: client.username,
|
||||
password: client.password,
|
||||
settings: client.settings,
|
||||
}}
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{({ handleSubmit, values }) => {
|
||||
return (
|
||||
<form
|
||||
<Form
|
||||
className="h-full flex flex-col bg-white dark:bg-gray-800 shadow-xl overflow-y-scroll"
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
|
@ -517,7 +596,7 @@ export function DownloadClientUpdateForm({ client, isOpen, toggle }: any) {
|
|||
<TextFieldWide name="name" label="Name" />
|
||||
|
||||
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
|
||||
<SwitchGroup name="enabled" label="Enabled" />
|
||||
<SwitchGroupWide name="enabled" label="Enabled" />
|
||||
</div>
|
||||
|
||||
<RadioFieldsetWide
|
||||
|
@ -532,81 +611,22 @@ export function DownloadClientUpdateForm({ client, isOpen, toggle }: any) {
|
|||
|
||||
{rulesComponentMap[values.type]}
|
||||
|
||||
<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-between">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center justify-center px-4 py-2 border border-transparent font-medium rounded-md text-red-700 bg-red-100 hover:bg-red-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:text-sm"
|
||||
onClick={toggleDeleteModal}
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
<div className="flex">
|
||||
<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-400 bg-white dark:bg-gray-700 hover:bg-gray-50 focus:border-rose-700 active:bg-rose-700",
|
||||
isTesting ? "cursor-not-allowed" : "",
|
||||
"mr-2 inline-flex 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-indigo-500 dark:focus:ring-blue-500"
|
||||
)}
|
||||
disabled={isTesting}
|
||||
onClick={() => testClient(values)}
|
||||
>
|
||||
{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"
|
||||
)}
|
||||
</button>
|
||||
<DownloadClientFormButtons
|
||||
type="UPDATE"
|
||||
toggleDeleteModal={toggleDeleteModal}
|
||||
isTesting={isTesting}
|
||||
isSuccessfulTest={isSuccessfulTest}
|
||||
isErrorTest={isErrorTest}
|
||||
cancelFn={toggle}
|
||||
testFn={testClient}
|
||||
values={values}
|
||||
/>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="mr-4 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-400 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-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-indigo-600 dark:bg-blue-600 hover:bg-indigo-700 dark:hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-blue-500"
|
||||
>
|
||||
Create
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DEBUG values={values} />
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}}
|
||||
</Form>
|
||||
</Formik>
|
||||
</div>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
|
|
|
@ -1,20 +1,22 @@
|
|||
import React, { Fragment } from "react";
|
||||
import { Fragment } from "react";
|
||||
import { useMutation, useQuery } from "react-query";
|
||||
import { Channel, Indexer, IndexerSchema, IndexerSchemaSettings, Network } from "../../domain/interfaces";
|
||||
import { sleep } from "../../utils/utils";
|
||||
import { sleep } from "../../utils";
|
||||
import { XIcon } from "@heroicons/react/solid";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { Field, Form } from "react-final-form";
|
||||
import { Field, FieldProps, Form, Formik } from "formik";
|
||||
import DEBUG from "../../components/debug";
|
||||
import Select from "react-select";
|
||||
import Select, { components, InputProps } from "react-select";
|
||||
import { queryClient } from "../../App";
|
||||
import { SwitchGroup, TextFieldWide } from "../../components/inputs";
|
||||
import APIClient from "../../api/APIClient";
|
||||
import { NumberFieldWide, PasswordFieldWide } from "../../components/inputs/wide";
|
||||
import { TextFieldWide, PasswordFieldWide, SwitchGroupWide } from "../../components/inputs/input_wide";
|
||||
|
||||
import { toast } from 'react-hot-toast'
|
||||
import Toast from '../../components/notifications/Toast';
|
||||
import { SlideOver } from "../../components/panels";
|
||||
|
||||
const Input = ({ type, ...rest }: InputProps) => <components.Input {...rest} />;
|
||||
|
||||
interface AddProps {
|
||||
isOpen: boolean;
|
||||
toggle: any;
|
||||
|
@ -44,7 +46,7 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
|
|||
onSuccess: (data) => {
|
||||
// console.log("irc mutation: ", data);
|
||||
|
||||
// queryClient.invalidateQueries(['indexer']);
|
||||
// queryClient.invalidateQueries(['networks']);
|
||||
// sleep(1500)
|
||||
|
||||
// toggle()
|
||||
|
@ -61,24 +63,24 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
|
|||
let channels: Channel[] = []
|
||||
if (ind.irc.channels.length) {
|
||||
ind.irc.channels.forEach(element => {
|
||||
channels.push({ name: element })
|
||||
channels.push({ name: element, password: "" })
|
||||
});
|
||||
}
|
||||
|
||||
const network: Network = {
|
||||
name: ind.name,
|
||||
enabled: false,
|
||||
server: formData.irc.server,
|
||||
port: formData.irc.port,
|
||||
tls: formData.irc.tls,
|
||||
server: ind.irc.server,
|
||||
port: ind.irc.port,
|
||||
tls: ind.irc.tls,
|
||||
nickserv: formData.irc.nickserv,
|
||||
invite_command: formData.irc.invite_command,
|
||||
settings: formData.irc.settings,
|
||||
channels: channels,
|
||||
}
|
||||
|
||||
console.log("network: ", network);
|
||||
|
||||
// console.log("network: ", network);
|
||||
// console.log("formData: ", formData);
|
||||
|
||||
mutation.mutate(formData, {
|
||||
onSuccess: (data) => {
|
||||
|
@ -86,7 +88,6 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
|
|||
ircMutation.mutate(network)
|
||||
}
|
||||
})
|
||||
|
||||
};
|
||||
|
||||
const renderSettingFields = (indexer: string) => {
|
||||
|
@ -109,7 +110,7 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
|
|||
return null
|
||||
})}
|
||||
<div hidden={true}>
|
||||
<TextFieldWide name={`name`} label="Name" defaultValue={ind?.name} />
|
||||
<TextFieldWide name="name" label="Name" defaultValue={ind?.name} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -141,11 +142,11 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
|
|||
return null
|
||||
})}
|
||||
|
||||
<div hidden={true}>
|
||||
<TextFieldWide name={`irc.server`} label="Server" defaultValue={ind.irc.server} />
|
||||
<NumberFieldWide name={`irc.port`} label="Port" defaultValue={ind.irc.port} />
|
||||
<SwitchGroup name="irc.tls" label="TLS" defaultValue={ind.irc.tls} />
|
||||
</div>
|
||||
{/* <div hidden={false}>
|
||||
<TextFieldWide name="irc.server" label="Server" defaultValue={ind.irc.server} />
|
||||
<NumberFieldWide name="irc.port" label="Port" defaultValue={ind.irc.port} />
|
||||
<SwitchGroupWide name="irc.tls" label="TLS" defaultValue={ind.irc.tls} />
|
||||
</div> */}
|
||||
</div>
|
||||
)}
|
||||
</Fragment>
|
||||
|
@ -170,18 +171,20 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
|
|||
leaveTo="translate-x-full"
|
||||
>
|
||||
<div className="w-screen max-w-2xl dark:border-gray-700 border-l">
|
||||
<Form
|
||||
<Formik
|
||||
enableReinitialize={true}
|
||||
initialValues={{
|
||||
enabled: true,
|
||||
identifier: "",
|
||||
irc: {}
|
||||
irc: {},
|
||||
settings: {},
|
||||
}}
|
||||
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{({ handleSubmit, values }) => {
|
||||
{({ values }) => {
|
||||
return (
|
||||
<form className="h-full flex flex-col bg-white dark:bg-gray-800 shadow-xl overflow-y-scroll"
|
||||
onSubmit={handleSubmit}>
|
||||
<Form className="h-full flex flex-col bg-white dark:bg-gray-800 shadow-xl overflow-y-scroll">
|
||||
<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">
|
||||
|
@ -206,11 +209,8 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="py-6 space-y-6 py-0 space-y-0 divide-y divide-gray-200 dark:divide-gray-700">
|
||||
|
||||
<div
|
||||
className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
|
||||
<div className="py-6 space-y-6 py-0 space-y-0 divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<div className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="identifier"
|
||||
|
@ -220,28 +220,29 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
|
|||
</label>
|
||||
</div>
|
||||
<div className="sm:col-span-2">
|
||||
<Field
|
||||
name="identifier"
|
||||
parse={val => val && val.value}
|
||||
format={val => data && data.find((o: any) => o.value === val)}
|
||||
render={({ input, meta }) => (
|
||||
<React.Fragment>
|
||||
<Select {...input}
|
||||
isClearable={true}
|
||||
placeholder="Choose an indexer"
|
||||
|
||||
options={data && data.sort((a, b): any => a.name.localeCompare(b.name)).map(v => ({
|
||||
label: v.name,
|
||||
value: v.identifier
|
||||
}))} />
|
||||
</React.Fragment>
|
||||
<Field name="identifier" type="select">
|
||||
{({ field, form: { setFieldValue } }: FieldProps) => (
|
||||
<Select {...field}
|
||||
isClearable={true}
|
||||
isSearchable={true}
|
||||
components={{ Input }}
|
||||
placeholder="Choose an indexer"
|
||||
value={field?.value && field.value.value}
|
||||
onChange={(option: any) => {
|
||||
setFieldValue(field.name, option?.value ?? "")
|
||||
}}
|
||||
options={data && data.sort((a, b): any => a.name.localeCompare(b.name)).map(v => ({
|
||||
label: v.name,
|
||||
value: v.identifier
|
||||
}))} />
|
||||
)}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
|
||||
<SwitchGroup name="enabled" label="Enabled" />
|
||||
<SwitchGroupWide name="enabled" label="Enabled" />
|
||||
</div>
|
||||
|
||||
|
||||
|
@ -272,10 +273,10 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
|
|||
</div>
|
||||
|
||||
<DEBUG values={values} />
|
||||
</form>
|
||||
</Form>
|
||||
)
|
||||
}}
|
||||
</Form>
|
||||
</Formik>
|
||||
</div>
|
||||
|
||||
</Transition.Child>
|
||||
|
@ -361,45 +362,38 @@ export function IndexerUpdateForm({ isOpen, toggle, indexer }: UpdateProps) {
|
|||
initialValues={initialValues}
|
||||
>
|
||||
{({ values }: any) => (
|
||||
<>
|
||||
<div
|
||||
className="py-6 space-y-6 sm:py-0 sm:space-y-0 divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<div
|
||||
className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="name"
|
||||
className="block text-sm font-medium text-gray-900 dark:text-white sm:mt-px sm:pt-2"
|
||||
>
|
||||
Name
|
||||
</label>
|
||||
</div>
|
||||
<Field name="name">
|
||||
{({ input, meta }) => (
|
||||
<div className="sm:col-span-2">
|
||||
<input
|
||||
type="text"
|
||||
{...input}
|
||||
className="block w-full shadow-sm dark:bg-gray-800 sm:text-sm dark:text-white focus:ring-indigo-500 focus:border-indigo-500 border-gray-300 dark:border-gray-700 rounded-md"
|
||||
/>
|
||||
{meta.touched && meta.error &&
|
||||
<span>{meta.error}</span>}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
<div className="py-6 space-y-6 sm:py-0 sm:space-y-0 divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<div className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="name"
|
||||
className="block text-sm font-medium text-gray-900 dark:text-white sm:mt-px sm:pt-2"
|
||||
>
|
||||
Name
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200 dark:sm:divide-gray-700">
|
||||
<SwitchGroup name="enabled" label="Enabled" />
|
||||
</div>
|
||||
|
||||
{renderSettingFields(indexer.settings)}
|
||||
|
||||
<Field name="name">
|
||||
{({ field, meta }: FieldProps) => (
|
||||
<div className="sm:col-span-2">
|
||||
<input
|
||||
type="text"
|
||||
{...field}
|
||||
className="block w-full shadow-sm dark:bg-gray-800 sm:text-sm dark:text-white focus:ring-indigo-500 focus:border-indigo-500 border-gray-300 dark:border-gray-700 rounded-md"
|
||||
/>
|
||||
{meta.touched && meta.error &&
|
||||
<span>{meta.error}</span>}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
</>
|
||||
)}
|
||||
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200 dark:sm:divide-gray-700">
|
||||
<SwitchGroupWide name="enabled" label="Enabled" />
|
||||
</div>
|
||||
|
||||
{renderSettingFields(indexer.settings)}
|
||||
</div>
|
||||
)}
|
||||
</SlideOver>
|
||||
)
|
||||
}
|
|
@ -1,19 +1,83 @@
|
|||
import { useMutation } from "react-query";
|
||||
import { Network } from "../../domain/interfaces";
|
||||
import { Channel, Network } from "../../domain/interfaces";
|
||||
import { XIcon } from "@heroicons/react/solid";
|
||||
import { Field } from "react-final-form";
|
||||
import { SwitchGroup, TextFieldWide } from "../../components/inputs";
|
||||
import { queryClient } from "../../App";
|
||||
|
||||
import arrayMutators from "final-form-arrays";
|
||||
import { FieldArray } from "react-final-form-arrays";
|
||||
import { Field, FieldArray, FieldProps } from "formik";
|
||||
import APIClient from "../../api/APIClient";
|
||||
import { NumberFieldWide, PasswordFieldWide } from "../../components/inputs/wide";
|
||||
|
||||
import { TextFieldWide, PasswordFieldWide, SwitchGroupWide, NumberFieldWide } from "../../components/inputs/input_wide";
|
||||
|
||||
import { toast } from 'react-hot-toast';
|
||||
import Toast from '../../components/notifications/Toast';
|
||||
import { SlideOver } from "../../components/panels";
|
||||
|
||||
function ChannelsFieldArray({ values }: any) {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<FieldArray name="channels">
|
||||
{({ remove, push }) => (
|
||||
<div className="flex flex-col border-2 border-dashed dark:border-gray-700 p-4">
|
||||
{values && values.channels.length > 0 ? (
|
||||
values.channels.map((_channel: Channel, index: number) => (
|
||||
<div key={index} className="flex justify-between">
|
||||
<div className="flex">
|
||||
<Field name={`channels.${index}.name`}>
|
||||
{({ field }: FieldProps) => (
|
||||
<input
|
||||
{...field}
|
||||
type="text"
|
||||
value={field.value ?? ""}
|
||||
onChange={field.onChange}
|
||||
placeholder="#Channel"
|
||||
className="mr-4 dark:bg-gray-700 focus:ring-indigo-500 dark:focus:ring-blue-500 focus:border-indigo-500 dark:focus:border-blue-500 border-gray-300 dark:border-gray-600 block w-full shadow-sm sm:text-sm dark:text-white rounded-md"
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<Field name={`channels.${index}.password`}>
|
||||
{({ field }: FieldProps) => (
|
||||
<input
|
||||
{...field}
|
||||
type="text"
|
||||
value={field.value ?? ""}
|
||||
onChange={field.onChange}
|
||||
placeholder="Password"
|
||||
className="mr-4 dark:bg-gray-700 focus:ring-indigo-500 dark:focus:ring-blue-500 focus:border-indigo-500 dark:focus:border-blue-500 border-gray-300 dark:border-gray-600 block w-full shadow-sm sm:text-sm dark:text-white rounded-md"
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
<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-indigo-500 dark:focus:ring-blue-500"
|
||||
onClick={() => remove(index)}
|
||||
>
|
||||
<span className="sr-only">Remove</span>
|
||||
<XIcon className="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<span className="text-center text-sm text-grey-darker dark:text-white">
|
||||
No channels!
|
||||
</span>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="border dark:border-gray-600 dark:bg-gray-700 my-4 px-4 py-2 text-sm text-gray-700 dark:text-white hover:bg-gray-50 dark:hover:bg-gray-600 rounded self-center text-center"
|
||||
onClick={() => push({ name: "", password: "" })}
|
||||
>
|
||||
Add Channel
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</FieldArray>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function IrcNetworkAddForm({ isOpen, toggle }: any) {
|
||||
const mutation = useMutation((network: Network) => APIClient.irc.createNetwork(network), {
|
||||
onSuccess: (data) => {
|
||||
|
@ -66,15 +130,13 @@ export function IrcNetworkAddForm({ isOpen, toggle }: any) {
|
|||
name: "",
|
||||
enabled: true,
|
||||
server: "",
|
||||
port: 6667,
|
||||
tls: false,
|
||||
pass: "",
|
||||
nickserv: {
|
||||
account: ""
|
||||
}
|
||||
}
|
||||
|
||||
const mutators = {
|
||||
...arrayMutators
|
||||
},
|
||||
channels: [],
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -85,19 +147,16 @@ export function IrcNetworkAddForm({ isOpen, toggle }: any) {
|
|||
toggle={toggle}
|
||||
onSubmit={onSubmit}
|
||||
initialValues={initialValues}
|
||||
mutators={mutators}
|
||||
validate={validate}
|
||||
>
|
||||
{() => (
|
||||
{(values) => (
|
||||
<>
|
||||
|
||||
|
||||
<TextFieldWide name="name" label="Name" placeholder="Name" required={true} />
|
||||
|
||||
<div className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y dark:divide-gray-700">
|
||||
|
||||
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200 dark:sm:divide-gray-700">
|
||||
<SwitchGroup name="enabled" label="Enabled" />
|
||||
<SwitchGroupWide name="enabled" label="Enabled" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
@ -105,7 +164,7 @@ export function IrcNetworkAddForm({ isOpen, toggle }: any) {
|
|||
<NumberFieldWide name="port" label="Port" placeholder="Eg 6667" required={true} />
|
||||
|
||||
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
|
||||
<SwitchGroup name="tls" label="TLS" />
|
||||
<SwitchGroupWide name="tls" label="TLS" />
|
||||
</div>
|
||||
|
||||
<PasswordFieldWide name="pass" label="Password" help="Network password" />
|
||||
|
@ -117,57 +176,7 @@ export function IrcNetworkAddForm({ isOpen, toggle }: any) {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-6">
|
||||
|
||||
<FieldArray name="channels">
|
||||
{({ fields }) => (
|
||||
<div className="flex flex-col border-2 border-dashed dark:border-gray-700 p-4">
|
||||
{fields && (fields.length as any) > 0 ? (
|
||||
fields.map((name, index) => (
|
||||
<div key={name} className="flex justify-between">
|
||||
<div className="flex">
|
||||
<Field
|
||||
name={`${name}.name`}
|
||||
component="input"
|
||||
type="text"
|
||||
placeholder="#Channel"
|
||||
className="mr-4 dark:bg-gray-700 focus:ring-indigo-500 dark:focus:ring-blue-500 focus:border-indigo-500 dark:focus:border-blue-500 border-gray-300 dark:border-gray-600 block w-full shadow-sm sm:text-sm dark:text-white rounded-md"
|
||||
/>
|
||||
<Field
|
||||
name={`${name}.password`}
|
||||
component="input"
|
||||
type="text"
|
||||
placeholder="Password"
|
||||
className="dark:bg-gray-700 focus:ring-indigo-500 dark:focus:ring-blue-500 focus:border-indigo-500 dark:focus:border-blue-500 border-gray-300 dark:border-gray-600 block w-full shadow-sm sm:text-sm dark:text-white rounded-md"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<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-indigo-500 dark:focus:ring-blue-500"
|
||||
onClick={() => fields.remove(index)}
|
||||
>
|
||||
<span className="sr-only">Remove</span>
|
||||
<XIcon className="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<span className="text-center text-sm text-grey-darker dark:text-white">
|
||||
No channels!
|
||||
</span>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="border dark:border-gray-600 dark:bg-gray-700 my-4 px-4 py-2 text-sm text-gray-700 dark:text-white hover:bg-gray-50 dark:hover:bg-gray-600 rounded self-center text-center"
|
||||
onClick={() => fields.push({ name: "", password: "" })}
|
||||
>
|
||||
Add Channel
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</FieldArray>
|
||||
</div>
|
||||
<ChannelsFieldArray values={values} />
|
||||
</>
|
||||
)}
|
||||
</SlideOver>
|
||||
|
@ -193,8 +202,6 @@ export function IrcNetworkUpdateForm({ isOpen, toggle, network }: any) {
|
|||
})
|
||||
|
||||
const onSubmit = (data: any) => {
|
||||
console.log(data)
|
||||
|
||||
// easy way to split textarea lines into array of strings for each newline.
|
||||
// parse on the field didn't really work.
|
||||
// TODO fix connect_commands on network update
|
||||
|
@ -241,14 +248,9 @@ export function IrcNetworkUpdateForm({ isOpen, toggle, network }: any) {
|
|||
nickserv: network.nickserv,
|
||||
pass: network.pass,
|
||||
invite_command: network.invite_command,
|
||||
// connect_commands: network.connect_commands,
|
||||
channels: network.channels
|
||||
}
|
||||
|
||||
const mutators = {
|
||||
...arrayMutators
|
||||
}
|
||||
|
||||
return (
|
||||
<SlideOver
|
||||
type="UPDATE"
|
||||
|
@ -258,18 +260,16 @@ export function IrcNetworkUpdateForm({ isOpen, toggle, network }: any) {
|
|||
onSubmit={onSubmit}
|
||||
deleteAction={deleteAction}
|
||||
initialValues={initialValues}
|
||||
mutators={mutators}
|
||||
validate={validate}
|
||||
>
|
||||
{() => (
|
||||
{(values) => (
|
||||
<>
|
||||
|
||||
<TextFieldWide name="name" label="Name" placeholder="Name" required={true} />
|
||||
|
||||
<div className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y dark:divide-gray-700">
|
||||
|
||||
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0">
|
||||
<SwitchGroup name="enabled" label="Enabled" />
|
||||
<SwitchGroupWide name="enabled" label="Enabled" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
@ -277,7 +277,7 @@ export function IrcNetworkUpdateForm({ isOpen, toggle, network }: any) {
|
|||
<NumberFieldWide name="port" label="Port" placeholder="Eg 6667" required={true} />
|
||||
|
||||
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
|
||||
<SwitchGroup name="tls" label="TLS" />
|
||||
<SwitchGroupWide name="tls" label="TLS" />
|
||||
</div>
|
||||
|
||||
<PasswordFieldWide name="pass" label="Password" help="Network password" />
|
||||
|
@ -289,57 +289,7 @@ export function IrcNetworkUpdateForm({ isOpen, toggle, network }: any) {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-6">
|
||||
|
||||
<FieldArray name="channels">
|
||||
{({ fields }) => (
|
||||
<div className="flex flex-col border-2 border-dashed dark:border-gray-700 p-4">
|
||||
{fields && (fields.length as any) > 0 ? (
|
||||
fields.map((name, index) => (
|
||||
<div key={name} className="flex justify-between">
|
||||
<div className="flex">
|
||||
<Field
|
||||
name={`${name}.name`}
|
||||
component="input"
|
||||
type="text"
|
||||
placeholder="#Channel"
|
||||
className="mr-4 dark:bg-gray-700 focus:ring-indigo-500 dark:focus:ring-blue-500 focus:border-indigo-500 dark:focus:border-blue-500 border-gray-300 dark:border-gray-600 block w-full shadow-sm sm:text-sm dark:text-white rounded-md"
|
||||
/>
|
||||
<Field
|
||||
name={`${name}.password`}
|
||||
component="input"
|
||||
type="text"
|
||||
placeholder="Password"
|
||||
className="dark:bg-gray-700 focus:ring-indigo-500 dark:focus:ring-blue-500 focus:border-indigo-500 dark:focus:border-blue-500 border-gray-300 dark:border-gray-600 block w-full shadow-sm sm:text-sm dark:text-white rounded-md"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<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-indigo-500 dark:focus:ring-blue-500"
|
||||
onClick={() => fields.remove(index)}
|
||||
>
|
||||
<span className="sr-only">Remove</span>
|
||||
<XIcon className="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<span className="text-center text-sm text-grey-darker dark:text-white">
|
||||
No channels!
|
||||
</span>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="border dark:border-gray-600 dark:bg-gray-700 my-4 px-4 py-2 text-sm text-gray-700 dark:text-white hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-blue-500 rounded self-center text-center"
|
||||
onClick={() => fields.push({ name: "", password: "" })}
|
||||
>
|
||||
Add Channel
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</FieldArray>
|
||||
</div>
|
||||
<ChannelsFieldArray values={values} />
|
||||
</>
|
||||
)}
|
||||
</SlideOver>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue