autobrr/web/src/forms/settings/IndexerForms.tsx
Ludvig Lundgren 2daeedbdc7
Feature: Support multi user per irc network (#78)
* feat: delete irc network in transaction

* feat: use compound key for irc handlers

* chore: update deps

* refactor: start network

* refactor: init irc handler

* indexers: update network name
2022-01-13 21:26:23 +01:00

398 lines
No EOL
20 KiB
TypeScript

import { Fragment } from "react";
import { useMutation, useQuery } from "react-query";
import { Channel, Indexer, IndexerSchema, IndexerSchemaSettings, Network } from "../../domain/interfaces";
import { sleep } from "../../utils";
import { XIcon } from "@heroicons/react/solid";
import { Dialog, Transition } from "@headlessui/react";
import { Field, FieldProps, Form, Formik } from "formik";
import DEBUG from "../../components/debug";
import Select, { components, InputProps } from "react-select";
import { queryClient } from "../../App";
import APIClient from "../../api/APIClient";
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;
}
export function IndexerAddForm({ isOpen, toggle }: AddProps) {
const { data } = useQuery<IndexerSchema[], Error>('indexerSchema', APIClient.indexers.getSchema,
{
enabled: isOpen,
refetchOnWindowFocus: false
}
)
const mutation = useMutation((indexer: Indexer) => APIClient.indexers.create(indexer), {
onSuccess: () => {
queryClient.invalidateQueries(['indexer']);
toast.custom((t) => <Toast type="success" body="Indexer was added" t={t} />)
sleep(1500)
toggle()
},
onError: () => {
toast.custom((t) => <Toast type="error" body="Indexer could not be added" t={t} />)
}
})
const ircMutation = useMutation((network: Network) => APIClient.irc.createNetwork(network), {
onSuccess: (data) => {
// console.log("irc mutation: ", data);
// queryClient.invalidateQueries(['networks']);
// sleep(1500)
// toggle()
}
})
const onSubmit = (formData: any) => {
let ind = data && data.find(i => i.identifier === formData.identifier)
if (!ind) {
return
}
let channels: Channel[] = []
if (ind.irc.channels.length) {
ind.irc.channels.forEach(element => {
channels.push({ name: element, password: "" })
});
}
const network: Network = {
name: ind.irc.network,
enabled: false,
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,
}
mutation.mutate(formData, {
onSuccess: (data) => {
// create irc
ircMutation.mutate(network)
}
})
};
const renderSettingFields = (indexer: string) => {
if (indexer !== "") {
let ind = data && data.find(i => i.identifier === indexer)
return (
<div key="opt">
{ind && ind.settings && ind.settings.map((f: any, idx: number) => {
switch (f.type) {
case "text":
return (
<TextFieldWide name={`settings.${f.name}`} label={f.label} key={idx} help={f.help} defaultValue="" />
)
case "secret":
return (
<PasswordFieldWide name={`settings.${f.name}`} label={f.label} key={idx} help={f.help} defaultValue="" />
)
}
return null
})}
<div hidden={true}>
<TextFieldWide name="name" label="Name" defaultValue={ind?.name} />
</div>
</div>
)
}
}
const renderIrcSettingFields = (indexer: string) => {
if (indexer !== "") {
let ind = data && data.find(i => i.identifier === indexer)
return (
<Fragment>
{ind && ind.irc && ind.irc.settings && (
<div className="border-t border-gray-200 dark:border-gray-700 py-5">
<div className="px-6 space-y-1">
<Dialog.Title className="text-lg font-medium text-gray-900 dark:text-white">IRC</Dialog.Title>
<p className="text-sm text-gray-500 dark:text-gray-200">
Networks, channels and invite commands are configured automatically.
</p>
</div>
{ind.irc.settings.map((f: IndexerSchemaSettings, idx: number) => {
switch (f.type) {
case "text":
return <TextFieldWide name={`irc.${f.name}`} label={f.label} required={f.required} key={idx} help={f.help} />
case "secret":
return <PasswordFieldWide name={`irc.${f.name}`} label={f.label} required={f.required} key={idx} help={f.help} defaultValue={f.default} />
}
return null
})}
{/* <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>
)
}
}
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 dark:border-gray-700 border-l">
<Formik
enableReinitialize={true}
initialValues={{
enabled: true,
identifier: "",
name: "",
irc: {},
settings: {},
}}
onSubmit={onSubmit}
>
{({ values }) => {
return (
<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">
<div className="space-y-1">
<Dialog.Title
className="text-lg font-medium text-gray-900 dark:text-white">Add
indexer</Dialog.Title>
<p className="text-sm text-gray-500 dark:text-gray-200">
Add indexer.
</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-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 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"
className="block text-sm font-medium text-gray-900 dark:text-white sm:mt-px sm:pt-2"
>
Indexer
</label>
</div>
<div className="sm:col-span-2">
<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("name", option?.label ?? "")
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">
<SwitchGroupWide name="enabled" label="Enabled" />
</div>
{renderSettingFields(values.identifier)}
</div>
{renderIrcSettingFields(values.identifier)}
</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-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"
>
Save
</button>
</div>
</div>
<DEBUG values={values} />
</Form>
)
}}
</Formik>
</div>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
)
}
interface UpdateProps {
isOpen: boolean;
toggle: any;
indexer: Indexer;
}
export function IndexerUpdateForm({ isOpen, toggle, indexer }: UpdateProps) {
const mutation = useMutation((indexer: Indexer) => APIClient.indexers.update(indexer), {
onSuccess: () => {
queryClient.invalidateQueries(['indexer']);
toast.custom((t) => <Toast type="success" body={`${indexer.name} was updated successfully`} t={t} />)
sleep(1500)
toggle()
}
})
const deleteMutation = useMutation((id: number) => APIClient.indexers.delete(id), {
onSuccess: () => {
queryClient.invalidateQueries(['indexer']);
toast.custom((t) => <Toast type="success" body={`${indexer.name} was deleted.`} t={t} />)
}
})
const onSubmit = (data: any) => {
// TODO clear data depending on type
mutation.mutate(data)
};
const deleteAction = () => {
deleteMutation.mutate(indexer.id)
}
const renderSettingFields = (settings: any[]) => {
if (settings !== []) {
return (
<div key="opt">
{settings && settings.map((f: any, idx: number) => {
switch (f.type) {
case "text":
return (
<TextFieldWide name={`settings.${f.name}`} label={f.label} key={idx} help={f.help} />
)
case "secret":
return (
<PasswordFieldWide name={`settings.${f.name}`} label={f.label} key={idx} help={f.help} />
)
}
return null
})}
</div>
)
}
}
let initialValues = {
id: indexer.id,
name: indexer.name,
enabled: indexer.enabled,
identifier: indexer.identifier,
settings: indexer.settings.reduce((o: any, obj: any) => ({ ...o, [obj.name]: obj.value }), {}),
}
return (
<SlideOver
type="UPDATE"
title="Indexer"
isOpen={isOpen}
toggle={toggle}
deleteAction={deleteAction}
onSubmit={onSubmit}
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">
{({ 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>
)
}