feat(filters): add external script and webhook checks

This commit is contained in:
ze0s 2022-07-23 15:19:28 +02:00
parent 16dd8c5419
commit d56693cd33
17 changed files with 635 additions and 200 deletions

View file

@ -13,6 +13,7 @@ interface TextFieldProps {
columns?: COL_WIDTHS;
autoComplete?: string;
hidden?: boolean;
disabled?: boolean;
}
export const TextField = ({
@ -22,7 +23,8 @@ export const TextField = ({
placeholder,
columns,
autoComplete,
hidden
hidden,
disabled,
}: TextFieldProps) => (
<div
className={classNames(
@ -47,7 +49,12 @@ export const TextField = ({
type="text"
defaultValue={defaultValue}
autoComplete={autoComplete}
className={classNames(meta.touched && meta.error ? "focus:ring-red-500 focus:border-red-500 border-red-500" : "focus:ring-indigo-500 dark:focus:ring-blue-500 focus:border-indigo-500 dark:focus:border-blue-500 border-gray-300 dark:border-gray-700", "mt-2 block w-full dark:bg-gray-800 dark:text-gray-100 rounded-md")}
className={classNames(
meta.touched && meta.error ? "focus:ring-red-500 focus:border-red-500 border-red-500" : "focus:ring-indigo-500 dark:focus:ring-blue-500 focus:border-indigo-500 dark:focus:border-blue-500 border-gray-300 dark:border-gray-700",
disabled ? "bg-gray-100 dark:bg-gray-700 cursor-not-allowed" : "dark:bg-gray-800",
"mt-2 block w-full dark:text-gray-100 rounded-md",
)}
disabled={disabled}
placeholder={placeholder}
/>
@ -60,6 +67,70 @@ export const TextField = ({
</div>
);
interface TextAreaProps {
name: string;
defaultValue?: string;
label?: string;
placeholder?: string;
columns?: COL_WIDTHS;
rows?: number;
autoComplete?: string;
hidden?: boolean;
disabled?: boolean;
}
export const TextArea = ({
name,
defaultValue,
label,
placeholder,
columns,
rows,
autoComplete,
hidden,
disabled,
}: TextAreaProps) => (
<div
className={classNames(
hidden ? "hidden" : "",
columns ? `col-span-${columns}` : "col-span-12"
)}
>
{label && (
<label htmlFor={name} className="block text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide">
{label}
</label>
)}
<Field name={name}>
{({
field,
meta
}: FieldProps) => (
<div>
<textarea
{...field}
id={name}
rows={rows}
defaultValue={defaultValue}
autoComplete={autoComplete}
className={classNames(
meta.touched && meta.error ? "focus:ring-red-500 focus:border-red-500 border-red-500" : "focus:ring-indigo-500 dark:focus:ring-blue-500 focus:border-indigo-500 dark:focus:border-blue-500 border-gray-300 dark:border-gray-700",
disabled ? "bg-gray-100 dark:bg-gray-700 cursor-not-allowed" : "dark:bg-gray-800",
"mt-2 block w-full dark:text-gray-100 rounded-md",
)}
placeholder={placeholder}
disabled={disabled}
/>
{meta.touched && meta.error && (
<p className="error text-sm text-red-600 mt-1">* {meta.error}</p>
)}
</div>
)}
</Field>
</div>
);
interface PasswordFieldProps {
name: string;
label?: string;
@ -132,13 +203,15 @@ interface NumberFieldProps {
label?: string;
placeholder?: string;
step?: number;
disabled?: boolean;
}
export const NumberField = ({
name,
label,
placeholder,
step
step,
disabled,
}: NumberFieldProps) => (
<div className="col-span-12 sm:col-span-6">
<label htmlFor={name} className="block text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide">
@ -159,9 +232,11 @@ export const NumberField = ({
meta.touched && meta.error
? "focus:ring-red-500 focus:border-red-500 border-red-500"
: "focus:ring-indigo-500 dark:focus:ring-blue-500 focus:border-indigo-500 dark:focus:border-blue-500 border-gray-300",
"mt-2 block w-full dark:bg-gray-800 border border-gray-300 dark:border-gray-700 dark:text-gray-100 rounded-md"
"mt-2 block w-full border border-gray-300 dark:border-gray-700 dark:text-gray-100 rounded-md",
disabled ? "bg-gray-100 dark:bg-gray-700 cursor-not-allowed" : "dark:bg-gray-800",
)}
placeholder={placeholder}
disabled={disabled}
/>
{meta.touched && meta.error && (
<div className="error">{meta.error}</div>

View file

@ -74,16 +74,18 @@ interface SwitchGroupProps {
label?: string;
description?: string;
className?: string;
heading?: boolean;
}
const SwitchGroup = ({
name,
label,
description
description,
heading,
}: SwitchGroupProps) => (
<HeadlessSwitch.Group as="ol" className="py-4 flex items-center justify-between">
{label && <div className="flex flex-col">
<HeadlessSwitch.Label as="p" className="text-sm font-medium text-gray-900 dark:text-gray-100"
<HeadlessSwitch.Label as={heading ? "h2" : "p"} className={classNames("font-medium text-gray-900 dark:text-gray-100", heading ? "text-lg" : "text-sm")}
passive>
{label}
</HeadlessSwitch.Label>

View file

@ -9,7 +9,7 @@ import {
useParams
} from "react-router-dom";
import { toast } from "react-hot-toast";
import { Field, FieldArray, FieldProps, Form, Formik, FormikValues } from "formik";
import { Field, FieldArray, FieldProps, Form, Formik, FormikValues, useFormikContext } from "formik";
import { Dialog, Transition, Switch as SwitchBasic } from "@headlessui/react";
import { ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/solid";
@ -50,6 +50,7 @@ import { AlertWarning } from "../../components/alerts";
import { DeleteModal } from "../../components/modals";
import { TitleSubtitle } from "../../components/headings";
import { EmptyListState } from "../../components/emptystates";
import { TextArea } from "../../components/inputs/input";
interface tabType {
name: string;
@ -61,6 +62,7 @@ const tabs: tabType[] = [
{ name: "Movies and TV", href: "movies-tv" },
{ name: "Music", href: "music" },
{ name: "Advanced", href: "advanced" },
{ name: "External", href: "external" },
{ name: "Actions", href: "actions" }
];
@ -280,7 +282,15 @@ export default function FilterDetails() {
albums: filter.albums,
origins: filter.origins || [],
indexers: filter.indexers || [],
actions: filter.actions || []
actions: filter.actions || [],
external_script_enabled: filter.external_script_enabled || false,
external_script_cmd: filter.external_script_cmd || "",
external_script_args: filter.external_script_args || "",
external_script_expect_status: filter.external_script_expect_status || 0,
external_webhook_enabled: filter.external_webhook_enabled || false,
external_webhook_host: filter.external_webhook_host || "",
external_webhook_data: filter.external_webhook_data ||"",
external_webhook_expect_status: filter.external_webhook_expect_status || 0,
} as Filter}
onSubmit={handleSubmit}
>
@ -291,6 +301,7 @@ export default function FilterDetails() {
<Route path="movies-tv" element={<MoviesTv />} />
<Route path="music" element={<Music />} />
<Route path="advanced" element={<Advanced />} />
<Route path="external" element={<External />} />
<Route path="actions" element={<FilterActions filter={filter} values={values} />}
/>
</Routes>
@ -527,6 +538,75 @@ function CollapsableSection({ title, subtitle, children }: CollapsableSectionPro
);
}
export function External() {
const { values } = useFormikContext<Filter>();
return (
<div>
<div className="mt-6">
<SwitchGroup name="external_script_enabled" heading={true} label="Script" description="Run external script and check status as part of filtering" />
<div className="mt-6 grid grid-cols-12 gap-6">
<TextField
name="external_script_cmd"
label="Command"
columns={6}
placeholder="Path to program eg. /bin/test"
disabled={!values.external_script_enabled}
/>
<TextField
name="external_script_args"
label="Arguments"
columns={6}
placeholder="Arguments eg. --test"
disabled={!values.external_script_enabled}
/>
<NumberField
name="external_script_expect_status"
label="Expected exit status"
placeholder="0"
disabled={!values.external_script_enabled}
/>
</div>
</div>
<div className="mt-6">
<div className="border-t dark:border-gray-700">
<SwitchGroup name="external_webhook_enabled" heading={true} label="Webhook" description="Run external webhook and check status as part of filtering" />
</div>
<div className="mt-6 grid grid-cols-12 gap-6">
<div className="grid col-span-6 gap-6">
<TextField
name="external_webhook_host"
label="Host"
columns={6}
placeholder="Host eg. http://localhost/webhook"
disabled={!values.external_webhook_enabled}
/>
<NumberField
name="external_webhook_expect_status"
label="Expected http status"
placeholder="200"
disabled={!values.external_webhook_enabled}
/>
</div>
<TextArea
name="external_webhook_data"
label="Data (json)"
columns={6}
rows={5}
placeholder={"{ \"key\": \"value\" }"}
disabled={!values.external_webhook_enabled}
/>
</div>
</div>
</div>
);
}
interface FilterActionsProps {
filter: Filter;
values: FormikValues;

View file

@ -52,6 +52,14 @@ interface Filter {
except_tags_any: string;
actions: Action[];
indexers: Indexer[];
external_script_enabled: boolean;
external_script_cmd: string;
external_script_args: string;
external_script_expect_status: number;
external_webhook_enabled: boolean;
external_webhook_host: string;
external_webhook_data: string;
external_webhook_expect_status: number;
}
interface Action {