diff --git a/internal/filter/service.go b/internal/filter/service.go index b8f6cf8..28a76cf 100644 --- a/internal/filter/service.go +++ b/internal/filter/service.go @@ -189,6 +189,16 @@ func (s *service) Update(filter domain.Filter) (*domain.Filter, error) { } } + // store actions + if filter.Actions != nil { + for _, action := range filter.Actions { + if _, err := s.actionRepo.Store(action); err != nil { + log.Error().Err(err).Msgf("could not store filter actions: %v", filter.Name) + return nil, err + } + } + } + return f, nil } diff --git a/web/package.json b/web/package.json index 122e207..a9585bb 100644 --- a/web/package.json +++ b/web/package.json @@ -17,6 +17,7 @@ "@types/react-dom": "^17.0.0", "final-form": "^4.20.2", "final-form-arrays": "^3.0.2", + "formik": "^2.2.9", "react": "^17.0.2", "react-cookie": "^4.1.1", "react-dom": "^17.0.2", diff --git a/web/src/App.tsx b/web/src/App.tsx index 2bb0936..5ce99a0 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -22,11 +22,9 @@ function App() { return ( - - diff --git a/web/src/components/inputs/TextField.tsx b/web/src/components/inputs/TextField.tsx index c5196f4..24284fc 100644 --- a/web/src/components/inputs/TextField.tsx +++ b/web/src/components/inputs/TextField.tsx @@ -32,6 +32,7 @@ const TextField: React.FC = ({ name, label, placeholder, columns , classN {...input} id={name} type="text" + value={input.value} autoComplete={autoComplete} className="mt-2 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-light-blue-500 focus:border-light-blue-500 sm:text-sm" placeholder={placeholder} diff --git a/web/src/screens/Base.tsx b/web/src/screens/Base.tsx index cf0115e..6dc8ae7 100644 --- a/web/src/screens/Base.tsx +++ b/web/src/screens/Base.tsx @@ -1,207 +1,207 @@ -import {Fragment} from 'react' -import {Disclosure, Menu, Transition} from '@headlessui/react' -import {BellIcon, ChevronDownIcon, MenuIcon, XIcon} from '@heroicons/react/outline' -import {NavLink,Link, Route, Switch} from "react-router-dom"; +import { Fragment } from 'react' +import { Disclosure, Menu, Transition } from '@headlessui/react' +import { ChevronDownIcon, MenuIcon, XIcon } from '@heroicons/react/outline' +import { NavLink, Link, Route, Switch } from "react-router-dom"; import Settings from "./Settings"; import { Dashboard } from "./Dashboard"; -import { FilterDetails, Filters} from "./Filters"; +import { FilterDetails, Filters } from "./filters"; import Logs from './Logs'; - -const profile = ['Settings', 'Sign out'] +import logo from '../logo.png'; function classNames(...classes: string[]) { return classes.filter(Boolean).join(' ') } export default function Base() { - const nav = [{name: 'Dashboard', path: "/"}, {name: 'Filters', path: "/filters"}, {name: "Settings", path: "/settings"},{name: "Logs", path: "/logs"}] + const nav = [{ name: 'Dashboard', path: "/" }, { name: 'Filters', path: "/filters" }, { name: "Settings", path: "/settings" }, { name: "Logs", path: "/logs" }] return ( -
-
- - {({open}) => ( - <> -
-
-
-
-
-
- {nav.map((item, itemIdx) => - - {item.name} - - )} -
-
+
+ + {({ open }) => ( + <> +
+
+
+
+
+ Logo + Logo
-
-
- {/**/} - {/* View notifications*/} - {/*
-
-
- {/* Mobile menu button */} - - Open main menu - {open ? ( -
+
+
+ + {({ open }) => ( + <> +
+ + + Open user menu for User + + +
+ + + + {({ active }) => ( + + Settings + + )} + + + {({ active }) => ( + + Logout + + )} + + + + + )} +
+
+
+
+ {/* Mobile menu button */} + + Open main menu + {open ? ( + +
+
- -
- {nav.map((item, itemIdx) => - itemIdx === 0 ? ( - - {/* Current: "bg-gray-900 text-white", Default: "text-gray-300 hover:bg-gray-700 hover:text-white" */} - - {item.name} - - - ) : ( - + +
+ {nav.map((item, itemIdx) => + itemIdx === 0 ? ( + + {/* Current: "bg-gray-900 text-white", Default: "text-gray-300 hover:bg-gray-700 hover:text-white" */} + {item.name} - ) - )} -
-
-
-
-
User
- {/*
tom@example.com
*/} -
- -
-
- {profile.map((item) => ( - - {item} - - ))} + + ) : ( + + {item.name} + + ) + )} +
+
+
+
+
User
- - - )} - +
+ + Settings + + + Logout + +
+
+ + + )} + - - - - + + + + - - - + + + - - - + + + - - - + + + - - - - -
+ + + +
) } \ No newline at end of file diff --git a/web/src/screens/Filters.tsx b/web/src/screens/Filters.tsx deleted file mode 100644 index 6e55f46..0000000 --- a/web/src/screens/Filters.tsx +++ /dev/null @@ -1,930 +0,0 @@ -import React, {Fragment, useRef, useState} from "react"; -import {Dialog, Switch, Transition} from "@headlessui/react"; -import {ChevronDownIcon, ChevronRightIcon, ExclamationIcon,} from '@heroicons/react/solid' -import {EmptyListState} from "../components/EmptyListState"; - -import { - Link, - NavLink, - Route, - Switch as RouteSwitch, - useHistory, - useLocation, - useParams, - useRouteMatch -} from "react-router-dom"; -import {FilterActionList} from "../components/FilterActionList"; -import {DownloadClient, Filter, Indexer} from "../domain/interfaces"; -import {useToggle} from "../hooks/hooks"; -import {useMutation, useQuery} from "react-query"; -import {queryClient} from "../App"; -import {CONTAINER_OPTIONS, CODECS_OPTIONS, RESOLUTION_OPTIONS, SOURCES_OPTIONS} from "../domain/constants"; -import {Field, Form} from "react-final-form"; -import {MultiSelectField, TextField} from "../components/inputs"; -import DEBUG from "../components/debug"; -import TitleSubtitle from "../components/headings/TitleSubtitle"; -import { SwitchGroup } from "../components/inputs"; -import {classNames} from "../styles/utils"; -import { FilterAddForm, FilterActionAddForm} from "../forms"; -import Select from "react-select"; -import APIClient from "../api/APIClient"; - -import { toast } from 'react-hot-toast' -import Toast from '../components/notifications/Toast'; - - -const tabs = [ - {name: 'General', href: '', current: true}, - // { name: 'TV', href: 'tv', current: false }, - // { name: 'Movies', href: 'movies', current: false }, - {name: 'Movies and TV', href: 'movies-tv', current: false}, - // { name: 'P2P', href: 'p2p', current: false }, - {name: 'Advanced', href: 'advanced', current: false}, - {name: 'Actions', href: 'actions', current: false}, -] - -function TabNavLink({item, url}: any) { - const location = useLocation(); - - const {pathname} = location; - const splitLocation = pathname.split("/"); - - // we need to clean the / if it's a base root path - let too = item.href ? `${url}/${item.href}` : url - - return ( - - {item.name} - - ) -} - -export function Filters() { - const [createFilterIsOpen, toggleCreateFilter] = useToggle(false) - - const {isLoading, error, data} = useQuery('filter', APIClient.filters.getAll, - { - refetchOnWindowFocus: false - } - ); - - if (isLoading) { - return null - } - - if (error) return (

'An error has occurred: '

) - - return ( -
- - -
-
-

Filters

- -
- -
-
-
- -
-
-
- {data && data.length > 0 ? : - } -
-
-
-
- ) -} - -interface FilterListProps { - filters: Filter[]; -} - -function FilterList({filters}: FilterListProps) { - return ( -
-
-
-
- - - - - - - - - - - {filters.map((filter: Filter, idx) => ( - - ))} - -
- Enabled - - Name - - Indexers - - Edit -
-
-
-
-
- ) -} - -interface FilterListItemProps { - filter: Filter; - idx: number; -} - -function FilterListItem({filter, idx}: FilterListItemProps) { - const [enabled, setEnabled] = useState(filter.enabled) - - const toggleActive = (status: boolean) => { - console.log(status) - setEnabled(status) - // call api - } - - return ( - - - - Use setting - - - {filter.name} - {filter.indexers && filter.indexers.map(t => - {t.name})} - - - Edit - - - - ) -} - -const FormButtonsGroup = ({deleteAction}: any) => { - const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false) - - const cancelButtonRef = useRef(null) - - return ( -
- - - -
- - - - - {/* This element is to trick the browser into centering the modal contents. */} - - -
-
-
-
-
-
- - Remove filter - -
-

- Are you sure you want to remove this filter? - This action cannot be undone. -

-
-
-
-
-
- - -
-
-
-
-
-
- -
- - -
- - -
-
-
- ) -} - -export function FilterDetails() { - let {url} = useRouteMatch(); - let history = useHistory(); - let {filterId}: any = useParams(); - - const {isLoading, data} = useQuery(['filter', parseInt(filterId)], () => APIClient.filters.getByID(parseInt(filterId)), - { - retry: false, - refetchOnWindowFocus: false, - onError: err => { - history.push("./") - } - }, - ) - - if (isLoading) { - return null - } - - if (!data) { - return null - } - - return ( -
-
-
-

- - Filters - -

-
-
-
-
-
-
-
- {/* Tabs */} -
- - -
-
-
- -
-
- - - - - - - - {/**/} - - - - {/**/} - {/*

movies

*/} - {/*
*/} - - {/**/} - {/*

p2p

*/} - {/*
*/} - - - - - - - - - -
-
-
-
-
-
-
- ) -} - -interface FilterTabGeneralProps { - filter: Filter; -} - -function FilterTabGeneral({filter}: FilterTabGeneralProps) { - const history = useHistory(); - - const { data } = useQuery('indexerList', APIClient.indexers.getOptions, - { - refetchOnWindowFocus: false - } - ) - - const updateMutation = useMutation((filter: Filter) => APIClient.filters.update(filter), { - onSuccess: () => { - // queryClient.setQueryData(['filter', filter.id], data) - toast.custom((t) => ) - - queryClient.invalidateQueries(["filter",filter.id]); - } - }) - - const deleteMutation = useMutation((id: number) => APIClient.filters.delete(id), { - onSuccess: () => { - // invalidate filters - queryClient.invalidateQueries("filter"); - toast.custom((t) => ) - - // redirect - history.push("/filters") - } - }) - - const submitOther = (data: Filter) => { - updateMutation.mutate(data) - } - - const deleteAction = () => { - deleteMutation.mutate(filter.id) - } - - return ( -
-
- {({handleSubmit, submitting, values, valid}) => { - return ( - -
-
- -
- - -
- - val && val.map((item: any) => ({ id: item.value.id, name: item.value.name, enabled: item.value.enabled, identifier: item.value.identifier}))} - format={values => values.map((val: any) => ({ label: val.name, value: val}))} - render={({input, meta}) => ( - + {tabs.map((tab) => ( + + ))} + +
+
+
+ +
+
+ + + {({ isSubmitting, values, dirty, resetForm }) => ( + + + + + + + + + + + + + + + + + + + + + + + + )} + + +
+
+
+
+
+ + ) +} + +interface GeneralProps { + indexers: Indexer[]; +} + +function General({ indexers }: GeneralProps) { + + let opts = indexers ? indexers.map(v => ({ + label: v.name, + value: v + })) : []; + + return ( +
+
+ +
+ + +
+ + + + {({ + field, + form: { setFieldValue }, + }: any) => { + return ( + ({ + label: v.name, + value: v + }))} + onChange={(values: any) => { + let am = values && values.map((i: any) => i.value) + + setFieldValue(field.name, am) + }} + isClearable={true} + isMulti={true} + placeholder="Choose indexers" + className="mt-2 block w-full focus:outline-none focus:ring-light-blue-500 focus:border-light-blue-500 sm:text-sm" + options={opts} + /> + ) + }} + +
+
+
+ +
+ + +
+ + + +
+
+ +
+ +
+ +
+ ); +} + +// interface FilterTabGeneralProps { +// filter: Filter; +// } + +function MoviesTv() { + + return ( +
+
+ + +
+ +
+ + +
+ + +
+
+ +
+ + +
+ + +
+ +
+ + +
+
+
+ ) +} + +function Advanced() { + const [releasesIsOpen, toggleReleases] = useToggle(false) + const [groupsIsOpen, toggleGroups] = useToggle(false) + const [categoriesIsOpen, toggleCategories] = useToggle(false) + const [uploadersIsOpen, toggleUploaders] = useToggle(false) + const [freeleechIsOpen, toggleFreeleech] = useToggle(false) + + return ( +
+
+
+
+

Releases

+

Match or ignore

+
+
+ +
+
+ {releasesIsOpen && ( +
+ + +
+ )} +
+ +
+
+
+

Groups

+

Match or ignore

+
+
+ +
+
+ {groupsIsOpen && ( +
+ + +
+ )} +
+ +
+
+
+

Categories and tags

+

Match or ignore categories or tags

+
+
+ +
+
+ {categoriesIsOpen && ( +
+ + + + + +
+ )} +
+ +
+
+
+

Uploaders

+

Match or ignore uploaders

+
+
+ +
+
+ {uploadersIsOpen && ( +
+ + +
+ )} +
+ +
+
+
+

Freeleech

+

Match only freeleech and freeleech percent

+
+
+ +
+
+ {freeleechIsOpen && ( +
+
+ +
+ + +
+ )} +
+
+ ) +} + +interface FilterActionsProps { + filter: Filter; + values: any; +} + +function FilterActions({ filter, values }: FilterActionsProps) { + // const [addActionIsOpen, toggleAddAction] = useToggle(false) + + const { data } = useQuery('downloadClients', APIClient.download_clients.getAll, + { + refetchOnWindowFocus: false + } + ) + + let newAction = { + name: "new action", + enabled: true, + 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: 0, + } + + return ( +
+ {/* {addActionIsOpen && + + } */} + + {({ remove, push }) => ( + +
+
+

Actions

+

+ Add to download clients or run custom commands. +

+
+
+ +
+
+
+ {values.actions.length > 0 ? + values.actions.map((action: any, index: any) => ( +
    + +
+ )) : + } +
+
+ )} +
+
+ ) +} + +interface FilterActionsItemProps { + action: Action; + clients: DownloadClient[]; + idx: number; + remove: any; +} + +function FilterActionsItem({ action, clients, idx, remove }: FilterActionsItemProps) { + const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false); + const [edit, toggleEdit] = useToggle(false); + + // const enabledMutation = useMutation( + // (actionID: number) => APIClient.actions.toggleEnable(actionID), + // { + // onSuccess: () => { + // // queryClient.invalidateQueries(["filter", filterID]); + // }, + // } + // ); + + // const toggleActive = () => { + // console.log("action: ", action); + + // enabledMutation.mutate(action.id); + // }; + + const cancelButtonRef = useRef(null); + + const TypeForm = (actionType: ActionType) => { + switch (actionType) { + case "TEST": + return ( + + ); + case "EXEC": + return ( +
+
+ + +
+
+ ); + case "WATCH_FOLDER": + return ( +
+ +
+ ); + case "QBITTORRENT": + return ( +
+
+ + +
+ +
+
+ +
+ + +
+ +
+ + +
+ +
+
+ +
+
+
+ ); + case "DELUGE_V1": + case "DELUGE_V2": + return ( +
+
+ + +
+ +
+
+ +
+ +
+ +
+ + +
+ +
+
+ +
+
+
+ ); + case "RADARR": + case "SONARR": + case "LIDARR": + return ( +
+ +
+ ); + + default: + return null; + } + }; + + return ( +
  • +
    + + {({ + field, + form: { setFieldValue }, + }: any) => ( + { + setFieldValue(field?.name ?? '', value) + }} + className={classNames( + field.value ? 'bg-teal-500' : 'bg-gray-200', + 'relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-light-blue-500' + )} + > + toggle enabled + + )} + + + + +
    + {edit && ( +
    + + + remove(idx)} + title="Remove filter action" + text="Are you sure you want to remove this action? This action cannot be undone." + /> + + + +
    + +
    + + {meta.touched && meta.error && ( +
    {meta.error}
    + )} +
    + + )} + +
    +); + +export default NumberField; diff --git a/web/src/screens/filters/inputs/Select.tsx b/web/src/screens/filters/inputs/Select.tsx new file mode 100644 index 0000000..cd4973a --- /dev/null +++ b/web/src/screens/filters/inputs/Select.tsx @@ -0,0 +1,116 @@ +import { Fragment } from "react"; +import { Field } from "formik"; +import { Listbox, Transition } from "@headlessui/react"; +import { CheckIcon, SelectorIcon } from "@heroicons/react/solid"; +import { classNames } from "../../../styles/utils"; + +interface Option { + label: string; + value: string; +} + +interface props { + name: string; + label: string; + optionDefaultText: string; + options: Option[]; +} + +function Select({ name, label, optionDefaultText, options }: props) { + return ( +
    + + {({ + field, + form: { setFieldValue }, + }: any) => ( + setFieldValue(field?.name, value)} + > + {({ open }) => ( + <> + + {label} + +
    + + + {field.value + ? options.find((c) => c.value === field.value)!.label + : optionDefaultText + } + + + + + + + + {options.map((opt) => ( + + classNames( + active + ? "text-white bg-indigo-600" + : "text-gray-900", + "cursor-default select-none relative py-2 pl-3 pr-9" + ) + } + value={opt.value} + > + {({ selected, active }) => ( + <> + + {opt.label} + + + {selected ? ( + + + ) : null} + + )} + + ))} + + +
    + + )} +
    + )} +
    +
    + ); +} + +export default Select; diff --git a/web/src/screens/filters/inputs/Switch.tsx b/web/src/screens/filters/inputs/Switch.tsx new file mode 100644 index 0000000..269d2e9 --- /dev/null +++ b/web/src/screens/filters/inputs/Switch.tsx @@ -0,0 +1,60 @@ +import { Switch as HeadlessSwitch } from '@headlessui/react' +import { FieldInputProps, FieldMetaProps, FieldProps, FormikProps, FormikValues } from 'formik' +import React, { InputHTMLAttributes } from 'react' +import { classNames } from "../../../styles/utils"; + +type SwitchProps = { + label: string + checked: boolean + disabled?: boolean + onChange: (value: boolean) => void + field?: FieldInputProps + form?: FormikProps + meta?: FieldMetaProps +} + +export const Switch: React.FC = ({ + label, + checked: $checked, + disabled = false, + onChange: $onChange, + field, + form, +}) => { + const checked = field?.checked ?? $checked + + return ( + + {label} + { + form?.setFieldValue(field?.name ?? '', value) + $onChange && $onChange(value) + }} + + className={classNames( + checked ? 'bg-teal-500' : 'bg-gray-200', + 'ml-4 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-light-blue-500' + )} + > + {({ checked }) => ( + + + ) +} + +export type SwitchFormikProps = SwitchProps & FieldProps & InputHTMLAttributes + +export const SwitchFormik: React.FC = args => \ No newline at end of file diff --git a/web/src/screens/filters/inputs/SwitchGroup.tsx b/web/src/screens/filters/inputs/SwitchGroup.tsx new file mode 100644 index 0000000..f1c4da2 --- /dev/null +++ b/web/src/screens/filters/inputs/SwitchGroup.tsx @@ -0,0 +1,89 @@ +import React from "react"; +import { Switch } from "@headlessui/react"; +import { Field } from "formik"; +import { classNames } from "../../../styles/utils"; + +interface Props { + name: string; + label?: string; + description?: string; + defaultValue?: boolean; + className?: string; +} + +const SwitchGroup: React.FC = ({ name, label, description, defaultValue }) => ( +
      + + {label &&
      + + {label} + + {description && ( + + {description} + + )} +
      + } + + + {({ + field, + form: { setFieldValue }, + }: any) => ( + { + setFieldValue(field?.name ?? '', value) + }} + className={classNames( + field.value ? 'bg-teal-500' : 'bg-gray-200', + 'ml-4 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-light-blue-500' + )} + > + {/* {label} */} + + + )} + + + {/* ( + + Use setting + + )} + /> */} +
      +
    +) + +export default SwitchGroup; \ No newline at end of file diff --git a/web/src/screens/filters/inputs/TextField.tsx b/web/src/screens/filters/inputs/TextField.tsx new file mode 100644 index 0000000..a150707 --- /dev/null +++ b/web/src/screens/filters/inputs/TextField.tsx @@ -0,0 +1,51 @@ +import { Field } from "formik"; +import React from "react"; +import { classNames } from "../../../styles/utils"; + +type COL_WIDTHS = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; + +interface Props { + name: string; + label?: string; + placeholder?: string; + columns?: COL_WIDTHS; + className?: string; + autoComplete?: string; +} + +const TextField: React.FC = ({ name, label, placeholder, columns, className, autoComplete }) => ( +
    + {label && ( + + )} + + {({ + field, + meta, + }: any) => ( +
    + + + {meta.touched && meta.error && ( +
    {meta.error}
    + )} +
    + )} +
    +
    +) + +export default TextField; diff --git a/web/src/screens/filters/inputs/index.ts b/web/src/screens/filters/inputs/index.ts new file mode 100644 index 0000000..2029573 --- /dev/null +++ b/web/src/screens/filters/inputs/index.ts @@ -0,0 +1,7 @@ +export { default as DownloadClientSelect } from "./DownloadClientSelect"; +export { default as TextField } from "./TextField"; +export { default as Select } from "./Select"; +export { default as SwitchGroup } from "./SwitchGroup"; +export { default as MultiSelect } from "./MultiSelect"; +export { default as NumberField } from "./NumberField"; +export { Switch } from "./Switch"; diff --git a/web/src/screens/filters/list.tsx b/web/src/screens/filters/list.tsx new file mode 100644 index 0000000..bd8ad7e --- /dev/null +++ b/web/src/screens/filters/list.tsx @@ -0,0 +1,157 @@ +import React, { useState } from "react"; +import { Switch } from "@headlessui/react"; +import { EmptyListState } from "../../components/EmptyListState"; + +import { + Link, +} from "react-router-dom"; +import { Filter } from "../../domain/interfaces"; +import { useToggle } from "../../hooks/hooks"; +import { useQuery } from "react-query"; +import { classNames } from "../../styles/utils"; +import { FilterAddForm } from "../../forms"; +import APIClient from "../../api/APIClient"; + +export default function Filters() { + const [createFilterIsOpen, toggleCreateFilter] = useToggle(false) + + const { isLoading, error, data } = useQuery('filter', APIClient.filters.getAll, + { + refetchOnWindowFocus: false + } + ); + + if (isLoading) { + return null + } + + if (error) return (

    'An error has occurred: '

    ) + + return ( +
    + + +
    +
    +

    Filters

    + +
    + +
    +
    +
    + +
    +
    +
    + {data && data.length > 0 ? : + } +
    +
    +
    +
    + ) +} + +interface FilterListProps { + filters: Filter[]; +} + +function FilterList({ filters }: FilterListProps) { + return ( +
    +
    +
    +
    + + + + + + + + + + + {filters.map((filter: Filter, idx) => ( + + ))} + +
    + Enabled + + Name + + Indexers + + Edit +
    +
    +
    +
    +
    + ) +} + +interface FilterListItemProps { + filter: Filter; + idx: number; +} + +function FilterListItem({ filter, idx }: FilterListItemProps) { + const [enabled, setEnabled] = useState(filter.enabled) + + const toggleActive = (status: boolean) => { + console.log(status) + setEnabled(status) + // call api + } + + return ( + + + + Use setting + + + {filter.name} + {filter.indexers && filter.indexers.map(t => + {t.name})} + + + Edit + + + + ) +} \ No newline at end of file diff --git a/web/tailwind.config.js b/web/tailwind.config.js index ff08dbb..b344a17 100644 --- a/web/tailwind.config.js +++ b/web/tailwind.config.js @@ -1,7 +1,7 @@ const colors = require('tailwindcss/colors') module.exports = { - mode: 'jit', + // mode: 'jit', purge: { content: [ './src/**/*.{tsx,ts,html,css}', diff --git a/web/yarn.lock b/web/yarn.lock index fa7038b..4d810f4 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -4203,6 +4203,11 @@ deep-is@^0.1.3, deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= +deepmerge@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170" + integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA== + deepmerge@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" @@ -5347,6 +5352,19 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +formik@^2.2.9: + version "2.2.9" + resolved "https://registry.yarnpkg.com/formik/-/formik-2.2.9.tgz#8594ba9c5e2e5cf1f42c5704128e119fc46232d0" + integrity sha512-LQLcISMmf1r5at4/gyJigGn0gOwFbeEAlji+N9InZF6LIMXnFNkO42sCI8Jt84YZggpD4cPWObAZaxpEFtSzNA== + dependencies: + deepmerge "^2.1.1" + hoist-non-react-statics "^3.3.0" + lodash "^4.17.21" + lodash-es "^4.17.21" + react-fast-compare "^2.0.1" + tiny-warning "^1.0.2" + tslib "^1.10.0" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -7252,6 +7270,11 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +lodash-es@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" + integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== + lodash._reinterpolate@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" @@ -9497,6 +9520,11 @@ react-error-overlay@^6.0.9: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a" integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew== +react-fast-compare@^2.0.1: + version "2.0.4" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" + integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== + react-final-form-arrays@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/react-final-form-arrays/-/react-final-form-arrays-3.1.3.tgz#d3594c500495a4cf5e437070ada989da9624bba2" @@ -11087,7 +11115,7 @@ tiny-invariant@^1.0.2: resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw== -tiny-warning@^1.0.0, tiny-warning@^1.0.3: +tiny-warning@^1.0.0, tiny-warning@^1.0.2, tiny-warning@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== @@ -11186,7 +11214,7 @@ tsconfig-paths@^3.9.0: minimist "^1.2.0" strip-bom "^3.0.0" -tslib@^1.8.1: +tslib@^1.10.0, tslib@^1.8.1: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==