-
+
+
-
-
-
+
+
+
-
- {({ handleSubmit, values }) => (
-
- )}
-
+
+ {({ handleSubmit, values }) => (
+
-
-
- )
+ {!!values && children !== undefined ? (
+ children(values)
+ ) : null}
+
+
+
+
+ {type === "UPDATE" && (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+ );
}
export { SlideOver };
\ No newline at end of file
diff --git a/web/src/domain/constants.ts b/web/src/domain/constants.ts
index 9c84eef..4821120 100644
--- a/web/src/domain/constants.ts
+++ b/web/src/domain/constants.ts
@@ -1,156 +1,158 @@
+import { MultiSelectOption } from "../components/inputs/select";
+
export const resolutions = [
- "2160p",
- "1080p",
- "1080i",
- "810p",
- "720p",
- "576p",
- "480p",
- "480i"
+ "2160p",
+ "1080p",
+ "1080i",
+ "810p",
+ "720p",
+ "576p",
+ "480p",
+ "480i"
];
-export const RESOLUTION_OPTIONS = resolutions.map(r => ({ value: r, label: r, key: r}));
+export const RESOLUTION_OPTIONS: MultiSelectOption[] = resolutions.map(r => ({ value: r, label: r, key: r }));
export const codecs = [
- "HEVC",
- "H.264",
- "H.265",
- "x264",
- "x265",
- "AVC",
- "VC-1",
- "AV1",
- "XviD"
+ "HEVC",
+ "H.264",
+ "H.265",
+ "x264",
+ "x265",
+ "AVC",
+ "VC-1",
+ "AV1",
+ "XviD"
];
-export const CODECS_OPTIONS = codecs.map(v => ({ value: v, label: v, key: v}));
+export const CODECS_OPTIONS: MultiSelectOption[] = codecs.map(v => ({ value: v, label: v, key: v }));
export const sources = [
- "BluRay",
- "UHD.BluRay",
- "WEB-DL",
- "WEB",
- "WEBRip",
- "BD5",
- "BD9",
- "BDr",
- "BDRip",
- "BRRip",
- "CAM",
- "DVDR",
- "DVDRip",
- "DVDScr",
- "HDCAM",
- "HDDVD",
- "HDDVDRip",
- "HDTS",
- "HDTV",
- "Mixed",
- "SiteRip",
+ "BluRay",
+ "UHD.BluRay",
+ "WEB-DL",
+ "WEB",
+ "WEBRip",
+ "BD5",
+ "BD9",
+ "BDr",
+ "BDRip",
+ "BRRip",
+ "CAM",
+ "DVDR",
+ "DVDRip",
+ "DVDScr",
+ "HDCAM",
+ "HDDVD",
+ "HDDVDRip",
+ "HDTS",
+ "HDTV",
+ "Mixed",
+ "SiteRip"
];
-export const SOURCES_OPTIONS = sources.map(v => ({ value: v, label: v, key: v}));
+export const SOURCES_OPTIONS: MultiSelectOption[] = sources.map(v => ({ value: v, label: v, key: v }));
export const containers = [
- "avi",
- "mp4",
- "mkv",
+ "avi",
+ "mp4",
+ "mkv"
];
-export const CONTAINER_OPTIONS = containers.map(v => ({ value: v, label: v, key: v}));
+export const CONTAINER_OPTIONS: MultiSelectOption[] = containers.map(v => ({ value: v, label: v, key: v }));
export const hdr = [
- "HDR",
- "HDR10",
- "HDR10+",
- "HLG",
- "DV",
- "DV HDR",
- "DV HDR10",
- "DV HDR10+",
- "DoVi",
- "Dolby Vision",
+ "HDR",
+ "HDR10",
+ "HDR10+",
+ "HLG",
+ "DV",
+ "DV HDR",
+ "DV HDR10",
+ "DV HDR10+",
+ "DoVi",
+ "Dolby Vision"
];
-export const HDR_OPTIONS = hdr.map(v => ({ value: v, label: v, key: v}));
+export const HDR_OPTIONS: MultiSelectOption[] = hdr.map(v => ({ value: v, label: v, key: v }));
export const quality_other = [
- "REMUX",
- "HYBRID",
- "REPACK",
+ "REMUX",
+ "HYBRID",
+ "REPACK"
];
-export const OTHER_OPTIONS = quality_other.map(v => ({ value: v, label: v, key: v}));
+export const OTHER_OPTIONS = quality_other.map(v => ({ value: v, label: v, key: v }));
export const formatMusic = [
- "MP3",
- "FLAC",
- "Ogg Vorbis",
- "Ogg",
- "AAC",
- "AC3",
- "DTS",
+ "MP3",
+ "FLAC",
+ "Ogg Vorbis",
+ "Ogg",
+ "AAC",
+ "AC3",
+ "DTS"
];
-export const FORMATS_OPTIONS = formatMusic.map(r => ({ value: r, label: r, key: r}));
+export const FORMATS_OPTIONS: MultiSelectOption[] = formatMusic.map(r => ({ value: r, label: r, key: r }));
export const sourcesMusic = [
- "CD",
- "WEB",
- "DVD",
- "Vinyl",
- "Soundboard",
- "DAT",
- "Cassette",
- "Blu-Ray",
- "SACD",
+ "CD",
+ "WEB",
+ "DVD",
+ "Vinyl",
+ "Soundboard",
+ "DAT",
+ "Cassette",
+ "Blu-Ray",
+ "SACD"
];
-export const SOURCES_MUSIC_OPTIONS = sourcesMusic.map(v => ({ value: v, label: v, key: v}));
+export const SOURCES_MUSIC_OPTIONS: MultiSelectOption[] = sourcesMusic.map(v => ({ value: v, label: v, key: v }));
export const qualityMusic = [
- "192",
- "256",
- "320",
- "APS (VBR)",
- "APX (VBR)",
- "V2 (VBR)",
- "V1 (VBR)",
- "V0 (VBR)",
- "Lossless",
- "24bit Lossless",
+ "192",
+ "256",
+ "320",
+ "APS (VBR)",
+ "APX (VBR)",
+ "V2 (VBR)",
+ "V1 (VBR)",
+ "V0 (VBR)",
+ "Lossless",
+ "24bit Lossless"
];
-export const QUALITY_MUSIC_OPTIONS = qualityMusic.map(v => ({ value: v, label: v, key: v}));
+export const QUALITY_MUSIC_OPTIONS: MultiSelectOption[] = qualityMusic.map(v => ({ value: v, label: v, key: v }));
export const releaseTypeMusic = [
- "Album",
- "Single",
- "EP",
- "Soundtrack",
- "Anthology",
- "Compilation",
- "Live album",
- "Remix",
- "Bootleg",
- "Interview",
- "Mixtape",
- "Demo",
- "Concert Recording",
- "DJ Mix",
- "Unknown",
+ "Album",
+ "Single",
+ "EP",
+ "Soundtrack",
+ "Anthology",
+ "Compilation",
+ "Live album",
+ "Remix",
+ "Bootleg",
+ "Interview",
+ "Mixtape",
+ "Demo",
+ "Concert Recording",
+ "DJ Mix",
+ "Unknown"
];
-export const RELEASE_TYPE_MUSIC_OPTIONS = releaseTypeMusic.map(v => ({ value: v, label: v, key: v}));
+export const RELEASE_TYPE_MUSIC_OPTIONS: MultiSelectOption[] = releaseTypeMusic.map(v => ({ value: v, label: v, key: v }));
export const originOptions = [
- "P2P",
- "Internal",
- "SCENE",
- "O-SCENE",
+ "P2P",
+ "Internal",
+ "SCENE",
+ "O-SCENE"
];
-export const ORIGIN_OPTIONS = originOptions.map(v => ({ value: v, label: v, key: v}));
+export const ORIGIN_OPTIONS = originOptions.map(v => ({ value: v, label: v, key: v }));
export interface RadioFieldsetOption {
label: string;
@@ -159,123 +161,128 @@ export interface RadioFieldsetOption {
}
export const DownloadClientTypeOptions: RadioFieldsetOption[] = [
- {
- label: "qBittorrent",
- description: "Add torrents directly to qBittorrent",
- value: "QBITTORRENT"
- },
- {
- label: "Deluge",
- description: "Add torrents directly to Deluge",
- value: "DELUGE_V1"
- },
- {
- label: "Deluge 2",
- description: "Add torrents directly to Deluge 2",
- value: "DELUGE_V2"
- },
- {
- label: "Radarr",
- description: "Send to Radarr and let it decide",
- value: "RADARR"
- },
- {
- label: "Sonarr",
- description: "Send to Sonarr and let it decide",
- value: "SONARR"
- },
- {
- label: "Lidarr",
- description: "Send to Lidarr and let it decide",
- value: "LIDARR"
- },
- {
- label: "Whisparr",
- description: "Send to Whisparr and let it decide",
- value: "WHISPARR"
- },
+ {
+ label: "qBittorrent",
+ description: "Add torrents directly to qBittorrent",
+ value: "QBITTORRENT"
+ },
+ {
+ label: "Deluge",
+ description: "Add torrents directly to Deluge",
+ value: "DELUGE_V1"
+ },
+ {
+ label: "Deluge 2",
+ description: "Add torrents directly to Deluge 2",
+ value: "DELUGE_V2"
+ },
+ {
+ label: "Radarr",
+ description: "Send to Radarr and let it decide",
+ value: "RADARR"
+ },
+ {
+ label: "Sonarr",
+ description: "Send to Sonarr and let it decide",
+ value: "SONARR"
+ },
+ {
+ label: "Lidarr",
+ description: "Send to Lidarr and let it decide",
+ value: "LIDARR"
+ },
+ {
+ label: "Whisparr",
+ description: "Send to Whisparr and let it decide",
+ value: "WHISPARR"
+ }
];
export const DownloadClientTypeNameMap: Record
= {
- "DELUGE_V1": "Deluge v1",
- "DELUGE_V2": "Deluge v2",
- "QBITTORRENT": "qBittorrent",
- "RADARR": "Radarr",
- "SONARR": "Sonarr",
- "LIDARR": "Lidarr",
- "WHISPARR": "Whisparr",
+ "DELUGE_V1": "Deluge v1",
+ "DELUGE_V2": "Deluge v2",
+ "QBITTORRENT": "qBittorrent",
+ "RADARR": "Radarr",
+ "SONARR": "Sonarr",
+ "LIDARR": "Lidarr",
+ "WHISPARR": "Whisparr"
};
export const ActionTypeOptions: RadioFieldsetOption[] = [
- {label: "Test", description: "A simple action to test a filter.", value: "TEST"},
- {label: "Watch dir", description: "Add filtered torrents to a watch directory", value: "WATCH_FOLDER"},
- {label: "Webhook", description: "Run webhook", value: "WEBHOOK"},
- {label: "Exec", description: "Run a custom command after a filter match", value: "EXEC"},
- {label: "qBittorrent", description: "Add torrents directly to qBittorrent", value: "QBITTORRENT"},
- {label: "Deluge", description: "Add torrents directly to Deluge", value: "DELUGE_V1"},
- {label: "Deluge v2", description: "Add torrents directly to Deluge 2", value: "DELUGE_V2"},
- {label: "Radarr", description: "Send to Radarr and let it decide", value: "RADARR"},
- {label: "Sonarr", description: "Send to Sonarr and let it decide", value: "SONARR"},
- {label: "Lidarr", description: "Send to Lidarr and let it decide", value: "LIDARR"},
- {label: "Whisparr", description: "Send to Whisparr and let it decide", value: "WHISPARR"},
+ { label: "Test", description: "A simple action to test a filter.", value: "TEST" },
+ { label: "Watch dir", description: "Add filtered torrents to a watch directory", value: "WATCH_FOLDER" },
+ { label: "Webhook", description: "Run webhook", value: "WEBHOOK" },
+ { label: "Exec", description: "Run a custom command after a filter match", value: "EXEC" },
+ { label: "qBittorrent", description: "Add torrents directly to qBittorrent", value: "QBITTORRENT" },
+ { label: "Deluge", description: "Add torrents directly to Deluge", value: "DELUGE_V1" },
+ { label: "Deluge v2", description: "Add torrents directly to Deluge 2", value: "DELUGE_V2" },
+ { label: "Radarr", description: "Send to Radarr and let it decide", value: "RADARR" },
+ { label: "Sonarr", description: "Send to Sonarr and let it decide", value: "SONARR" },
+ { label: "Lidarr", description: "Send to Lidarr and let it decide", value: "LIDARR" },
+ { label: "Whisparr", description: "Send to Whisparr and let it decide", value: "WHISPARR" }
];
export const ActionTypeNameMap = {
- "TEST": "Test",
- "WATCH_FOLDER": "Watch folder",
- "WEBHOOK": "Webhook",
- "EXEC": "Exec",
- "DELUGE_V1": "Deluge v1",
- "DELUGE_V2": "Deluge v2",
- "QBITTORRENT": "qBittorrent",
- "RADARR": "Radarr",
- "SONARR": "Sonarr",
- "LIDARR": "Lidarr",
- "WHISPARR": "Whisparr",
+ "TEST": "Test",
+ "WATCH_FOLDER": "Watch folder",
+ "WEBHOOK": "Webhook",
+ "EXEC": "Exec",
+ "DELUGE_V1": "Deluge v1",
+ "DELUGE_V2": "Deluge v2",
+ "QBITTORRENT": "qBittorrent",
+ "RADARR": "Radarr",
+ "SONARR": "Sonarr",
+ "LIDARR": "Lidarr",
+ "WHISPARR": "Whisparr"
};
-export const PushStatusOptions: any[] = [
- {
- label: "Rejected",
- value: "PUSH_REJECTED",
- },
- {
- label: "Approved",
- value: "PUSH_APPROVED"
- },
- {
- label: "Error",
- value: "PUSH_ERROR"
- },
+export interface OptionBasic {
+ label: string;
+ value: string;
+}
+
+export const PushStatusOptions: OptionBasic[] = [
+ {
+ label: "Rejected",
+ value: "PUSH_REJECTED"
+ },
+ {
+ label: "Approved",
+ value: "PUSH_APPROVED"
+ },
+ {
+ label: "Error",
+ value: "PUSH_ERROR"
+ }
];
-export const NotificationTypeOptions: any[] = [
- {
- label: "Discord",
- value: "DISCORD",
- },
+export const NotificationTypeOptions: OptionBasic[] = [
+ {
+ label: "Discord",
+ value: "DISCORD"
+ }
];
export interface SelectOption {
label: string;
description: string;
- value: any;
+ value: string;
}
export const EventOptions: SelectOption[] = [
- {
- label: "Push Rejected",
- value: "PUSH_REJECTED",
- description: "On push rejected for the arrs or download client",
- },
- {
- label: "Push Approved",
- value: "PUSH_APPROVED",
- description: "On push approved for the arrs or download client",
- },
- {
- label: "Push Error",
- value: "PUSH_ERROR",
- description: "On push error for the arrs or download client",
- },
+ {
+ label: "Push Rejected",
+ value: "PUSH_REJECTED",
+ description: "On push rejected for the arrs or download client"
+ },
+ {
+ label: "Push Approved",
+ value: "PUSH_APPROVED",
+ description: "On push approved for the arrs or download client"
+ },
+ {
+ label: "Push Error",
+ value: "PUSH_ERROR",
+ description: "On push error for the arrs or download client"
+ }
];
diff --git a/web/src/domain/react-table-config.d.ts b/web/src/domain/react-table-config.d.ts
index ddd8100..033971a 100644
--- a/web/src/domain/react-table-config.d.ts
+++ b/web/src/domain/react-table-config.d.ts
@@ -46,9 +46,9 @@ import {
UseSortByInstanceProps,
UseSortByOptions,
UseSortByState
-} from 'react-table'
+} from "react-table";
-declare module 'react-table' {
+declare module "react-table" {
// take this file as-is, or comment out the sections that don't apply to your plugin configuration
export interface TableOptions>
diff --git a/web/src/forms/filters/FilterAddForm.tsx b/web/src/forms/filters/FilterAddForm.tsx
index b0c4da8..65b27b6 100644
--- a/web/src/forms/filters/FilterAddForm.tsx
+++ b/web/src/forms/filters/FilterAddForm.tsx
@@ -3,149 +3,160 @@ import { useMutation } from "react-query";
import { toast } from "react-hot-toast";
import { XIcon } from "@heroicons/react/solid";
import { Dialog, Transition } from "@headlessui/react";
-import { Field, Form, Formik } from "formik";
+import { Field, Form, Formik, FormikErrors, FormikValues } from "formik";
import type { FieldProps } from "formik";
import { queryClient } from "../../App";
import { APIClient } from "../../api/APIClient";
import DEBUG from "../../components/debug";
-import Toast from '../../components/notifications/Toast';
+import Toast from "../../components/notifications/Toast";
-function FilterAddForm({ isOpen, toggle }: any) {
- const mutation = useMutation(
- (filter: Filter) => APIClient.filters.create(filter),
- {
- onSuccess: (_, filter) => {
- queryClient.invalidateQueries("filters");
- toast.custom((t) => );
+interface filterAddFormProps {
+ isOpen: boolean;
+ toggle: () => void;
+}
- toggle();
- }
- }
- )
+function FilterAddForm({ isOpen, toggle }: filterAddFormProps) {
+ const mutation = useMutation(
+ (filter: Filter) => APIClient.filters.create(filter),
+ {
+ onSuccess: (_, filter) => {
+ queryClient.invalidateQueries("filters");
+ toast.custom((t) => );
- const handleSubmit = (data: any) => mutation.mutate(data);
- const validate = (values: any) => values.name ? {} : { name: "Required" };
+ toggle();
+ }
+ }
+ );
- return (
-
-
+
+ );
}
export default FilterAddForm;
\ No newline at end of file
diff --git a/web/src/forms/settings/DownloadClientForms.tsx b/web/src/forms/settings/DownloadClientForms.tsx
index 4d46733..265ab0f 100644
--- a/web/src/forms/settings/DownloadClientForms.tsx
+++ b/web/src/forms/settings/DownloadClientForms.tsx
@@ -1,646 +1,673 @@
-import { Fragment, useRef, useState } from "react";
+import React, { Fragment, useRef, useState } from "react";
import { useMutation } from "react-query";
import { Dialog, Transition } from "@headlessui/react";
import { XIcon } from "@heroicons/react/solid";
-import { sleep, classNames } from "../../utils";
+import { classNames, sleep } from "../../utils";
import { Form, Formik, useFormikContext } from "formik";
import DEBUG from "../../components/debug";
import { queryClient } from "../../App";
import { APIClient } from "../../api/APIClient";
import { DownloadClientTypeOptions } from "../../domain/constants";
-import { toast } from 'react-hot-toast'
-import Toast from '../../components/notifications/Toast';
+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";
+import DownloadClient from "../../screens/settings/DownloadClient";
interface InitialValuesSettings {
- basic?: {
- auth: boolean;
- username: string;
- password: string;
- };
- rules?: {
- enabled?: boolean;
- ignore_slow_torrents?: boolean;
- download_speed_threshold?: number;
- max_active_downloads?: number;
- };
+ 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: DownloadClientType;
- enabled: boolean;
- host: string;
- port: number;
- tls: boolean;
- tls_skip_verify: boolean;
- username: string;
- password: string;
- settings: InitialValuesSettings;
+ name: string;
+ type: DownloadClientType;
+ enabled: boolean;
+ host: string;
+ port: number;
+ tls: boolean;
+ tls_skip_verify: boolean;
+ username: string;
+ password: string;
+ settings: InitialValuesSettings;
}
function FormFieldsDefault() {
- const {
- values: { tls },
- } = useFormikContext();
+ const {
+ values: { tls }
+ } = useFormikContext();
- return (
-
-
+ return (
+
+
-
+
-
-
+
+
- {tls && (
-
-
-
- )}
-
+ {tls && (
+
+
+
+ )}
+
-
-
-
- );
+
+
+
+ );
}
function FormFieldsArr() {
- const {
- values: { settings },
- } = useFormikContext();
+ const {
+ values: { settings }
+ } = useFormikContext();
- return (
+ return (
+
+
+
+
+
+
+
+
+
+ {settings.basic?.auth === true && (
-
-
-
-
-
-
-
-
- {settings.basic?.auth === true && (
-
-
-
-
- )}
+
+
- );
+ )}
+
+ );
}
-export const componentMap: any = {
- DELUGE_V1: ,
- DELUGE_V2: ,
- QBITTORRENT: ,
- RADARR: ,
- SONARR: ,
- LIDARR: ,
- WHISPARR: ,
+export interface componentMapType {
+ [key: string]: React.ReactElement;
+}
+
+export const componentMap: componentMapType = {
+ DELUGE_V1: ,
+ DELUGE_V2: ,
+ QBITTORRENT: ,
+ RADARR: ,
+ SONARR: ,
+ LIDARR: ,
+ WHISPARR:
};
-
function FormFieldsRulesBasic() {
- const {
- values: { settings },
- } = useFormikContext();
+ const {
+ values: { settings }
+ } = useFormikContext();
- return (
-
+ return (
+
-
-
Rules
-
- Manage max downloads.
-
-
+
+
Rules
+
+ Manage max downloads.
+
+
-
-
-
+
+
+
- {settings && settings.rules?.enabled === true && (
-
-
-
- )}
-
- );
+ {settings && settings.rules?.enabled === true && (
+
+
+
+ )}
+
+ );
}
function FormFieldsRules() {
- const {
- values: { settings },
- } = useFormikContext();
+ const {
+ values: { settings }
+ } = useFormikContext();
- return (
-
+ return (
+
-
-
Rules
-
- Manage max downloads etc.
-
-
+
+
Rules
+
+ Manage max downloads etc.
+
+
-
-
-
+
+
+
- {settings.rules?.enabled === true && (
-
-
-
-
-
+ {settings.rules?.enabled === true && (
+
+
+
+
+
- {settings.rules?.ignore_slow_torrents === true && (
-
-
-
- )}
-
- )}
-
- );
+ {settings.rules?.ignore_slow_torrents === true && (
+
+
+
+ )}
+
+ )}
+
+ );
}
-export const rulesComponentMap: any = {
- DELUGE_V1: ,
- DELUGE_V2: ,
- QBITTORRENT: ,
+export const rulesComponentMap: componentMapType = {
+ DELUGE_V1: ,
+ DELUGE_V2: ,
+ QBITTORRENT:
};
interface formButtonsProps {
- isSuccessfulTest: boolean;
- isErrorTest: boolean;
- isTesting: boolean;
- cancelFn: any;
- testFn: any;
- values: any;
- type: "CREATE" | "UPDATE";
- toggleDeleteModal?: any;
+ isSuccessfulTest: boolean;
+ isErrorTest: boolean;
+ isTesting: boolean;
+ cancelFn: () => void;
+ testFn: (data: unknown) => void;
+ values: unknown;
+ type: "CREATE" | "UPDATE";
+ toggleDeleteModal?: () => void;
}
-function DownloadClientFormButtons({ type, isSuccessfulTest, isErrorTest, isTesting, cancelFn, testFn, values, toggleDeleteModal }: formButtonsProps) {
+function DownloadClientFormButtons({
+ type,
+ isSuccessfulTest,
+ isErrorTest,
+ isTesting,
+ cancelFn,
+ testFn,
+ values,
+ toggleDeleteModal
+}: formButtonsProps) {
- const test = () => {
- testFn(values)
- }
+ const test = () => {
+ testFn(values);
+ };
- return (
-
-
- {type === "UPDATE" && (
-
- )}
-
-
+ return (
+
+
+ {type === "UPDATE" && (
+
+ )}
+
+
-
-
-
-
+
+
- )
+
+
+ );
}
-export function DownloadClientAddForm({ isOpen, toggle }: any) {
- const [isTesting, setIsTesting] = useState(false);
- const [isSuccessfulTest, setIsSuccessfulTest] = useState(false);
- const [isErrorTest, setIsErrorTest] = useState(false);
+interface formProps {
+ isOpen: boolean;
+ toggle: () => void;
+}
- const mutation = useMutation(
- (client: DownloadClient) => APIClient.download_clients.create(client),
- {
- onSuccess: () => {
- queryClient.invalidateQueries(["downloadClients"]);
- toast.custom((t) =>
)
+export function DownloadClientAddForm({ isOpen, toggle }: formProps) {
+ const [isTesting, setIsTesting] = useState(false);
+ const [isSuccessfulTest, setIsSuccessfulTest] = useState(false);
+ const [isErrorTest, setIsErrorTest] = useState(false);
- toggle();
- },
- onError: () => {
- toast.custom((t) =>
)
- }
- }
- );
+ const mutation = useMutation(
+ (client: DownloadClient) => APIClient.download_clients.create(client),
+ {
+ onSuccess: () => {
+ queryClient.invalidateQueries(["downloadClients"]);
+ toast.custom((t) =>
);
- const testClientMutation = useMutation(
- (client: DownloadClient) => APIClient.download_clients.test(client),
- {
- onMutate: () => {
- setIsTesting(true);
- setIsErrorTest(false);
- setIsSuccessfulTest(false);
- },
- onSuccess: () => {
- sleep(1000)
- .then(() => {
- setIsTesting(false);
- setIsSuccessfulTest(true);
- })
- .then(() => {
- sleep(2500).then(() => {
- setIsSuccessfulTest(false);
- });
- });
- },
- onError: () => {
- console.log('not added')
- setIsTesting(false);
- setIsErrorTest(true);
- sleep(2500).then(() => {
- setIsErrorTest(false);
- });
- },
- }
- );
-
- const onSubmit = (data: any) => {
- mutation.mutate(data);
- };
-
- const testClient = (data: any) => {
- testClientMutation.mutate(data);
- };
-
- const initialValues: InitialValues = {
- name: "",
- type: "QBITTORRENT",
- enabled: true,
- host: "",
- port: 10000,
- tls: false,
- tls_skip_verify: false,
- username: "",
- password: "",
- settings: {}
+ toggle();
+ },
+ onError: () => {
+ toast.custom((t) => );
+ }
}
+ );
- return (
-
-
-
-
-
-
-
-
-
- {({ handleSubmit, values }) => (
-
- )}
-
-
-
-
-
-
-
- );
-}
-
-export function DownloadClientUpdateForm({ client, isOpen, toggle }: any) {
- const [isTesting, setIsTesting] = useState(false);
- const [isSuccessfulTest, setIsSuccessfulTest] = useState(false);
- const [isErrorTest, setIsErrorTest] = useState(false);
- const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false);
-
- const mutation = useMutation(
- (client: DownloadClient) => APIClient.download_clients.update(client),
- {
- onSuccess: () => {
- queryClient.invalidateQueries(["downloadClients"]);
- toast.custom((t) => )
- toggle();
- },
- }
- );
-
- const deleteMutation = useMutation(
- (clientID: number) => APIClient.download_clients.delete(clientID),
- {
- onSuccess: () => {
- queryClient.invalidateQueries();
- toast.custom((t) => )
- toggleDeleteModal();
- },
- }
- );
-
- const testClientMutation = useMutation(
- (client: DownloadClient) => APIClient.download_clients.test(client),
- {
- onMutate: () => {
- setIsTesting(true);
- setIsErrorTest(false);
- setIsSuccessfulTest(false);
- },
- onSuccess: () => {
- sleep(1000)
- .then(() => {
- setIsTesting(false);
- setIsSuccessfulTest(true);
- })
- .then(() => {
- sleep(2500).then(() => {
- setIsSuccessfulTest(false);
- });
- });
- },
- onError: () => {
- setIsTesting(false);
- setIsErrorTest(true);
- sleep(2500).then(() => {
- setIsErrorTest(false);
- });
- },
- }
- );
-
- const onSubmit = (data: any) => {
- mutation.mutate(data);
- };
-
- const cancelButtonRef = useRef(null);
- const cancelModalButtonRef = useRef(null);
-
- const deleteAction = () => {
- deleteMutation.mutate(client.id);
- };
-
- const testClient = (data: any) => {
- testClientMutation.mutate(data);
- };
-
- const initialValues = {
- id: client.id,
- name: client.name,
- type: client.type,
- enabled: client.enabled,
- host: client.host,
- port: client.port,
- tls: client.tls,
- tls_skip_verify: client.tls_skip_verify,
- username: client.username,
- password: client.password,
- settings: client.settings,
+ const testClientMutation = useMutation(
+ (client: DownloadClient) => APIClient.download_clients.test(client),
+ {
+ onMutate: () => {
+ setIsTesting(true);
+ setIsErrorTest(false);
+ setIsSuccessfulTest(false);
+ },
+ onSuccess: () => {
+ sleep(1000)
+ .then(() => {
+ setIsTesting(false);
+ setIsSuccessfulTest(true);
+ })
+ .then(() => {
+ sleep(2500).then(() => {
+ setIsSuccessfulTest(false);
+ });
+ });
+ },
+ onError: () => {
+ console.log("not added");
+ setIsTesting(false);
+ setIsErrorTest(true);
+ sleep(2500).then(() => {
+ setIsErrorTest(false);
+ });
+ }
}
+ );
- return (
-
- {
+ mutation.mutate(data as DownloadClient);
+ };
+
+ const testClient = (data: unknown) => {
+ testClientMutation.mutate(data as DownloadClient);
+ };
+
+ const initialValues: InitialValues = {
+ name: "",
+ type: "QBITTORRENT",
+ enabled: true,
+ host: "",
+ port: 10000,
+ tls: false,
+ tls_skip_verify: false,
+ username: "",
+ password: "",
+ settings: {}
+ };
+
+ return (
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
- {({ handleSubmit, values }) => {
- return (
-
- );
- }}
-
+
+
+ {({ handleSubmit, values }) => (
+
+
+
+
+
+
+
+
+
+
+
+
{componentMap[values.type]}
+
+
+
+ {rulesComponentMap[values.type]}
+
+
+
+
+
+ )}
+
+
+
+
+
+
+
+ );
+}
+
+interface updateFormProps {
+ isOpen: boolean;
+ toggle: () => void;
+ client: DownloadClient;
+}
+
+export function DownloadClientUpdateForm({ client, isOpen, toggle }: updateFormProps) {
+ const [isTesting, setIsTesting] = useState(false);
+ const [isSuccessfulTest, setIsSuccessfulTest] = useState(false);
+ const [isErrorTest, setIsErrorTest] = useState(false);
+ const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false);
+
+ const mutation = useMutation(
+ (client: DownloadClient) => APIClient.download_clients.update(client),
+ {
+ onSuccess: () => {
+ queryClient.invalidateQueries(["downloadClients"]);
+ toast.custom((t) =>
);
+ toggle();
+ }
+ }
+ );
+
+ const deleteMutation = useMutation(
+ (clientID: number) => APIClient.download_clients.delete(clientID),
+ {
+ onSuccess: () => {
+ queryClient.invalidateQueries();
+ toast.custom((t) => );
+ toggleDeleteModal();
+ }
+ }
+ );
+
+ const testClientMutation = useMutation(
+ (client: DownloadClient) => APIClient.download_clients.test(client),
+ {
+ onMutate: () => {
+ setIsTesting(true);
+ setIsErrorTest(false);
+ setIsSuccessfulTest(false);
+ },
+ onSuccess: () => {
+ sleep(1000)
+ .then(() => {
+ setIsTesting(false);
+ setIsSuccessfulTest(true);
+ })
+ .then(() => {
+ sleep(2500).then(() => {
+ setIsSuccessfulTest(false);
+ });
+ });
+ },
+ onError: () => {
+ setIsTesting(false);
+ setIsErrorTest(true);
+ sleep(2500).then(() => {
+ setIsErrorTest(false);
+ });
+ }
+ }
+ );
+
+ const onSubmit = (data: unknown) => {
+ mutation.mutate(data as DownloadClient);
+ };
+
+ const cancelButtonRef = useRef(null);
+ const cancelModalButtonRef = useRef(null);
+
+ const deleteAction = () => {
+ deleteMutation.mutate(client.id);
+ };
+
+ const testClient = (data: unknown) => {
+ testClientMutation.mutate(data as DownloadClient);
+ };
+
+ const initialValues = {
+ id: client.id,
+ name: client.name,
+ type: client.type,
+ enabled: client.enabled,
+ host: client.host,
+ port: client.port,
+ tls: client.tls,
+ tls_skip_verify: client.tls_skip_verify,
+ username: client.username,
+ password: client.password,
+ settings: client.settings
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {({ handleSubmit, values }) => {
+ return (
+
+ );
+ }}
+
+
+
+
+
+
+
+ );
}
diff --git a/web/src/forms/settings/FeedForms.tsx b/web/src/forms/settings/FeedForms.tsx
index 5c9a103..737f643 100644
--- a/web/src/forms/settings/FeedForms.tsx
+++ b/web/src/forms/settings/FeedForms.tsx
@@ -1,115 +1,118 @@
-import {useMutation} from "react-query";
-import {APIClient} from "../../api/APIClient";
-import {queryClient} from "../../App";
-import {toast} from "react-hot-toast";
+import { useMutation } from "react-query";
+import { APIClient } from "../../api/APIClient";
+import { queryClient } from "../../App";
+import { toast } from "react-hot-toast";
import Toast from "../../components/notifications/Toast";
-import {SlideOver} from "../../components/panels";
-import {NumberFieldWide, PasswordFieldWide, SwitchGroupWide, TextFieldWide} from "../../components/inputs";
-import {ImplementationMap} from "../../screens/settings/Feed";
+import { SlideOver } from "../../components/panels";
+import { NumberFieldWide, PasswordFieldWide, SwitchGroupWide, TextFieldWide } from "../../components/inputs";
+import { ImplementationMap } from "../../screens/settings/Feed";
+import { componentMapType } from "./DownloadClientForms";
interface UpdateProps {
- isOpen: boolean;
- toggle: any;
- feed: Feed;
+ isOpen: boolean;
+ toggle: () => void;
+ feed: Feed;
}
-export function FeedUpdateForm({isOpen, toggle, feed}: UpdateProps) {
- const mutation = useMutation(
- (feed: Feed) => APIClient.feeds.update(feed),
- {
- onSuccess: () => {
- queryClient.invalidateQueries(["feeds"]);
- toast.custom((t) => )
- toggle();
- },
- }
- );
-
- const deleteMutation = useMutation(
- (feedID: number) => APIClient.feeds.delete(feedID),
- {
- onSuccess: () => {
- queryClient.invalidateQueries(["feeds"]);
- toast.custom((t) => )
- },
- }
- );
-
- const onSubmit = (formData: any) => {
- mutation.mutate(formData);
+export function FeedUpdateForm({ isOpen, toggle, feed }: UpdateProps) {
+ const mutation = useMutation(
+ (feed: Feed) => APIClient.feeds.update(feed),
+ {
+ onSuccess: () => {
+ queryClient.invalidateQueries(["feeds"]);
+ toast.custom((t) => );
+ toggle();
+ }
}
+ );
- const deleteAction = () => {
- deleteMutation.mutate(feed.id);
- };
-
- const initialValues = {
- id: feed.id,
- indexer: feed.indexer,
- enabled: feed.enabled,
- type: feed.type,
- name: feed.name,
- url: feed.url,
- api_key: feed.api_key,
- interval: feed.interval,
+ const deleteMutation = useMutation(
+ (feedID: number) => APIClient.feeds.delete(feedID),
+ {
+ onSuccess: () => {
+ queryClient.invalidateQueries(["feeds"]);
+ toast.custom((t) => );
+ }
}
+ );
- return (
-
- {(values) => (
-
-
+ const onSubmit = (formData: unknown) => {
+ mutation.mutate(formData as Feed);
+ };
-
-
-
-
-
-
- {ImplementationMap[feed.type]}
-
-
+ const deleteAction = () => {
+ deleteMutation.mutate(feed.id);
+ };
-
-
-
-
- {componentMap[values.type]}
-
- )}
-
- )
+ const initialValues = {
+ id: feed.id,
+ indexer: feed.indexer,
+ enabled: feed.enabled,
+ type: feed.type,
+ name: feed.name,
+ url: feed.url,
+ api_key: feed.api_key,
+ interval: feed.interval
+ };
+
+ return (
+
+ {(values) => (
+
+
+
+
+
+
+
+
+
+ {ImplementationMap[feed.type]}
+
+
+
+
+
+
+
+ {componentMap[values.type]}
+
+ )}
+
+ );
}
function FormFieldsTorznab() {
- return (
-
- );
+ return (
+
+ );
}
-const componentMap: any = {
- TORZNAB: ,
+const componentMap: componentMapType = {
+ TORZNAB:
};
\ No newline at end of file
diff --git a/web/src/forms/settings/IndexerForms.tsx b/web/src/forms/settings/IndexerForms.tsx
index 0b26db0..a1a46a0 100644
--- a/web/src/forms/settings/IndexerForms.tsx
+++ b/web/src/forms/settings/IndexerForms.tsx
@@ -1,153 +1,163 @@
-import {Fragment, useState} from "react";
+import { Fragment, useState } from "react";
import { toast } from "react-hot-toast";
import { useMutation, useQuery } from "react-query";
-import Select, { components } from "react-select";
-import { Field, Form, Formik } from "formik";
+import Select, {
+ components,
+ ControlProps,
+ InputProps,
+ MenuProps,
+ OptionProps
+} from "react-select";
+import { Field, Form, Formik, FormikValues } from "formik";
import type { FieldProps } from "formik";
import { XIcon } from "@heroicons/react/solid";
import { Dialog, Transition } from "@headlessui/react";
-import {sleep, slugify} from "../../utils";
+import { sleep, slugify } from "../../utils";
import { queryClient } from "../../App";
import DEBUG from "../../components/debug";
import { APIClient } from "../../api/APIClient";
import {
- TextFieldWide,
- PasswordFieldWide,
- SwitchGroupWide
+ TextFieldWide,
+ PasswordFieldWide,
+ SwitchGroupWide
} from "../../components/inputs";
import { SlideOver } from "../../components/panels";
-import Toast from '../../components/notifications/Toast';
+import Toast from "../../components/notifications/Toast";
-const Input = (props: any) => {
+const Input = (props: InputProps) => {
return (
);
-}
+};
-const Control = (props: any) => {
+const Control = (props: ControlProps) => {
return (
);
-}
+};
-const Menu = (props: any) => {
+const Menu = (props: MenuProps) => {
return (
);
-}
+};
-const Option = (props: any) => {
- return (
-
- );
-}
+const Option = (props: OptionProps) => {
+ return (
+
+ );
+};
const IrcSettingFields = (ind: IndexerDefinition, indexer: string) => {
- if (indexer !== "") {
- return (
-
- {ind && ind.irc && ind.irc.settings && (
-
-
-
IRC
-
+ if (indexer !== "") {
+ return (
+
+ {ind && ind.irc && ind.irc.settings && (
+
+
+
IRC
+
Networks, channels and invite commands are configured automatically.
-
-
- {ind.irc.settings.map((f: IndexerSetting, idx: number) => {
- switch (f.type) {
- case "text":
- return
- case "secret":
- if (f.name === "invite_command") {
- return
- }
- return
- }
- return null
- })}
-
- )}
-
- )
- }
-}
+
+
+ {ind.irc.settings.map((f: IndexerSetting, idx: number) => {
+ switch (f.type) {
+ case "text":
+ return
;
+ case "secret":
+ if (f.name === "invite_command") {
+ return
;
+ }
+ return
;
+ }
+ return null;
+ })}
+
+ )}
+
+ );
+ }
+};
const FeedSettingFields = (ind: IndexerDefinition, indexer: string) => {
- if (indexer !== "") {
- return (
-
- {ind && ind.torznab && ind.torznab.settings && (
-
-
-
Torznab
-
+ if (indexer !== "") {
+ return (
+
+ {ind && ind.torznab && ind.torznab.settings && (
+
+
+
Torznab
+
Torznab feed
-
-
+
+
-
+
- {ind.torznab.settings.map((f: IndexerSetting, idx: number) => {
- switch (f.type) {
- case "text":
- return
- case "secret":
- return
- }
- return null
- })}
-
- )}
-
- )
- }
-}
+ {ind.torznab.settings.map((f: IndexerSetting, idx: number) => {
+ switch (f.type) {
+ case "text":
+ return
;
+ case "secret":
+ return
;
+ }
+ return null;
+ })}
+
+ )}
+
+ );
+ }
+};
const SettingFields = (ind: IndexerDefinition, indexer: string) => {
- if (indexer !== "") {
- return (
-
- {ind && ind.settings && ind.settings.map((f: any, idx: number) => {
- switch (f.type) {
- case "text":
- return (
-
- )
- case "secret":
- return (
-
- )
- }
- return null
- })}
-
-
-
-
- )
- }
-}
+ if (indexer !== "") {
+ return (
+
+ {ind && ind.settings && ind.settings.map((f, idx: number) => {
+ switch (f.type) {
+ case "text":
+ return (
+
+ );
+ case "secret":
+ return (
+
+ );
+ }
+ return null;
+ })}
+
+
+
+
+ );
+ }
+};
function slugIdentifier(name: string) {
- const l = name.toLowerCase()
- const r = l.replaceAll("torznab", "")
- return slugify(`torznab-${r}`)
+ const l = name.toLowerCase();
+ const r = l.replaceAll("torznab", "");
+ return slugify(`torznab-${r}`);
}
// interface initialValues {
@@ -160,385 +170,391 @@ function slugIdentifier(name: string) {
// settings?: Record;
// }
+type SelectValue = {
+ label: string;
+ value: string;
+};
+
interface AddProps {
isOpen: boolean;
- toggle: any;
+ toggle: () => void;
}
export function IndexerAddForm({ isOpen, toggle }: AddProps) {
- const [indexer, setIndexer] = useState({} as IndexerDefinition)
+ const [indexer, setIndexer] = useState({} as IndexerDefinition);
- const { data } = useQuery('indexerDefinition', APIClient.indexers.getSchema,
- {
- enabled: isOpen,
- refetchOnWindowFocus: false
+ const { data } = useQuery("indexerDefinition", APIClient.indexers.getSchema,
+ {
+ enabled: isOpen,
+ refetchOnWindowFocus: false
+ }
+ );
+
+ const mutation = useMutation(
+ (indexer: Indexer) => APIClient.indexers.create(indexer), {
+ onSuccess: () => {
+ queryClient.invalidateQueries(["indexer"]);
+ toast.custom((t) => );
+ sleep(1500);
+ toggle();
+ },
+ onError: () => {
+ toast.custom((t) => );
+ }
+ });
+
+ const ircMutation = useMutation(
+ (network: IrcNetworkCreate) => APIClient.irc.createNetwork(network)
+ );
+
+ const feedMutation = useMutation(
+ (feed: FeedCreate) => APIClient.feeds.create(feed)
+ );
+
+ const onSubmit = (formData: FormikValues) => {
+ const ind = data && data.find(i => i.identifier === formData.identifier);
+ if (!ind)
+ return;
+
+ if (formData.implementation === "torznab") {
+ // create slug for indexer identifier as "torznab-indexer_name"
+ const name = slugIdentifier(formData.name);
+
+ const createFeed: FeedCreate = {
+ name: formData.name,
+ enabled: false,
+ type: "TORZNAB",
+ url: formData.feed.url,
+ api_key: formData.feed.api_key,
+ interval: 30,
+ indexer: name,
+ indexer_id: 0
+ };
+
+ mutation.mutate(formData as Indexer, {
+ onSuccess: (indexer) => {
+ // @eslint-ignore
+ createFeed.indexer_id = indexer.id;
+
+ feedMutation.mutate(createFeed);
}
- )
+ });
+ return;
+ }
- const mutation = useMutation(
- (indexer: Indexer) => APIClient.indexers.create(indexer), {
+ if (formData.implementation === "irc") {
+
+ const channels: IrcChannel[] = [];
+ if (ind.irc?.channels.length) {
+ ind.irc.channels.forEach(element => {
+ channels.push({
+ id: 0,
+ enabled: true,
+ name: element,
+ password: "",
+ detached: false,
+ monitoring: false
+ });
+ });
+ }
+
+ const network: IrcNetworkCreate = {
+ name: ind.irc.network,
+ pass: "",
+ enabled: false,
+ connected: false,
+ server: ind.irc.server,
+ port: ind.irc.port,
+ tls: ind.irc.tls,
+ nickserv: formData.irc.nickserv,
+ invite_command: formData.irc.invite_command,
+ channels: channels
+ };
+
+ mutation.mutate(formData as Indexer, {
onSuccess: () => {
- queryClient.invalidateQueries(['indexer']);
- toast.custom((t) => )
- sleep(1500)
- toggle()
- },
- onError: () => {
- toast.custom((t) => )
+ ircMutation.mutate(network);
}
- })
+ });
+ }
+ };
- const ircMutation = useMutation(
- (network: IrcNetworkCreate) => APIClient.irc.createNetwork(network)
- );
+ return (
+
+
+
+
- const feedMutation = useMutation(
- (feed: FeedCreate) => APIClient.feeds.create(feed)
- );
-
- const onSubmit = (formData: any) => {
- const ind = data && data.find(i => i.identifier === formData.identifier);
- if (!ind)
- return;
-
- if (formData.implementation === "torznab") {
- // create slug for indexer identifier as "torznab-indexer_name"
- const name = slugIdentifier(formData.name)
-
- const createFeed: FeedCreate = {
- name: formData.name,
- enabled: false,
- type: "TORZNAB",
- url: formData.feed.url,
- api_key: formData.feed.api_key,
- interval: 30,
- indexer: name,
- indexer_id: 0,
- }
-
- mutation.mutate(formData, {
- onSuccess: (indexer) => {
- createFeed.indexer_id = indexer!.id
-
- feedMutation.mutate(createFeed)
- }
- });
- return;
- }
-
- if (formData.implementation === "irc") {
-
- const channels: IrcChannel[] = [];
- if (ind.irc?.channels.length) {
- ind.irc.channels.forEach(element => {
- channels.push({
- id: 0,
- enabled: true,
- name: element,
- password: "",
- detached: false,
- monitoring: false
- });
- });
- }
-
- const network: IrcNetworkCreate = {
- name: ind.irc.network,
- pass: "",
- enabled: false,
- connected: false,
- server: ind.irc.server,
- port: ind.irc.port,
- tls: ind.irc.tls,
- nickserv: formData.irc.nickserv,
- invite_command: formData.irc.invite_command,
- channels: channels,
- }
-
- mutation.mutate(formData, {
- onSuccess: () => {
- ircMutation.mutate(network)
- }
- });
- }
- };
-
- return (
-
-
-
-
-
-
-
-
-
- {({ values }) => (
-
+ )}
+
+
+
+
+
+
+
+
+ );
}
interface UpdateProps {
isOpen: boolean;
- toggle: any;
- indexer: Indexer;
+ toggle: () => void;
+ indexer: IndexerDefinition;
}
export function IndexerUpdateForm({ isOpen, toggle, indexer }: UpdateProps) {
- const mutation = useMutation((indexer: Indexer) => APIClient.indexers.update(indexer), {
- onSuccess: () => {
- queryClient.invalidateQueries(['indexer']);
- toast.custom((t) =>
)
- sleep(1500)
+ const mutation = useMutation((indexer: Indexer) => APIClient.indexers.update(indexer), {
+ onSuccess: () => {
+ queryClient.invalidateQueries(["indexer"]);
+ toast.custom((t) =>
);
+ sleep(1500);
- toggle()
- }
- })
-
- const deleteMutation = useMutation((id: number) => APIClient.indexers.delete(id), {
- onSuccess: () => {
- queryClient.invalidateQueries(['indexer']);
- toast.custom((t) =>
)
- sleep(1500);
-
- toggle();
- }
- })
-
- const onSubmit = (data: any) => {
- // TODO clear data depending on type
- mutation.mutate(data)
- };
-
- const deleteAction = () => {
- deleteMutation.mutate(indexer.id)
+ toggle();
}
+ });
- const renderSettingFields = (settings: IndexerSetting[]) => {
- if (settings === undefined || settings === null) {
- return null
- }
-
- return (
-
- {settings.map((f: IndexerSetting, idx: number) => {
- switch (f.type) {
- case "text":
- return (
-
- )
- case "secret":
- return (
-
- )
- }
- return null
- })}
-
- )
+ const deleteMutation = useMutation((id: number) => APIClient.indexers.delete(id), {
+ onSuccess: () => {
+ queryClient.invalidateQueries(["indexer"]);
+ toast.custom((t) =>
);
}
+ });
- const initialValues = {
- id: indexer.id,
- name: indexer.name,
- enabled: indexer.enabled,
- identifier: indexer.identifier,
- implementation: indexer.implementation,
- settings: indexer.settings?.reduce(
- (o: Record
, obj: IndexerSetting) => ({
- ...o,
- [obj.name]: obj.value
- } as Record),
- {} as Record
- ),
+ const onSubmit = (data: unknown) => {
+ // TODO clear data depending on type
+ mutation.mutate(data as Indexer);
+ };
+
+ const deleteAction = () => {
+ deleteMutation.mutate(indexer.id ?? 0);
+ };
+
+ const renderSettingFields = (settings: IndexerSetting[]) => {
+ if (settings === undefined) {
+ return null;
}
return (
-
- {() => (
-
-
-
-
-
-
- {({ field, meta }: FieldProps) => (
-
-
- {meta.touched && meta.error && {meta.error}}
-
- )}
-
-
+
+ {settings.map((f: IndexerSetting, idx: number) => {
+ switch (f.type) {
+ case "text":
+ return (
+
+ );
+ case "secret":
+ return (
+
+ );
+ }
+ return null;
+ })}
+
+ );
+ };
-
-
-
-
- {renderSettingFields(indexer.settings)}
-
- )}
-
+ const initialValues = {
+ id: indexer.id,
+ name: indexer.name,
+ enabled: indexer.enabled,
+ identifier: indexer.identifier,
+ implementation: indexer.implementation,
+ settings: indexer.settings?.reduce(
+ (o: Record, obj: IndexerSetting) => ({
+ ...o,
+ [obj.name]: obj.value
+ } as Record),
+ {} as Record
)
+ };
+
+ return (
+
+ {() => (
+
+
+
+
+
+
+ {({ field, meta }: FieldProps) => (
+
+
+ {meta.touched && meta.error && {meta.error}}
+
+ )}
+
+
+
+
+
+
+
+ {renderSettingFields(indexer.settings)}
+
+ )}
+
+ );
}
\ No newline at end of file
diff --git a/web/src/forms/settings/IrcForms.tsx b/web/src/forms/settings/IrcForms.tsx
index 365fa19..dfa118c 100644
--- a/web/src/forms/settings/IrcForms.tsx
+++ b/web/src/forms/settings/IrcForms.tsx
@@ -1,87 +1,87 @@
import { useMutation } from "react-query";
import { toast } from "react-hot-toast";
import { XIcon } from "@heroicons/react/solid";
-import { Field, FieldArray } from "formik";
+import { Field, FieldArray, FormikErrors, FormikValues } from "formik";
import type { FieldProps } from "formik";
import { queryClient } from "../../App";
import { APIClient } from "../../api/APIClient";
import {
- TextFieldWide,
- PasswordFieldWide,
- SwitchGroupWide,
- NumberFieldWide
-} from "../../components/inputs/input_wide";
+ TextFieldWide,
+ PasswordFieldWide,
+ SwitchGroupWide,
+ NumberFieldWide
+} from "../../components/inputs";
import { SlideOver } from "../../components/panels";
-import Toast from '../../components/notifications/Toast';
+import Toast from "../../components/notifications/Toast";
interface ChannelsFieldArrayProps {
channels: IrcChannel[];
}
const ChannelsFieldArray = ({ channels }: ChannelsFieldArrayProps) => (
-
-
- {({ remove, push }) => (
-
- {channels && channels.length > 0 ? (
- channels.map((_channel: IrcChannel, index: number) => (
-
- ))
- ) : (
-
- No channels!
-
+
+
+ {({ remove, push }) => (
+
+ {channels && channels.length > 0 ? (
+ channels.map((_channel: IrcChannel, index: number) => (
+
+
+
+
+ ))
+ ) : (
+
+ No channels!
+
+ )}
+
+
+ )}
+
+
);
interface IrcNetworkAddFormValues {
@@ -95,105 +95,101 @@ interface IrcNetworkAddFormValues {
channels: IrcChannel[];
}
-export function IrcNetworkAddForm({ isOpen, toggle }: any) {
- const mutation = useMutation(
- (network: IrcNetwork) => APIClient.irc.createNetwork(network),
- {
- onSuccess: () => {
- queryClient.invalidateQueries(['networks']);
- toast.custom((t) => )
- toggle()
- },
- onError: () => {
- toast.custom((t) => )
- },
- }
- );
+interface AddFormProps {
+ isOpen: boolean;
+ toggle: () => void;
+}
- const onSubmit = (data: any) => {
- // easy way to split textarea lines into array of strings for each newline.
- // parse on the field didn't really work.
- data.connect_commands = (
- data.connect_commands && data.connect_commands.length > 0 ?
- data.connect_commands.replace(/\r\n/g, "\n").split("\n") :
- []
- );
-
- mutation.mutate(data);
- };
-
- const validate = (values: IrcNetworkAddFormValues) => {
- const errors = {} as any;
- if (!values.name)
- errors.name = "Required";
-
- if (!values.port)
- errors.port = "Required";
-
- if (!values.server)
- errors.server = "Required";
-
- if (!values.nickserv || !values.nickserv.account)
- errors.nickserv = { account: "Required" };
-
- return errors;
+export function IrcNetworkAddForm({ isOpen, toggle }: AddFormProps) {
+ const mutation = useMutation(
+ (network: IrcNetwork) => APIClient.irc.createNetwork(network),
+ {
+ onSuccess: () => {
+ queryClient.invalidateQueries(["networks"]);
+ toast.custom((t) => );
+ toggle();
+ },
+ onError: () => {
+ toast.custom((t) => );
+ }
}
+ );
- const initialValues: IrcNetworkAddFormValues = {
- name: "",
- enabled: true,
- server: "",
- port: 6667,
- tls: false,
- pass: "",
- nickserv: {
- account: ""
- },
- channels: [],
- };
+ const onSubmit = (data: unknown) => {
+ mutation.mutate(data as IrcNetwork);
+ };
+ const validate = (values: FormikValues) => {
+ const errors = {} as FormikErrors;
+ if (!values.name)
+ errors.name = "Required";
- return (
-
- {(values) => (
- <>
-
+ if (!values.port)
+ errors.port = "Required";
-
+ if (!values.server)
+ errors.server = "Required";
-
-
-
+ if (!values.nickserv || !values.nickserv.account)
+ errors.nickserv = { account: "Required" };
-
-
-
+ return errors;
+ };
-
-
-
+ const initialValues: IrcNetworkAddFormValues = {
+ name: "",
+ enabled: true,
+ server: "",
+ port: 6667,
+ tls: false,
+ pass: "",
+ nickserv: {
+ account: ""
+ },
+ channels: []
+ };
-
+ return (
+
+ {(values) => (
+ <>
+
-
-
+
-
+
+
+
-
- >
- )}
-
- )
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )}
+
+ );
}
interface IrcNetworkUpdateFormValues {
@@ -216,118 +212,113 @@ interface IrcNetworkUpdateFormProps {
}
export function IrcNetworkUpdateForm({
- isOpen,
- toggle,
- network
+ isOpen,
+ toggle,
+ network
}: IrcNetworkUpdateFormProps) {
- const mutation = useMutation((network: IrcNetwork) => APIClient.irc.updateNetwork(network), {
- onSuccess: () => {
- queryClient.invalidateQueries(['networks']);
- toast.custom((t) => )
- toggle()
- }
- })
+ const mutation = useMutation((network: IrcNetwork) => APIClient.irc.updateNetwork(network), {
+ onSuccess: () => {
+ queryClient.invalidateQueries(["networks"]);
+ toast.custom((t) => );
+ toggle();
+ }
+ });
- const deleteMutation = useMutation((id: number) => APIClient.irc.deleteNetwork(id), {
- onSuccess: () => {
- queryClient.invalidateQueries(['networks']);
- toast.custom((t) => )
+ const deleteMutation = useMutation((id: number) => APIClient.irc.deleteNetwork(id), {
+ onSuccess: () => {
+ queryClient.invalidateQueries(["networks"]);
+ toast.custom((t) => );
- toggle()
- }
- })
+ toggle();
+ }
+ });
- const onSubmit = (data: any) => {
- // 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
- // let cmds = data.connect_commands && data.connect_commands.length > 0 ? data.connect_commands.replace(/\r\n/g,"\n").split("\n") : [];
- // data.connect_commands = cmds
- // console.log("formatted", data)
+ const onSubmit = (data: unknown) => {
+ mutation.mutate(data as IrcNetwork);
+ };
- mutation.mutate(data)
- };
+ const validate = (values: FormikValues) => {
+ const errors = {} as FormikErrors;
- const validate = (values: any) => {
- const errors = {} as any;
-
- if (!values.name) {
- errors.name = "Required";
- }
-
- if (!values.server) {
- errors.server = "Required";
- }
-
- if (!values.port) {
- errors.port = "Required";
- }
-
- if (!values.nickserv?.account) {
- errors.nickserv.account = "Required";
- }
-
- return errors;
+ if (!values.name) {
+ errors.name = "Required";
}
- const deleteAction = () => {
- deleteMutation.mutate(network.id)
+ if (!values.server) {
+ errors.server = "Required";
}
- const initialValues: IrcNetworkUpdateFormValues = {
- id: network.id,
- name: network.name,
- enabled: network.enabled,
- server: network.server,
- port: network.port,
- tls: network.tls,
- nickserv: network.nickserv,
- pass: network.pass,
- channels: network.channels,
- invite_command: network.invite_command
+ if (!values.port) {
+ errors.port = "Required";
}
- return (
-
- {(values) => (
- <>
-
+ if (!values.nickserv?.account) {
+ errors.nickserv = {
+ account: "Required"
+ };
+ }
-
+ return errors;
+ };
-
-
-
+ const deleteAction = () => {
+ deleteMutation.mutate(network.id);
+ };
-
-
-
+ const initialValues: IrcNetworkUpdateFormValues = {
+ id: network.id,
+ name: network.name,
+ enabled: network.enabled,
+ server: network.server,
+ port: network.port,
+ tls: network.tls,
+ nickserv: network.nickserv,
+ pass: network.pass,
+ channels: network.channels,
+ invite_command: network.invite_command
+ };
-
-
-
+ return (
+
+ {(values) => (
+ <>
+
-
+
-
+
+
+
-
- >
- )}
-
- )
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )}
+
+ );
}
\ No newline at end of file
diff --git a/web/src/forms/settings/NotifiactionForms.tsx b/web/src/forms/settings/NotifiactionForms.tsx
index e8ba885..31c4326 100644
--- a/web/src/forms/settings/NotifiactionForms.tsx
+++ b/web/src/forms/settings/NotifiactionForms.tsx
@@ -1,83 +1,87 @@
import { Dialog, Transition } from "@headlessui/react";
import { Fragment } from "react";
-import {Field, Form, Formik} from "formik";
-import type {FieldProps} from "formik";
-import {XIcon} from "@heroicons/react/solid";
-import Select, {components} from "react-select";
+import { Field, Form, Formik, FormikErrors, FormikValues } from "formik";
+import type { FieldProps } from "formik";
+import { XIcon } from "@heroicons/react/solid";
+import Select, { components, ControlProps, InputProps, MenuProps, OptionProps } from "react-select";
import {
- SwitchGroupWide,
- TextFieldWide
+ SwitchGroupWide,
+ TextFieldWide
} from "../../components/inputs";
import DEBUG from "../../components/debug";
-import {EventOptions, NotificationTypeOptions} from "../../domain/constants";
-import {useMutation} from "react-query";
-import {APIClient} from "../../api/APIClient";
-import {queryClient} from "../../App";
-import {toast} from "react-hot-toast";
+import { EventOptions, NotificationTypeOptions, SelectOption } from "../../domain/constants";
+import { useMutation } from "react-query";
+import { APIClient } from "../../api/APIClient";
+import { queryClient } from "../../App";
+import { toast } from "react-hot-toast";
import Toast from "../../components/notifications/Toast";
-import {SlideOver} from "../../components/panels";
+import { SlideOver } from "../../components/panels";
+import { componentMapType } from "./DownloadClientForms";
+const Input = (props: InputProps) => {
+ return (
+
+ );
+};
-const Input = (props: any) => {
- return (
-
- );
-}
+const Control = (props: ControlProps) => {
+ return (
+
+ );
+};
-const Control = (props: any) => {
- return (
-
- );
-}
+const Menu = (props: MenuProps) => {
+ return (
+
+ );
+};
-const Menu = (props: any) => {
- return (
-
- );
-}
-
-const Option = (props: any) => {
- return (
-
- );
-}
+const Option = (props: OptionProps) => {
+ return (
+
+ );
+};
function FormFieldsDiscord() {
- return (
-
- {/*
*/}
- {/*
Credentials*/}
- {/*
*/}
- {/* Api keys etc*/}
- {/*
*/}
- {/*
*/}
+ return (
+
+ {/*
*/}
+ {/*
Credentials*/}
+ {/*
*/}
+ {/* Api keys etc*/}
+ {/*
*/}
+ {/*
*/}
-
-
- );
+
+
+ );
}
-const componentMap: any = {
- DISCORD: ,
+const componentMap: componentMapType = {
+ DISCORD:
};
interface NotificationAddFormValues {
@@ -87,349 +91,353 @@ interface NotificationAddFormValues {
interface AddProps {
isOpen: boolean;
- toggle: any;
+ toggle: () => void;
}
-export function NotificationAddForm({isOpen, toggle}: AddProps) {
- const mutation = useMutation(
- (notification: Notification) => APIClient.notifications.create(notification),
- {
- onSuccess: () => {
- queryClient.invalidateQueries(['notifications']);
- toast.custom((t) => )
- toggle()
- },
- onError: () => {
- toast.custom((t) => )
- },
- }
- );
-
- const onSubmit = (formData: any) => {
- mutation.mutate(formData)
+export function NotificationAddForm({ isOpen, toggle }: AddProps) {
+ const mutation = useMutation(
+ (notification: Notification) => APIClient.notifications.create(notification),
+ {
+ onSuccess: () => {
+ queryClient.invalidateQueries(["notifications"]);
+ toast.custom((t) => );
+ toggle();
+ },
+ onError: () => {
+ toast.custom((t) => );
+ }
}
+ );
- const validate = (values: NotificationAddFormValues) => {
- const errors = {} as any;
- if (!values.name)
- errors.name = "Required";
+ const onSubmit = (formData: unknown) => {
+ mutation.mutate(formData as Notification);
+ };
- return errors;
- }
+ const validate = (values: NotificationAddFormValues) => {
+ const errors = {} as FormikErrors;
+ if (!values.name)
+ errors.name = "Required";
- return (
-
-
-
-
+ return errors;
+ };
-
-
-
-
- {({values}) => (
-
-
-
-
-
-
Add
- Notifications
-
- Trigger notifications on different events.
-
-
-
-
-
-
-
+ return (
+
+
+
+
-
-
-
-
-
-
-
-
-
- {({
- field,
- form: {setFieldValue, resetForm}
- }: FieldProps) => (
-
-
-
-
-
-
-
-
-
-
-
-
Events
-
- Select what events to trigger on
-
-
-
-
-
-
-
-
-
- {componentMap[values.type]}
-
-
-
-
-
-
-
-
-
-
-
- )}
-
+
+
+
+
+ {({ values }) => (
+
+
+
+
+
+
+ Add Notifications
+
+
+ Trigger notifications on different events.
+
+
+
+
+
+
-
-
-
-
-
- )
-}
+
-const EventCheckBoxes = () => (
-
-)
-
-interface UpdateProps {
- isOpen: boolean;
- toggle: any;
- notification: Notification;
-}
-
-export function NotificationUpdateForm({isOpen, toggle, notification}: UpdateProps) {
- const mutation = useMutation(
- (notification: Notification) => APIClient.notifications.update(notification),
- {
- onSuccess: () => {
- queryClient.invalidateQueries(["notifications"]);
- toast.custom((t) => )
- toggle();
- },
- }
- );
-
- const deleteMutation = useMutation(
- (notificationID: number) => APIClient.notifications.delete(notificationID),
- {
- onSuccess: () => {
- queryClient.invalidateQueries(["notifications"]);
- toast.custom((t) => )
- },
- }
- );
-
- const onSubmit = (formData: any) => {
- mutation.mutate(formData);
- }
-
- const deleteAction = () => {
- deleteMutation.mutate(notification.id);
- };
-
- const initialValues = {
- id: notification.id,
- enabled: notification.enabled,
- type: notification.type,
- name: notification.name,
- webhook: notification.webhook,
- events: notification.events || [],
- }
-
- return (
-
- {(values) => (
-
-
-
-
-
+
+
-
+
-
- {({field, form: {setFieldValue, resetForm}}: FieldProps) => (
-
- placeholder="Choose a type"
- styles={{
- singleValue: (base) => ({
- ...base,
- color: "unset"
- })
- }}
- theme={(theme) => ({
- ...theme,
- spacing: {
- ...theme.spacing,
- controlHeight: 30,
- baseUnit: 2,
- }
- })}
- value={field?.value && NotificationTypeOptions.find(o => o.value == field?.value)}
- onChange={(option: any) => {
- resetForm()
- setFieldValue(field.name, option?.value ?? "")
- }}
- options={NotificationTypeOptions}
- />
- )}
-
-
+
-
-
+
-
Events
-
- Select what events to trigger on
-
+
Events
+
+ Select what events to trigger on
+
-
+
+
+
-
- {componentMap[values.type]}
-
- )}
-
- )
+ {componentMap[values.type]}
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+
+
+
+
+
+
+ );
+}
+
+const EventCheckBoxes = () => (
+
+);
+
+interface UpdateProps {
+ isOpen: boolean;
+ toggle: () => void;
+ notification: Notification;
+}
+
+export function NotificationUpdateForm({ isOpen, toggle, notification }: UpdateProps) {
+ const mutation = useMutation(
+ (notification: Notification) => APIClient.notifications.update(notification),
+ {
+ onSuccess: () => {
+ queryClient.invalidateQueries(["notifications"]);
+ toast.custom((t) => );
+ toggle();
+ }
+ }
+ );
+
+ const deleteMutation = useMutation(
+ (notificationID: number) => APIClient.notifications.delete(notificationID),
+ {
+ onSuccess: () => {
+ queryClient.invalidateQueries(["notifications"]);
+ toast.custom((t) => );
+ }
+ }
+ );
+
+ const onSubmit = (formData: unknown) => {
+ mutation.mutate(formData as Notification);
+ };
+
+ const deleteAction = () => {
+ deleteMutation.mutate(notification.id);
+ };
+
+ const initialValues = {
+ id: notification.id,
+ enabled: notification.enabled,
+ type: notification.type,
+ name: notification.name,
+ webhook: notification.webhook,
+ events: notification.events || []
+ };
+
+ return (
+
+ {(values) => (
+
+
+
+
+
+
+
+
+
+
+ {({ field, form: { setFieldValue, resetForm } }: FieldProps) => (
+ ({
+ ...base,
+ color: "unset"
+ })
+ }}
+ theme={(theme) => ({
+ ...theme,
+ spacing: {
+ ...theme.spacing,
+ controlHeight: 30,
+ baseUnit: 2
+ }
+ })}
+ value={field?.value && NotificationTypeOptions.find(o => o.value == field?.value)}
+ onChange={(option: unknown) => {
+ resetForm();
+ const opt = option as SelectOption;
+ setFieldValue(field.name, opt.value ?? "");
+ }}
+ options={NotificationTypeOptions}
+ />
+ )}
+
+
+
+
+
+
+
+
+
+
+
Events
+
+ Select what events to trigger on
+
+
+
+
+
+
+
+
+ {componentMap[values.type]}
+
+ )}
+
+ );
}
diff --git a/web/src/hooks/hooks.ts b/web/src/hooks/hooks.ts
index 3f56754..4ad4901 100644
--- a/web/src/hooks/hooks.ts
+++ b/web/src/hooks/hooks.ts
@@ -1,8 +1,8 @@
import { useState } from "react";
export function useToggle(initialValue = false): [boolean, () => void] {
- const [value, setValue] = useState(initialValue);
- const toggle = () => setValue(v => !v);
+ const [value, setValue] = useState(initialValue);
+ const toggle = () => setValue(v => !v);
- return [value, toggle];
+ return [value, toggle];
}
diff --git a/web/src/index.tsx b/web/src/index.tsx
index 3e22e05..e454f0a 100644
--- a/web/src/index.tsx
+++ b/web/src/index.tsx
@@ -17,8 +17,8 @@ window.APP = window.APP || {};
InitializeGlobalContext();
ReactDOM.render(
-
-
- ,
- document.getElementById("root")
+
+
+ ,
+ document.getElementById("root")
);
diff --git a/web/src/reportWebVitals.ts b/web/src/reportWebVitals.ts
index f81e5f4..ad7f32f 100644
--- a/web/src/reportWebVitals.ts
+++ b/web/src/reportWebVitals.ts
@@ -2,7 +2,7 @@ import type { ReportHandler } from "web-vitals";
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) {
- import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
+ import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
diff --git a/web/src/screens/Base.tsx b/web/src/screens/Base.tsx
index 78e8f7b..5b6eb2f 100644
--- a/web/src/screens/Base.tsx
+++ b/web/src/screens/Base.tsx
@@ -1,6 +1,6 @@
import { Fragment } from "react";
-import { NavLink, Link, Route, Switch } from "react-router-dom";
import type { match } from "react-router-dom";
+import { Link, NavLink, Route, Switch } from "react-router-dom";
import { Disclosure, Menu, Transition } from "@headlessui/react";
import { ExternalLinkIcon } from "@heroicons/react/solid";
import { ChevronDownIcon, MenuIcon, XIcon } from "@heroicons/react/outline";
@@ -11,9 +11,9 @@ import { Logs } from "./Logs";
import { Releases } from "./releases";
import { Dashboard } from "./dashboard";
import { FilterDetails, Filters } from "./filters";
-import { AuthContext } from '../utils/Context';
+import { AuthContext } from "../utils/Context";
-import logo from '../logo.png';
+import logo from "../logo.png";
interface NavItem {
name: string;
@@ -21,229 +21,230 @@ interface NavItem {
}
function classNames(...classes: string[]) {
- return classes.filter(Boolean).join(' ')
+ return classes.filter(Boolean).join(" ");
}
const isActiveMatcher = (
- match: match | null,
- location: { pathname: string },
- item: NavItem
+ match: match | null,
+ location: { pathname: string },
+ item: NavItem
) => {
if (!match)
return false;
if (match?.url === "/" && item.path === "/" && location.pathname === "/")
- return true
+ return true;
if (match.url === "/")
- return false;
+ return false;
return true;
-}
+};
export default function Base() {
- const authContext = AuthContext.useValue();
- const nav: Array = [
- { name: 'Dashboard', path: "/" },
- { name: 'Filters', path: "/filters" },
- { name: 'Releases', path: "/releases" },
- { name: "Settings", path: "/settings" },
- { name: "Logs", path: "/logs" }
- ];
+ const authContext = AuthContext.useValue();
+ const nav: Array = [
+ { name: "Dashboard", path: "/" },
+ { name: "Filters", path: "/filters" },
+ { name: "Releases", path: "/releases" },
+ { name: "Settings", path: "/settings" },
+ { name: "Logs", path: "/logs" }
+ ];
- return (
-
-
- {({ open }) => (
- <>
-
-
-
-
-
-

-

-
-
-
- {nav.map((item, itemIdx) =>
-
isActiveMatcher(match, location, item)}
- >
- {item.name}
-
- )}
-
- Docs
-
-
-
-
-
-
-
-
-
-
-
- {/* Mobile menu button */}
-
- Open main menu
- {open ? (
-
- ) : (
-
- )}
-
-
-
-
-
-
-
-
- {nav.map((item) =>
-
isActiveMatcher(match, location, item)}
+ return (
+
+
+ {({ open }) => (
+ <>
+
+
+
+
+
+

+

+
+
+
+ {nav.map((item, itemIdx) =>
+
isActiveMatcher(match, location, item)}
+ >
+ {item.name}
+
+ )}
+
+ Docs
+
+
+
+
+
+
+
+
+ Settings
+
+ )}
+
+
+ {({ active }) => (
+
+ Logout
+
+ )}
+
+
+
+ >
+ )}
+
+
+
+
+ {/* Mobile menu button */}
+
+ Open main menu
+ {open ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
-
- >
+
+
+ {nav.map((item) =>
+ isActiveMatcher(match, location, item)}
+ >
+ {item.name}
+
)}
-
+
+ Logout
+
+
-
-
-
-
+
+ >
+ )}
+
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
- )
+
+
+
+
+
+
+
+
+
+ );
}
diff --git a/web/src/screens/Logs.tsx b/web/src/screens/Logs.tsx
index 79ca243..8a2a7aa 100644
--- a/web/src/screens/Logs.tsx
+++ b/web/src/screens/Logs.tsx
@@ -18,7 +18,7 @@ const LogColors: Record = {
"TRACE": "text-purple-300",
"DEBUG": "text-yellow-500",
"INFO": "text-green-500",
- "ERROR": "text-red-500",
+ "ERROR": "text-red-500"
};
export const Logs = () => {
@@ -29,7 +29,7 @@ export const Logs = () => {
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "auto" });
- }
+ };
useEffect(() => {
const es = APIClient.events.logs();
@@ -40,7 +40,7 @@ export const Logs = () => {
if (settings.scrollOnNewLog)
scrollToBottom();
- }
+ };
return () => es.close();
}, [setLogs, settings]);
@@ -96,7 +96,7 @@ export const Logs = () => {
key={idx}
className={classNames(
settings.indentLogLines ? "grid justify-start grid-flow-col" : "",
- settings.hideWrappedText ? "truncate hover:text-ellipsis hover:whitespace-normal" : "",
+ settings.hideWrappedText ? "truncate hover:text-ellipsis hover:whitespace-normal" : ""
)}
>
{
)}
>
{a.level}
- {' '}
+ {" "}
) : null}
@@ -125,5 +125,5 @@ export const Logs = () => {
- )
-}
+ );
+};
diff --git a/web/src/screens/Settings.tsx b/web/src/screens/Settings.tsx
index 313edf5..6c39701 100644
--- a/web/src/screens/Settings.tsx
+++ b/web/src/screens/Settings.tsx
@@ -1,120 +1,137 @@
-import {BellIcon, ChatAlt2Icon, CogIcon, CollectionIcon, DownloadIcon, KeyIcon, RssIcon} from '@heroicons/react/outline'
-import {NavLink, Route, Switch as RouteSwitch, useLocation, useRouteMatch} from "react-router-dom";
+import { BellIcon, ChatAlt2Icon, CogIcon, CollectionIcon, DownloadIcon, KeyIcon, RssIcon } from "@heroicons/react/outline";
+import { NavLink, Route, Switch as RouteSwitch, useLocation, useRouteMatch } from "react-router-dom";
import { classNames } from "../utils";
import IndexerSettings from "./settings/Indexer";
import { IrcSettings } from "./settings/Irc";
import ApplicationSettings from "./settings/Application";
import DownloadClientSettings from "./settings/DownloadClient";
-import { RegexPlayground } from './settings/RegexPlayground';
+import { RegexPlayground } from "./settings/RegexPlayground";
import ReleaseSettings from "./settings/Releases";
import NotificationSettings from "./settings/Notifications";
import FeedSettings from "./settings/Feed";
-const subNavigation = [
- {name: 'Application', href: '', icon: CogIcon, current: true},
- {name: 'Indexers', href: 'indexers', icon: KeyIcon, current: false},
- {name: 'IRC', href: 'irc', icon: ChatAlt2Icon, current: false},
- {name: 'Feeds', href: 'feeds', icon: RssIcon, current: false},
- {name: 'Clients', href: 'clients', icon: DownloadIcon, current: false},
- {name: 'Notifications', href: 'notifications', icon: BellIcon, current: false},
- {name: 'Releases', href: 'releases', icon: CollectionIcon, current: false},
- // {name: 'Regex Playground', href: 'regex-playground', icon: CogIcon, current: false}
- // {name: 'Rules', href: 'rules', icon: ClipboardCheckIcon, current: false},
-]
-
-function SubNavLink({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
- const too = item.href ? `${url}/${item.href}` : url
- return (
-
-
- {item.name}
-
- )
+interface NavTabType {
+ name: string;
+ href: string;
+ icon: typeof CogIcon;
+ current: boolean;
}
-function SidebarNav({subNavigation, url}: any) {
- return (
-
- )
+const subNavigation: NavTabType[] = [
+ { name: "Application", href: "", icon: CogIcon, current: true },
+ { name: "Indexers", href: "indexers", icon: KeyIcon, current: false },
+ { name: "IRC", href: "irc", icon: ChatAlt2Icon, current: false },
+ { name: "Feeds", href: "feeds", icon: RssIcon, current: false },
+ { name: "Clients", href: "clients", icon: DownloadIcon, current: false },
+ { name: "Notifications", href: "notifications", icon: BellIcon, current: false },
+ { name: "Releases", href: "releases", icon: CollectionIcon, current: false }
+ // {name: 'Regex Playground', href: 'regex-playground', icon: CogIcon, current: false}
+ // {name: 'Rules', href: 'rules', icon: ClipboardCheckIcon, current: false},
+];
+
+interface NavLinkProps {
+ item: NavTabType;
+ url: string;
+}
+
+function SubNavLink({ item, url }: NavLinkProps) {
+ const location = useLocation();
+ const { pathname } = location;
+
+ const splitLocation = pathname.split("/");
+
+ // we need to clean the / if it's a base root path
+ const too = item.href ? `${url}/${item.href}` : url;
+ return (
+
+
+ {item.name}
+
+ );
+}
+
+interface SidebarNavProps {
+ subNavigation: NavTabType[];
+ url: string;
+}
+
+function SidebarNav({ subNavigation, url }: SidebarNavProps) {
+ return (
+
+ );
}
export default function Settings() {
- const { url } = useRouteMatch();
- return (
-
-
+ const { url } = useRouteMatch();
+ return (
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
- )
+
+
+
+
+
+
+
+
+ );
}
diff --git a/web/src/screens/auth/login.tsx b/web/src/screens/auth/login.tsx
index d97eb43..7bbad22 100644
--- a/web/src/screens/auth/login.tsx
+++ b/web/src/screens/auth/login.tsx
@@ -34,11 +34,11 @@ export const Login = () => {
isLoggedIn: true
});
history.push("/");
- },
+ }
}
);
- const handleSubmit = (data: any) => mutation.mutate(data);
+ const handleSubmit = (data: LoginData) => mutation.mutate(data);
return (
@@ -75,4 +75,4 @@ export const Login = () => {
);
-}
+};
diff --git a/web/src/screens/auth/logout.tsx b/web/src/screens/auth/logout.tsx
index 48e5d1f..72ffdc9 100644
--- a/web/src/screens/auth/logout.tsx
+++ b/web/src/screens/auth/logout.tsx
@@ -29,4 +29,4 @@ export const Logout = () => {
Logged out
);
-}
+};
diff --git a/web/src/screens/auth/onboarding.tsx b/web/src/screens/auth/onboarding.tsx
index 60a45fe..e660d1a 100644
--- a/web/src/screens/auth/onboarding.tsx
+++ b/web/src/screens/auth/onboarding.tsx
@@ -27,7 +27,7 @@ export const Onboarding = () => {
if (values.password1 !== values.password2)
obj.password2 = "Passwords don't match!";
- return obj;
+ return obj;
};
const history = useHistory();
@@ -37,7 +37,7 @@ export const Onboarding = () => {
{
onSuccess: () => {
history.push("/login");
- },
+ }
}
);
@@ -81,5 +81,5 @@ export const Onboarding = () => {
);
-}
+};
diff --git a/web/src/screens/dashboard/ActivityTable.tsx b/web/src/screens/dashboard/ActivityTable.tsx
index 76783e9..3626859 100644
--- a/web/src/screens/dashboard/ActivityTable.tsx
+++ b/web/src/screens/dashboard/ActivityTable.tsx
@@ -5,7 +5,7 @@ import {
useFilters,
useGlobalFilter,
useSortBy,
- usePagination
+ usePagination, FilterProps, Column
} from "react-table";
import { APIClient } from "../../api/APIClient";
@@ -17,17 +17,17 @@ import * as DataTable from "../../components/data-table";
// This is a custom filter UI for selecting
// a unique option from a list
function SelectColumnFilter({
- column: { filterValue, setFilter, preFilteredRows, id, render },
-}: any) {
+ column: { filterValue, setFilter, preFilteredRows, id, render }
+}: FilterProps