mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 00:39:13 +00:00
chore: add eslint and cleanup (#118)
* refactor: modified existing react imports to conform with the recommended approach of not using the default export directly, since it will be deprecated in one of the future releases. see https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html for more info. note: react types don't require importing of react. refactor: cleaned up some of the imports * feat: added eslint and fixed all the errors/warning. eslint can now be invoked by running "npm run lint". chore: updated .gitignore not to include unnecessary artefacts. refactor: re-organized some of the imports. * refactor: converted remaining few typed functional components to proper prop argument structure. * fix: fixed small react-query invalidation bug for the FilterDetails component. Co-authored-by: anonymous <anonymous>
This commit is contained in:
parent
d1f08903d1
commit
fe06363530
29 changed files with 463 additions and 343 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -20,8 +20,14 @@ Thumbs.db
|
|||
|
||||
# Other
|
||||
.idea
|
||||
.yarn
|
||||
node_modules/
|
||||
web/build
|
||||
bin/
|
||||
log/
|
||||
dist/
|
||||
# If needed, package-lock.json shall be added
|
||||
# manually using an explicit git add command.
|
||||
package-lock.json
|
||||
# Ditto for yarn, except we're using npm.
|
||||
yarn.lock
|
||||
|
|
|
@ -26,13 +26,9 @@
|
|||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
"eject": "react-scripts eject",
|
||||
"lint": "esw src/ --ext \".ts,.tsx,.js,.jsx\" --color",
|
||||
"lint:watch": "npm run lint -- --watch"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
@ -57,9 +53,70 @@
|
|||
"@types/react-dom": "^17.0.0",
|
||||
"@types/react-router-dom": "^5.1.7",
|
||||
"@types/react-table": "^7.7.7",
|
||||
"@typescript-eslint/eslint-plugin": "^5.10.2",
|
||||
"@typescript-eslint/parser": "^5.10.2",
|
||||
"autoprefixer": "^10.4.2",
|
||||
"eslint": "^8.8.0",
|
||||
"eslint-plugin-import": "^2.25.4",
|
||||
"eslint-plugin-react": "^7.28.0",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"eslint-watch": "^8.0.0",
|
||||
"postcss": "^8.4.6",
|
||||
"tailwindcss": "^3.0.18",
|
||||
"typescript": "^4.1.2"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:import/errors",
|
||||
"plugin:import/warnings",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:react-hooks/recommended"
|
||||
],
|
||||
"plugins": [
|
||||
"react",
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
"react/jsx-uses-react": "off",
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off"
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 11,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"jsx": true,
|
||||
"experimentalObjectRestSpread": true
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
},
|
||||
"import/resolver": {
|
||||
"node": {
|
||||
"extensions": [
|
||||
".ts",
|
||||
".tsx",
|
||||
".js",
|
||||
".jsx"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"jquery": false
|
||||
},
|
||||
"globals": {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,4 +39,4 @@ export function App() {
|
|||
) : null}
|
||||
</QueryClientProvider>
|
||||
)
|
||||
};
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import {baseUrl, sseBaseUrl} from "../utils";
|
||||
|
||||
function baseClient(endpoint: string, method: string, { body, ...customConfig}: any = {}) {
|
||||
let baseURL = baseUrl()
|
||||
const baseURL = baseUrl()
|
||||
|
||||
const headers = {'content-type': 'application/json'}
|
||||
const config = {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import React from "react";
|
||||
import { Field } from "formik";
|
||||
|
||||
interface ErrorFieldProps {
|
||||
|
@ -7,7 +6,7 @@ interface ErrorFieldProps {
|
|||
subscribe?: any;
|
||||
}
|
||||
|
||||
const ErrorField: React.FC<ErrorFieldProps> = ({ name, classNames }) => (
|
||||
const ErrorField = ({ name, classNames }: ErrorFieldProps) => (
|
||||
<Field name={name} subscribe={{ touched: true, error: true }}>
|
||||
{({ meta: { touched, error } }: any) =>
|
||||
touched && error ? <span className={classNames}>{error}</span> : null
|
||||
|
@ -21,7 +20,11 @@ interface CheckboxFieldProps {
|
|||
sublabel?: string;
|
||||
}
|
||||
|
||||
const CheckboxField: React.FC<CheckboxFieldProps> = ({ name, label, sublabel }) => (
|
||||
const CheckboxField = ({
|
||||
name,
|
||||
label,
|
||||
sublabel
|
||||
}: CheckboxFieldProps) => (
|
||||
<div className="relative flex items-start">
|
||||
<div className="flex items-center h-5">
|
||||
<Field
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import React from "react";
|
||||
import { Field } from "formik";
|
||||
import { classNames } from "../../utils";
|
||||
import { EyeIcon, EyeOffIcon } from "@heroicons/react/solid";
|
||||
|
@ -11,11 +10,16 @@ interface TextFieldProps {
|
|||
label?: string;
|
||||
placeholder?: string;
|
||||
columns?: COL_WIDTHS;
|
||||
className?: string;
|
||||
autoComplete?: string;
|
||||
}
|
||||
|
||||
const TextField: React.FC<TextFieldProps> = ({ name, label, placeholder, columns, className, autoComplete }) => (
|
||||
export const TextField = ({
|
||||
name,
|
||||
label,
|
||||
placeholder,
|
||||
columns,
|
||||
autoComplete
|
||||
}: TextFieldProps) => (
|
||||
<div
|
||||
className={classNames(
|
||||
columns ? `col-span-${columns}` : "col-span-12"
|
||||
|
@ -55,14 +59,22 @@ interface PasswordFieldProps {
|
|||
label?: string;
|
||||
placeholder?: string;
|
||||
columns?: COL_WIDTHS;
|
||||
className?: string;
|
||||
autoComplete?: string;
|
||||
defaultValue?: string;
|
||||
help?: string;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
const PasswordField: React.FC<PasswordFieldProps> = ({ name, label, placeholder, defaultValue, columns, className, autoComplete, help, required }) => {
|
||||
export const PasswordField = ({
|
||||
name,
|
||||
label,
|
||||
placeholder,
|
||||
defaultValue,
|
||||
columns,
|
||||
autoComplete,
|
||||
help,
|
||||
required
|
||||
}: PasswordFieldProps) => {
|
||||
const [isVisible, toggleVisibility] = useToggle(false)
|
||||
|
||||
return (
|
||||
|
@ -113,17 +125,13 @@ interface NumberFieldProps {
|
|||
name: string;
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
const NumberField: React.FC<NumberFieldProps> = ({
|
||||
export const NumberField = ({
|
||||
name,
|
||||
label,
|
||||
placeholder,
|
||||
required,
|
||||
className,
|
||||
}) => (
|
||||
placeholder
|
||||
}: NumberFieldProps) => (
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<label htmlFor={name} className="block text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide">
|
||||
{label}
|
||||
|
@ -155,5 +163,3 @@ const NumberField: React.FC<NumberFieldProps> = ({
|
|||
</Field>
|
||||
</div>
|
||||
);
|
||||
|
||||
export { TextField, PasswordField, NumberField };
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from "react";
|
||||
import { Field, FieldProps } from "formik";
|
||||
import { Field } from "formik";
|
||||
import type { FieldProps } from "formik";
|
||||
import { classNames } from "../../utils";
|
||||
import { useToggle } from "../../hooks/hooks";
|
||||
import { EyeIcon, EyeOffIcon } from "@heroicons/react/solid";
|
||||
|
@ -12,12 +12,19 @@ interface TextFieldWideProps {
|
|||
help?: string;
|
||||
placeholder?: string;
|
||||
defaultValue?: string;
|
||||
className?: string;
|
||||
required?: boolean;
|
||||
hidden?: boolean;
|
||||
}
|
||||
|
||||
const TextFieldWide: React.FC<TextFieldWideProps> = ({ name, label, help, placeholder, defaultValue, required, hidden, className }) => (
|
||||
export const TextFieldWide = ({
|
||||
name,
|
||||
label,
|
||||
help,
|
||||
placeholder,
|
||||
defaultValue,
|
||||
required,
|
||||
hidden
|
||||
}: TextFieldWideProps) => (
|
||||
<div hidden={hidden} className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
|
||||
<div>
|
||||
<label htmlFor={name} className="block text-sm font-medium text-gray-900 dark:text-white sm:mt-px sm:pt-2">
|
||||
|
@ -56,7 +63,14 @@ interface PasswordFieldWideProps {
|
|||
required?: boolean;
|
||||
}
|
||||
|
||||
function PasswordFieldWide({ name, label, placeholder, defaultValue, help, required }: PasswordFieldWideProps) {
|
||||
export const PasswordFieldWide = ({
|
||||
name,
|
||||
label,
|
||||
placeholder,
|
||||
defaultValue,
|
||||
help,
|
||||
required
|
||||
}: PasswordFieldWideProps) => {
|
||||
const [isVisible, toggleVisibility] = useToggle(false)
|
||||
|
||||
return (
|
||||
|
@ -103,21 +117,17 @@ interface NumberFieldWideProps {
|
|||
help?: string;
|
||||
placeholder?: string;
|
||||
defaultValue?: number;
|
||||
className?: string;
|
||||
required?: boolean;
|
||||
hidden?: boolean;
|
||||
}
|
||||
|
||||
const NumberFieldWide: React.FC<NumberFieldWideProps> = ({
|
||||
export const NumberFieldWide = ({
|
||||
name,
|
||||
label,
|
||||
placeholder,
|
||||
help,
|
||||
defaultValue,
|
||||
required,
|
||||
hidden,
|
||||
className,
|
||||
}) => (
|
||||
required
|
||||
}: NumberFieldWideProps) => (
|
||||
<div className="px-4 space-y-1 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
|
||||
<div>
|
||||
<label
|
||||
|
@ -165,7 +175,12 @@ interface SwitchGroupWideProps {
|
|||
className?: string;
|
||||
}
|
||||
|
||||
const SwitchGroupWide: React.FC<SwitchGroupWideProps> = ({ name, label, description, defaultValue }) => (
|
||||
export const SwitchGroupWide = ({
|
||||
name,
|
||||
label,
|
||||
description,
|
||||
defaultValue
|
||||
}: SwitchGroupWideProps) => (
|
||||
<ul className="mt-2 divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<Switch.Group as="li" className="py-4 flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
|
@ -214,4 +229,3 @@ const SwitchGroupWide: React.FC<SwitchGroupWideProps> = ({ name, label, descript
|
|||
</ul>
|
||||
)
|
||||
|
||||
export { NumberFieldWide, TextFieldWide, PasswordFieldWide, SwitchGroupWide };
|
||||
|
|
|
@ -9,17 +9,15 @@ interface MultiSelectProps {
|
|||
label?: string;
|
||||
options?: [] | any;
|
||||
name: string;
|
||||
className?: string;
|
||||
columns?: COL_WIDTHS;
|
||||
}
|
||||
|
||||
const MultiSelect: React.FC<MultiSelectProps> = ({
|
||||
export const MultiSelect = ({
|
||||
name,
|
||||
label,
|
||||
options,
|
||||
className,
|
||||
columns,
|
||||
}) => (
|
||||
}: MultiSelectProps) => (
|
||||
<div
|
||||
className={classNames(
|
||||
columns ? `col-span-${columns}` : "col-span-12"
|
||||
|
@ -44,9 +42,8 @@ const MultiSelect: React.FC<MultiSelectProps> = ({
|
|||
labelledBy={name}
|
||||
value={field.value && field.value.map((item: any) => options.find((o: any) => o.value === item))}
|
||||
onChange={(values: any) => {
|
||||
let am = values && values.map((i: any) => i.value)
|
||||
|
||||
setFieldValue(field.name, am)
|
||||
const am = values && values.map((i: any) => i.value);
|
||||
setFieldValue(field.name, am);
|
||||
}}
|
||||
className="dark:bg-gray-700 dark"
|
||||
/>
|
||||
|
@ -55,13 +52,12 @@ const MultiSelect: React.FC<MultiSelectProps> = ({
|
|||
</div>
|
||||
);
|
||||
|
||||
const IndexerMultiSelect: React.FC<MultiSelectProps> = ({
|
||||
export const IndexerMultiSelect = ({
|
||||
name,
|
||||
label,
|
||||
options,
|
||||
className,
|
||||
columns,
|
||||
}) => (
|
||||
}: MultiSelectProps) => (
|
||||
<div
|
||||
className={classNames(
|
||||
columns ? `col-span-${columns}` : "col-span-12"
|
||||
|
@ -86,9 +82,8 @@ const IndexerMultiSelect: React.FC<MultiSelectProps> = ({
|
|||
labelledBy={name}
|
||||
value={field.value && field.value.map((item: any) => options.find((o: any) => o.value?.id === item.id))}
|
||||
onChange={(values: any) => {
|
||||
let am = values && values.map((i: any) => i.value)
|
||||
|
||||
setFieldValue(field.name, am)
|
||||
const am = values && values.map((i: any) => i.value);
|
||||
setFieldValue(field.name, am);
|
||||
}}
|
||||
className="dark:bg-gray-700 dark"
|
||||
/>
|
||||
|
@ -103,8 +98,10 @@ interface DownloadClientSelectProps {
|
|||
clients: DownloadClient[];
|
||||
}
|
||||
|
||||
export default function DownloadClientSelect({
|
||||
name, action, clients,
|
||||
export function DownloadClientSelect({
|
||||
name,
|
||||
action,
|
||||
clients
|
||||
}: DownloadClientSelectProps) {
|
||||
return (
|
||||
<div className="col-span-6 sm:col-span-6">
|
||||
|
@ -212,7 +209,12 @@ interface SelectFieldProps {
|
|||
options: SelectFieldOption[];
|
||||
}
|
||||
|
||||
function Select({ name, label, optionDefaultText, options }: SelectFieldProps) {
|
||||
export const Select = ({
|
||||
name,
|
||||
label,
|
||||
optionDefaultText,
|
||||
options
|
||||
}: SelectFieldProps) => {
|
||||
return (
|
||||
<div className="col-span-6">
|
||||
<Field name={name} type="select">
|
||||
|
@ -309,7 +311,12 @@ function Select({ name, label, optionDefaultText, options }: SelectFieldProps) {
|
|||
);
|
||||
}
|
||||
|
||||
function SelectWide({ name, label, optionDefaultText, options }: SelectFieldProps) {
|
||||
export const SelectWide = ({
|
||||
name,
|
||||
label,
|
||||
optionDefaultText,
|
||||
options
|
||||
}: SelectFieldProps) => {
|
||||
return (
|
||||
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
|
||||
|
||||
|
@ -409,5 +416,3 @@ function SelectWide({ name, label, optionDefaultText, options }: SelectFieldProp
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { MultiSelect, Select, SelectWide, DownloadClientSelect, IndexerMultiSelect }
|
|
@ -1,6 +1,12 @@
|
|||
import React, { InputHTMLAttributes } from 'react'
|
||||
import { Switch as HeadlessSwitch } from '@headlessui/react'
|
||||
import { FieldInputProps, FieldMetaProps, FieldProps, FormikProps, FormikValues, Field } from 'formik'
|
||||
import { Field } from "formik";
|
||||
import type {
|
||||
FieldInputProps,
|
||||
FieldMetaProps,
|
||||
FieldProps,
|
||||
FormikProps,
|
||||
FormikValues
|
||||
} from "formik";
|
||||
import { Switch as HeadlessSwitch } from "@headlessui/react";
|
||||
import { classNames } from "../../utils";
|
||||
|
||||
type SwitchProps<V = any> = {
|
||||
|
@ -13,14 +19,14 @@ type SwitchProps<V = any> = {
|
|||
meta?: FieldMetaProps<V>
|
||||
}
|
||||
|
||||
export const Switch: React.FC<SwitchProps> = ({
|
||||
export const Switch = ({
|
||||
label,
|
||||
checked: $checked,
|
||||
disabled = false,
|
||||
onChange: $onChange,
|
||||
field,
|
||||
form,
|
||||
}) => {
|
||||
}: SwitchProps) => {
|
||||
const checked = field?.checked ?? $checked
|
||||
|
||||
return (
|
||||
|
@ -55,19 +61,22 @@ export const Switch: React.FC<SwitchProps> = ({
|
|||
)
|
||||
}
|
||||
|
||||
export type SwitchFormikProps = SwitchProps & FieldProps & InputHTMLAttributes<HTMLInputElement>
|
||||
export type SwitchFormikProps = SwitchProps & FieldProps & React.InputHTMLAttributes<HTMLInputElement>;
|
||||
|
||||
export const SwitchFormik: React.FC<SwitchProps> = args => <Switch {...args} />
|
||||
export const SwitchFormik = (props: SwitchProps) => <Switch {...props} />
|
||||
|
||||
interface SwitchGroupProps {
|
||||
name: string;
|
||||
label?: string;
|
||||
description?: string;
|
||||
defaultValue?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const SwitchGroup: React.FC<SwitchGroupProps> = ({ name, label, description, defaultValue }) => (
|
||||
const SwitchGroup = ({
|
||||
name,
|
||||
label,
|
||||
description
|
||||
}: SwitchGroupProps) => (
|
||||
<ul className="mt-2 divide-y divide-gray-200">
|
||||
<HeadlessSwitch.Group as="li" className="py-4 flex items-center justify-between">
|
||||
{label && <div className="flex flex-col">
|
||||
|
|
2
web/src/domain/react-table-config.d.ts
vendored
2
web/src/domain/react-table-config.d.ts
vendored
|
@ -108,7 +108,7 @@ declare module 'react-table' {
|
|||
UseResizeColumnsColumnProps<D>,
|
||||
UseSortByColumnProps<D> {}
|
||||
|
||||
export interface Cell<D extends Record<string, unknown> = Record<string, unknown>, V = any>
|
||||
export interface Cell<D extends Record<string, unknown> = Record<string, unknown>>
|
||||
extends UseGroupByCellProps<D>,
|
||||
UseRowStateCellProps<D> {}
|
||||
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
import { Fragment, useEffect } from "react";
|
||||
import { Fragment } from "react";
|
||||
import { useMutation } from "react-query";
|
||||
import { queryClient } from "../../App";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { XIcon } from "@heroicons/react/solid";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import DEBUG from "../../components/debug";
|
||||
import APIClient from "../../api/APIClient";
|
||||
import { Field, Form, Formik } from "formik";
|
||||
import type { FieldProps } from "formik";
|
||||
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { queryClient } from "../../App";
|
||||
import APIClient from "../../api/APIClient";
|
||||
import DEBUG from "../../components/debug";
|
||||
import Toast from '../../components/notifications/Toast';
|
||||
import { Field, FieldProps, Form, Formik } from "formik";
|
||||
|
||||
function FilterAddForm({ isOpen, toggle }: any) {
|
||||
const mutation = useMutation((filter: Filter) => APIClient.filters.create(filter), {
|
||||
|
@ -20,23 +21,8 @@ function FilterAddForm({ isOpen, toggle }: any) {
|
|||
}
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
// console.log("render add action form")
|
||||
}, []);
|
||||
|
||||
const handleSubmit = (data: any) => {
|
||||
mutation.mutate(data)
|
||||
}
|
||||
|
||||
const validate = (values: any) => {
|
||||
const errors = {} as any;
|
||||
|
||||
if (!values.name) {
|
||||
errors.name = "Required";
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
const handleSubmit = (data: any) => mutation.mutate(data);
|
||||
const validate = (values: any) => values.name ? {} : { name: "Required" };
|
||||
|
||||
return (
|
||||
<Transition.Root show={isOpen} as={Fragment}>
|
||||
|
|
|
@ -300,7 +300,7 @@ export function DownloadClientAddForm({ isOpen, toggle }: any) {
|
|||
});
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
onError: () => {
|
||||
console.log('not added')
|
||||
setIsTesting(false);
|
||||
setIsErrorTest(true);
|
||||
|
@ -319,7 +319,7 @@ export function DownloadClientAddForm({ isOpen, toggle }: any) {
|
|||
testClientMutation.mutate(data);
|
||||
};
|
||||
|
||||
let initialValues: InitialValues = {
|
||||
const initialValues: InitialValues = {
|
||||
name: "",
|
||||
type: "QBITTORRENT",
|
||||
enabled: true,
|
||||
|
@ -480,7 +480,7 @@ export function DownloadClientUpdateForm({ client, isOpen, toggle }: any) {
|
|||
});
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
onError: () => {
|
||||
setIsTesting(false);
|
||||
setIsErrorTest(true);
|
||||
sleep(2500).then(() => {
|
||||
|
@ -505,7 +505,7 @@ export function DownloadClientUpdateForm({ client, isOpen, toggle }: any) {
|
|||
testClientMutation.mutate(data);
|
||||
};
|
||||
|
||||
let initialValues = {
|
||||
const initialValues = {
|
||||
id: client.id,
|
||||
name: client.name,
|
||||
type: client.type,
|
||||
|
|
|
@ -1,18 +1,24 @@
|
|||
import { Fragment } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { useMutation, useQuery } from "react-query";
|
||||
import { sleep } from "../../utils";
|
||||
import Select, { components } from "react-select";
|
||||
import { Field, Form, Formik } from "formik";
|
||||
import type { FieldProps } from "formik";
|
||||
|
||||
import { XIcon } from "@heroicons/react/solid";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { Field, FieldProps, Form, Formik } from "formik";
|
||||
import DEBUG from "../../components/debug";
|
||||
import Select, { components } from "react-select";
|
||||
import { queryClient } from "../../App";
|
||||
import APIClient from "../../api/APIClient";
|
||||
import { TextFieldWide, PasswordFieldWide, SwitchGroupWide } from "../../components/inputs/input_wide";
|
||||
|
||||
import { toast } from 'react-hot-toast'
|
||||
import Toast from '../../components/notifications/Toast';
|
||||
import { sleep } from "../../utils";
|
||||
import { queryClient } from "../../App";
|
||||
import DEBUG from "../../components/debug";
|
||||
import APIClient from "../../api/APIClient";
|
||||
import {
|
||||
TextFieldWide,
|
||||
PasswordFieldWide,
|
||||
SwitchGroupWide
|
||||
} from "../../components/inputs/input_wide";
|
||||
import { SlideOver } from "../../components/panels";
|
||||
import Toast from '../../components/notifications/Toast';
|
||||
|
||||
const Input = (props: any) => {
|
||||
return (
|
||||
|
@ -67,28 +73,19 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
|
|||
}
|
||||
})
|
||||
|
||||
const ircMutation = useMutation((network: Network) => APIClient.irc.createNetwork(network), {
|
||||
onSuccess: (data) => {
|
||||
// console.log("irc mutation: ", data);
|
||||
|
||||
// queryClient.invalidateQueries(['networks']);
|
||||
// sleep(1500)
|
||||
|
||||
// toggle()
|
||||
}
|
||||
})
|
||||
const ircMutation = useMutation(
|
||||
(network: Network) => APIClient.irc.createNetwork(network)
|
||||
);
|
||||
|
||||
const onSubmit = (formData: any) => {
|
||||
let ind = data && data.find(i => i.identifier === formData.identifier)
|
||||
const ind = data && data.find(i => i.identifier === formData.identifier);
|
||||
if (!ind)
|
||||
return;
|
||||
|
||||
if (!ind) {
|
||||
return
|
||||
}
|
||||
|
||||
let channels: Channel[] = []
|
||||
const channels: Channel[] = [];
|
||||
if (ind.irc.channels.length) {
|
||||
ind.irc.channels.forEach(element => {
|
||||
channels.push({ name: element, password: "" })
|
||||
channels.push({ name: element, password: "" });
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -105,17 +102,13 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
|
|||
}
|
||||
|
||||
mutation.mutate(formData, {
|
||||
onSuccess: (data) => {
|
||||
// create irc
|
||||
ircMutation.mutate(network)
|
||||
}
|
||||
})
|
||||
onSuccess: () => ircMutation.mutate(network)
|
||||
});
|
||||
};
|
||||
|
||||
const renderSettingFields = (indexer: string) => {
|
||||
if (indexer !== "") {
|
||||
let ind = data && data.find(i => i.identifier === indexer)
|
||||
|
||||
const ind = data && data.find(i => i.identifier === indexer);
|
||||
return (
|
||||
<div key="opt">
|
||||
{ind && ind.settings && ind.settings.map((f: any, idx: number) => {
|
||||
|
@ -140,10 +133,8 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
|
|||
}
|
||||
|
||||
const renderIrcSettingFields = (indexer: string) => {
|
||||
|
||||
if (indexer !== "") {
|
||||
let ind = data && data.find(i => i.identifier === indexer)
|
||||
|
||||
const ind = data && data.find(i => i.identifier === indexer);
|
||||
return (
|
||||
<Fragment>
|
||||
{ind && ind.irc && ind.irc.settings && (
|
||||
|
@ -366,7 +357,7 @@ export function IndexerUpdateForm({ isOpen, toggle, indexer }: UpdateProps) {
|
|||
)
|
||||
}
|
||||
|
||||
let initialValues = {
|
||||
const initialValues = {
|
||||
id: indexer.id,
|
||||
name: indexer.name,
|
||||
enabled: indexer.enabled,
|
||||
|
@ -390,7 +381,7 @@ export function IndexerUpdateForm({ isOpen, toggle, indexer }: UpdateProps) {
|
|||
onSubmit={onSubmit}
|
||||
initialValues={initialValues}
|
||||
>
|
||||
{({ values }: any) => (
|
||||
{() => (
|
||||
<div className="py-6 space-y-6 sm:py-0 sm:space-y-0 divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<div className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
|
||||
<div>
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
import { useMutation } from "react-query";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { XIcon } from "@heroicons/react/solid";
|
||||
import { queryClient } from "../../App";
|
||||
import { Field, FieldArray } from "formik";
|
||||
import type { FieldProps } from "formik";
|
||||
|
||||
import { Field, FieldArray, FieldProps } from "formik";
|
||||
import { queryClient } from "../../App";
|
||||
import APIClient from "../../api/APIClient";
|
||||
|
||||
import { TextFieldWide, PasswordFieldWide, SwitchGroupWide, NumberFieldWide } from "../../components/inputs/input_wide";
|
||||
|
||||
import { toast } from 'react-hot-toast';
|
||||
import Toast from '../../components/notifications/Toast';
|
||||
import {
|
||||
TextFieldWide,
|
||||
PasswordFieldWide,
|
||||
SwitchGroupWide,
|
||||
NumberFieldWide
|
||||
} from "../../components/inputs/input_wide";
|
||||
import { SlideOver } from "../../components/panels";
|
||||
import Toast from '../../components/notifications/Toast';
|
||||
|
||||
function ChannelsFieldArray({ values }: any) {
|
||||
return (
|
||||
|
@ -79,7 +84,7 @@ function ChannelsFieldArray({ values }: any) {
|
|||
|
||||
export function IrcNetworkAddForm({ isOpen, toggle }: any) {
|
||||
const mutation = useMutation((network: Network) => APIClient.irc.createNetwork(network), {
|
||||
onSuccess: (data) => {
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(['networks']);
|
||||
toast.custom((t) => <Toast type="success" body="IRC Network added" t={t} />)
|
||||
toggle()
|
||||
|
@ -92,11 +97,15 @@ export function IrcNetworkAddForm({ isOpen, toggle }: any) {
|
|||
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.
|
||||
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("formated", data)
|
||||
const 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("formated", data);
|
||||
|
||||
mutation.mutate(data)
|
||||
mutation.mutate(data);
|
||||
};
|
||||
|
||||
const validate = (values: any) => {
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import React from "react";
|
||||
import { useState, useCallback } from "react";
|
||||
|
||||
export function useToggle(initialValue = false): [boolean, () => void] {
|
||||
const [value, setValue] = useState(initialValue);
|
||||
const toggle = useCallback(() => setValue(v => !v), []);
|
||||
|
||||
export function useToggle(initialValue: boolean = false): [boolean, () => void] {
|
||||
const [value, setValue] = React.useState<boolean>(initialValue);
|
||||
const toggle = React.useCallback(() => {
|
||||
setValue(v => !v);
|
||||
}, []);
|
||||
return [value, toggle];
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import { StrictMode } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import "./index.css";
|
||||
|
||||
import { App } from "./App";
|
||||
|
||||
import { InitializeGlobalContext } from "./utils/Context";
|
||||
|
||||
declare global {
|
||||
|
@ -16,8 +15,8 @@ window.APP = window.APP || {};
|
|||
InitializeGlobalContext();
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
</StrictMode>,
|
||||
document.getElementById("root")
|
||||
);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ReportHandler } from 'web-vitals';
|
||||
import type { ReportHandler } from "web-vitals";
|
||||
|
||||
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
import formatDistanceToNowStrict from 'date-fns/formatDistanceToNowStrict'
|
||||
import React from 'react'
|
||||
import { useTable, useFilters, useGlobalFilter, useSortBy, usePagination } from 'react-table'
|
||||
import APIClient from '../api/APIClient'
|
||||
import { useQuery } from 'react-query'
|
||||
import { EmptyListState } from '../components/emptystates'
|
||||
import { ReleaseStatusCell } from './Releases'
|
||||
import * as React from "react";
|
||||
import { useQuery } from "react-query";
|
||||
import formatDistanceToNowStrict from "date-fns/formatDistanceToNowStrict";
|
||||
import {
|
||||
useTable,
|
||||
useFilters,
|
||||
useGlobalFilter,
|
||||
useSortBy,
|
||||
usePagination
|
||||
} from "react-table";
|
||||
|
||||
import APIClient from "../api/APIClient";
|
||||
import { EmptyListState } from "../components/emptystates";
|
||||
import { ReleaseStatusCell } from "./Releases";
|
||||
|
||||
export function Dashboard() {
|
||||
return (
|
||||
|
@ -366,7 +373,6 @@ export function SelectColumnFilter({
|
|||
// };
|
||||
|
||||
export function StatusPill({ value }: any) {
|
||||
|
||||
const statusMap: any = {
|
||||
"FILTER_APPROVED": <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold uppercase bg-blue-100 text-blue-800 ">Approved</span>,
|
||||
"FILTER_REJECTED": <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold uppercase bg-red-100 text-red-800">Rejected</span>,
|
||||
|
@ -376,13 +382,10 @@ export function StatusPill({ value }: any) {
|
|||
"MIXED": <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold uppercase bg-yellow-100 text-yellow-800">MIXED</span>,
|
||||
}
|
||||
|
||||
return (
|
||||
statusMap[value]
|
||||
);
|
||||
};
|
||||
|
||||
export function AgeCell({ value, column, row }: any) {
|
||||
return statusMap[value];
|
||||
}
|
||||
|
||||
export function AgeCell({ value }: any) {
|
||||
const formatDate = formatDistanceToNowStrict(
|
||||
new Date(value),
|
||||
{ addSuffix: true }
|
||||
|
@ -393,13 +396,13 @@ export function AgeCell({ value, column, row }: any) {
|
|||
)
|
||||
}
|
||||
|
||||
export function ReleaseCell({ value, column, row }: any) {
|
||||
export function ReleaseCell({ value }: any) {
|
||||
return (
|
||||
<div className="text-sm font-medium text-gray-900 dark:text-gray-300">{value}</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function IndexerCell({ value, column, row }: any) {
|
||||
export function IndexerCell({ value }: any) {
|
||||
return (
|
||||
<div className="text-sm font-medium text-gray-900 dark:text-gray-500" title={value}>{value}</div>
|
||||
)
|
||||
|
@ -464,55 +467,67 @@ function Table({ columns, data }: any) {
|
|||
<div className="overflow-hidden bg-white shadow dark:bg-gray-800 sm:rounded-lg">
|
||||
<table {...getTableProps()} className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead className="bg-gray-50 dark:bg-gray-800">
|
||||
{headerGroups.map((headerGroup: { getHeaderGroupProps: () => JSX.IntrinsicAttributes & React.ClassAttributes<HTMLTableRowElement> & React.HTMLAttributes<HTMLTableRowElement>; headers: any[] }) => (
|
||||
<tr {...headerGroup.getHeaderGroupProps()}>
|
||||
{headerGroup.headers.map(column => (
|
||||
// Add the sorting props to control sorting. For this example
|
||||
// we can add them into the header props
|
||||
<th
|
||||
scope="col"
|
||||
className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase group"
|
||||
{...column.getHeaderProps(column.getSortByToggleProps())}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
{column.render('Header')}
|
||||
{/* Add a sort direction indicator */}
|
||||
<span>
|
||||
{column.isSorted
|
||||
? column.isSortedDesc
|
||||
? <SortDownIcon className="w-4 h-4 text-gray-400" />
|
||||
: <SortUpIcon className="w-4 h-4 text-gray-400" />
|
||||
: (
|
||||
<SortIcon className="w-4 h-4 text-gray-400 opacity-0 group-hover:opacity-100" />
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
{headerGroups.map((headerGroup) => {
|
||||
const { key: rowKey, ...rowRest } = headerGroup.getHeaderGroupProps();
|
||||
return (
|
||||
<tr key={rowKey} {...rowRest}>
|
||||
{headerGroup.headers.map((column) => {
|
||||
const { key: columnKey, ...columnRest } = column.getHeaderProps(column.getSortByToggleProps());
|
||||
return (
|
||||
// Add the sorting props to control sorting. For this example
|
||||
// we can add them into the header props
|
||||
<th
|
||||
key={`${rowKey}-${columnKey}`}
|
||||
scope="col"
|
||||
className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase group"
|
||||
{...columnRest}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
{column.render('Header')}
|
||||
{/* Add a sort direction indicator */}
|
||||
<span>
|
||||
{column.isSorted ? (
|
||||
column.isSortedDesc ? (
|
||||
<SortDownIcon className="w-4 h-4 text-gray-400" />
|
||||
) : (
|
||||
<SortUpIcon className="w-4 h-4 text-gray-400" />
|
||||
)
|
||||
) : (
|
||||
<SortIcon className="w-4 h-4 text-gray-400 opacity-0 group-hover:opacity-100" />
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</th>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</thead>
|
||||
<tbody
|
||||
{...getTableBodyProps()}
|
||||
className="divide-y divide-gray-200 dark:divide-gray-700"
|
||||
>
|
||||
{page.map((row: any, i: any) => { // new
|
||||
prepareRow(row)
|
||||
{page.map((row: any) => {
|
||||
prepareRow(row);
|
||||
const { key: bodyRowKey, ...bodyRowRest } = row.getRowProps();
|
||||
return (
|
||||
<tr {...row.getRowProps()}>
|
||||
<tr key={bodyRowKey} {...bodyRowRest}>
|
||||
{row.cells.map((cell: any) => {
|
||||
return (
|
||||
<td
|
||||
{...cell.getCellProps()}
|
||||
className="px-6 py-4 whitespace-nowrap"
|
||||
role="cell"
|
||||
>
|
||||
{cell.column.Cell.name === "defaultRenderer"
|
||||
? <div className="text-sm text-gray-500">{cell.render('Cell')}</div>
|
||||
: cell.render('Cell')
|
||||
}
|
||||
</td>
|
||||
)
|
||||
const { key: cellRowKey, ...cellRowRest } = cell.getCellProps();
|
||||
return (
|
||||
<td
|
||||
key={cellRowKey}
|
||||
className="px-6 py-4 whitespace-nowrap"
|
||||
role="cell"
|
||||
{...cellRowRest}
|
||||
>
|
||||
{cell.column.Cell.name === "defaultRenderer"
|
||||
? <div className="text-sm text-gray-500">{cell.render('Cell')}</div>
|
||||
: cell.render('Cell')
|
||||
}
|
||||
</td>
|
||||
)
|
||||
})}
|
||||
</tr>
|
||||
)
|
||||
|
|
|
@ -20,13 +20,12 @@ export default function Logs() {
|
|||
const es = APIClient.events.logs()
|
||||
|
||||
es.onmessage = (event) => {
|
||||
let d: LogEvent = JSON.parse(event.data)
|
||||
|
||||
setLogs(prevState => ([...prevState, d]))
|
||||
scrollToBottom()
|
||||
const d = JSON.parse(event.data) as LogEvent;
|
||||
setLogs(prevState => ([...prevState, d]));
|
||||
scrollToBottom();
|
||||
}
|
||||
return () => {
|
||||
es.close()
|
||||
es.close();
|
||||
}
|
||||
}, [setLogs]);
|
||||
|
||||
|
|
|
@ -1,13 +1,23 @@
|
|||
import { BanIcon, ExclamationCircleIcon } from "@heroicons/react/outline"
|
||||
import ClockIcon from "@heroicons/react/outline/ClockIcon"
|
||||
import { ChevronDoubleLeftIcon, ChevronLeftIcon, ChevronRightIcon, ChevronDoubleRightIcon, CheckIcon } from "@heroicons/react/solid"
|
||||
import { formatDistanceToNowStrict } from "date-fns"
|
||||
import React from "react"
|
||||
import { useQuery } from "react-query"
|
||||
import { useTable, useSortBy, usePagination } from "react-table"
|
||||
import APIClient from "../api/APIClient"
|
||||
import { EmptyListState } from "../components/emptystates"
|
||||
import { classNames, simplifyDate } from "../utils"
|
||||
import * as React from "react";
|
||||
import { useQuery } from "react-query";
|
||||
import { formatDistanceToNowStrict } from "date-fns";
|
||||
import { useTable, useSortBy, usePagination } from "react-table";
|
||||
import {
|
||||
ClockIcon,
|
||||
BanIcon,
|
||||
ExclamationCircleIcon
|
||||
} from "@heroicons/react/outline";
|
||||
import {
|
||||
ChevronDoubleLeftIcon,
|
||||
ChevronLeftIcon,
|
||||
ChevronRightIcon,
|
||||
ChevronDoubleRightIcon,
|
||||
CheckIcon
|
||||
} from "@heroicons/react/solid";
|
||||
|
||||
import APIClient from "../api/APIClient";
|
||||
import { EmptyListState } from "../components/emptystates";
|
||||
import { classNames, simplifyDate } from "../utils";
|
||||
|
||||
export function Releases() {
|
||||
return (
|
||||
|
@ -95,13 +105,11 @@ export function StatusPill({ value }: any) {
|
|||
"MIXED": <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold uppercase bg-yellow-100 text-yellow-800">MIXED</span>,
|
||||
}
|
||||
|
||||
return (
|
||||
statusMap[value]
|
||||
);
|
||||
};
|
||||
return statusMap[value];
|
||||
}
|
||||
|
||||
|
||||
export function AgeCell({ value, column, row }: any) {
|
||||
export function AgeCell({ value }: any) {
|
||||
|
||||
const formatDate = formatDistanceToNowStrict(
|
||||
new Date(value),
|
||||
|
@ -113,7 +121,7 @@ export function AgeCell({ value, column, row }: any) {
|
|||
)
|
||||
}
|
||||
|
||||
export function ReleaseCell({ value, column, row }: any) {
|
||||
export function ReleaseCell({ value }: any) {
|
||||
return (
|
||||
<div className="text-sm font-medium text-gray-900 dark:text-gray-300" title={value}>{value}</div>
|
||||
)
|
||||
|
@ -125,7 +133,7 @@ interface ReleaseStatusCellProps {
|
|||
row: any;
|
||||
}
|
||||
|
||||
export function ReleaseStatusCell({ value, column, row }: ReleaseStatusCellProps) {
|
||||
export function ReleaseStatusCell({ value }: ReleaseStatusCellProps) {
|
||||
const statusMap: any = {
|
||||
"PUSH_ERROR": <span className="mr-1 inline-flex items-center rounded text-xs font-semibold uppercase bg-pink-100 text-pink-800 hover:bg-pink-300 cursor-pointer">
|
||||
<ExclamationCircleIcon className="h-5 w-5" aria-hidden="true" />
|
||||
|
@ -147,7 +155,7 @@ export function ReleaseStatusCell({ value, column, row }: ReleaseStatusCellProps
|
|||
)
|
||||
}
|
||||
|
||||
export function IndexerCell({ value, column, row }: any) {
|
||||
export function IndexerCell({ value }: any) {
|
||||
return (
|
||||
<div className="text-sm font-medium text-gray-900 dark:text-gray-500" title={value}>{value}</div>
|
||||
)
|
||||
|
@ -317,58 +325,70 @@ function Table() {
|
|||
<div className="overflow-hidden bg-white shadow dark:bg-gray-800 sm:rounded-lg">
|
||||
<table {...getTableProps()} className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead className="bg-gray-50 dark:bg-gray-800">
|
||||
{headerGroups.map((headerGroup: { getHeaderGroupProps: () => JSX.IntrinsicAttributes & React.ClassAttributes<HTMLTableRowElement> & React.HTMLAttributes<HTMLTableRowElement>; headers: any[] }) => (
|
||||
<tr {...headerGroup.getHeaderGroupProps()}>
|
||||
{headerGroup.headers.map(column => (
|
||||
// Add the sorting props to control sorting. For this example
|
||||
// we can add them into the header props
|
||||
<th
|
||||
scope="col"
|
||||
className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase group"
|
||||
{...column.getHeaderProps(column.getSortByToggleProps())}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
{column.render('Header')}
|
||||
{/* Add a sort direction indicator */}
|
||||
<span>
|
||||
{column.isSorted
|
||||
? column.isSortedDesc
|
||||
? <SortDownIcon className="w-4 h-4 text-gray-400" />
|
||||
: <SortUpIcon className="w-4 h-4 text-gray-400" />
|
||||
: (
|
||||
<SortIcon className="w-4 h-4 text-gray-400 opacity-0 group-hover:opacity-100" />
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
{headerGroups.map((headerGroup) => {
|
||||
const { key: rowKey, ...rowRest } = headerGroup.getHeaderGroupProps();
|
||||
return (
|
||||
<tr key={rowKey} {...rowRest}>
|
||||
{headerGroup.headers.map((column) => {
|
||||
const { key: columnKey, ...columnRest } = column.getHeaderProps(column.getSortByToggleProps());
|
||||
return (
|
||||
// Add the sorting props to control sorting. For this example
|
||||
// we can add them into the header props
|
||||
<th
|
||||
key={`${rowKey}-${columnKey}`}
|
||||
scope="col"
|
||||
className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase group"
|
||||
{...columnRest}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
{column.render('Header')}
|
||||
{/* Add a sort direction indicator */}
|
||||
<span>
|
||||
{column.isSorted ? (
|
||||
column.isSortedDesc ? (
|
||||
<SortDownIcon className="w-4 h-4 text-gray-400" />
|
||||
) : (
|
||||
<SortUpIcon className="w-4 h-4 text-gray-400" />
|
||||
)
|
||||
) : (
|
||||
<SortIcon className="w-4 h-4 text-gray-400 opacity-0 group-hover:opacity-100" />
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</th>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</thead>
|
||||
<tbody
|
||||
{...getTableBodyProps()}
|
||||
className="divide-y divide-gray-200 dark:divide-gray-700"
|
||||
>
|
||||
{page.map((row: any, i: any) => { // new
|
||||
{page.map((row: any) => { // new
|
||||
prepareRow(row)
|
||||
const { key: bodyRowKey, ...bodyRowRest } = row.getRowProps();
|
||||
return (
|
||||
<tr {...row.getRowProps()}>
|
||||
<tr key={bodyRowKey} {...bodyRowRest}>
|
||||
{row.cells.map((cell: any) => {
|
||||
const { key: cellRowKey, ...cellRowRest } = cell.getCellProps();
|
||||
return (
|
||||
<td
|
||||
{...cell.getCellProps()}
|
||||
key={cellRowKey}
|
||||
className="px-6 py-4 whitespace-nowrap"
|
||||
role="cell"
|
||||
{...cellRowRest}
|
||||
>
|
||||
{cell.column.Cell.name === "defaultRenderer"
|
||||
? <div className="text-sm text-gray-500">{cell.render('Cell')}</div>
|
||||
: cell.render('Cell')
|
||||
}
|
||||
</td>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -21,14 +21,12 @@ const subNavigation = [
|
|||
|
||||
function SubNavLink({item, url}: any) {
|
||||
const location = useLocation();
|
||||
|
||||
const {pathname} = location;
|
||||
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
|
||||
|
||||
const too = item.href ? `${url}/${item.href}` : url
|
||||
return (
|
||||
<NavLink
|
||||
key={item.name}
|
||||
|
@ -62,8 +60,7 @@ function SidebarNav({subNavigation, url}: any) {
|
|||
}
|
||||
|
||||
export default function Settings() {
|
||||
let {url} = useRouteMatch();
|
||||
|
||||
const { url } = useRouteMatch();
|
||||
return (
|
||||
<main className="relative -mt-48">
|
||||
<header className="py-10">
|
||||
|
|
|
@ -13,7 +13,7 @@ function Logout() {
|
|||
|
||||
useEffect(
|
||||
() => {
|
||||
APIClient.auth.logout().then(r => {
|
||||
APIClient.auth.logout().then(() => {
|
||||
setAuthContext({ username: "", isLoggedIn: false });
|
||||
removeCookie("user_session");
|
||||
history.push('/login');
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import { Fragment, useRef } from "react";
|
||||
import { Dialog, Transition, Switch as SwitchBasic } from "@headlessui/react";
|
||||
import { ChevronDownIcon, ChevronRightIcon, } from '@heroicons/react/solid'
|
||||
import { EmptyListState } from "../../components/emptystates";
|
||||
|
||||
import { useMutation, useQuery } from "react-query";
|
||||
import {
|
||||
NavLink,
|
||||
Route,
|
||||
|
@ -12,23 +9,46 @@ import {
|
|||
useParams,
|
||||
useRouteMatch
|
||||
} from "react-router-dom";
|
||||
import { useToggle } from "../../hooks/hooks";
|
||||
import { useMutation, useQuery } from "react-query";
|
||||
import { queryClient } from "../../App";
|
||||
import { CONTAINER_OPTIONS, CODECS_OPTIONS, RESOLUTION_OPTIONS, SOURCES_OPTIONS, ActionTypeNameMap, ActionTypeOptions, HDR_OPTIONS, FORMATS_OPTIONS, SOURCES_MUSIC_OPTIONS, QUALITY_MUSIC_OPTIONS, RELEASE_TYPE_MUSIC_OPTIONS } from "../../domain/constants";
|
||||
|
||||
import DEBUG from "../../components/debug";
|
||||
import { TitleSubtitle } from "../../components/headings";
|
||||
import { buildPath, classNames } from "../../utils";
|
||||
import APIClient from "../../api/APIClient";
|
||||
|
||||
import { toast } from 'react-hot-toast'
|
||||
import Toast from '../../components/notifications/Toast';
|
||||
|
||||
import { toast } from "react-hot-toast";
|
||||
import { Field, FieldArray, Form, Formik } from "formik";
|
||||
import { Dialog, Transition, Switch as SwitchBasic } from "@headlessui/react";
|
||||
import { ChevronDownIcon, ChevronRightIcon, } from "@heroicons/react/solid";
|
||||
|
||||
|
||||
import {
|
||||
CONTAINER_OPTIONS,
|
||||
CODECS_OPTIONS,
|
||||
RESOLUTION_OPTIONS,
|
||||
SOURCES_OPTIONS,
|
||||
ActionTypeNameMap,
|
||||
ActionTypeOptions,
|
||||
HDR_OPTIONS,
|
||||
FORMATS_OPTIONS,
|
||||
SOURCES_MUSIC_OPTIONS,
|
||||
QUALITY_MUSIC_OPTIONS,
|
||||
RELEASE_TYPE_MUSIC_OPTIONS
|
||||
} from "../../domain/constants";
|
||||
import { queryClient } from "../../App";
|
||||
import APIClient from "../../api/APIClient";
|
||||
import { useToggle } from "../../hooks/hooks";
|
||||
import { buildPath, classNames } from "../../utils";
|
||||
|
||||
import {
|
||||
NumberField,
|
||||
TextField,
|
||||
SwitchGroup,
|
||||
Select,
|
||||
MultiSelect,
|
||||
DownloadClientSelect,
|
||||
IndexerMultiSelect,
|
||||
CheckboxField
|
||||
} from "../../components/inputs";
|
||||
import DEBUG from "../../components/debug";
|
||||
import Toast from "../../components/notifications/Toast";
|
||||
import { AlertWarning } from "../../components/alerts";
|
||||
import { DeleteModal } from "../../components/modals";
|
||||
import { NumberField, TextField, SwitchGroup, Select, MultiSelect, DownloadClientSelect, IndexerMultiSelect, CheckboxField } from "../../components/inputs";
|
||||
import { TitleSubtitle } from "../../components/headings";
|
||||
import { EmptyListState } from "../../components/emptystates";
|
||||
|
||||
const tabs = [
|
||||
{ name: 'General', href: '', current: true },
|
||||
|
@ -46,8 +66,7 @@ function TabNavLink({ item, url }: any) {
|
|||
const splitLocation = pathname.split("/");
|
||||
|
||||
// we need to clean the / if it's a base root path
|
||||
let too = item.href ? `${url}/${item.href}` : url
|
||||
|
||||
const too = item.href ? `${url}/${item.href}` : url
|
||||
return (
|
||||
<NavLink
|
||||
key={item.name}
|
||||
|
@ -64,7 +83,7 @@ function TabNavLink({ item, url }: any) {
|
|||
)
|
||||
}
|
||||
|
||||
const FormButtonsGroup = ({ values, deleteAction, reset, dirty }: any) => {
|
||||
const FormButtonsGroup = ({ values, deleteAction, reset }: any) => {
|
||||
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false);
|
||||
|
||||
const cancelModalButtonRef = useRef(null);
|
||||
|
@ -111,19 +130,19 @@ const FormButtonsGroup = ({ values, deleteAction, reset, dirty }: any) => {
|
|||
}
|
||||
|
||||
export default function FilterDetails() {
|
||||
let { url } = useRouteMatch();
|
||||
let history = useHistory();
|
||||
let { filterId }: any = useParams();
|
||||
const history = useHistory();
|
||||
const { url } = useRouteMatch();
|
||||
const { filterId } = useParams<{ filterId: string }>();
|
||||
|
||||
const { isLoading, data: filter } = useQuery<Filter, Error>(['filter', parseInt(filterId)], () => APIClient.filters.getByID(parseInt(filterId)),
|
||||
const { isLoading, data: filter } = useQuery<Filter, Error>(
|
||||
['filter', +filterId],
|
||||
() => APIClient.filters.getByID(parseInt(filterId)),
|
||||
{
|
||||
retry: false,
|
||||
refetchOnWindowFocus: false,
|
||||
onError: err => {
|
||||
history.push("./")
|
||||
}
|
||||
},
|
||||
)
|
||||
onError: () => history.push("./")
|
||||
}
|
||||
);
|
||||
|
||||
const updateMutation = useMutation((filter: Filter) => APIClient.filters.update(filter), {
|
||||
onSuccess: (filter) => {
|
||||
|
@ -160,11 +179,9 @@ export default function FilterDetails() {
|
|||
}
|
||||
|
||||
const handleMobileNav = (e: any, href: string) => {
|
||||
let s = history.location.pathname.split(/((?:\/.*?)?\/filters\/\d)/gi)
|
||||
|
||||
let p = buildPath(s[1], href)
|
||||
|
||||
history.push(p)
|
||||
const s = history.location.pathname.split(/((?:\/.*?)?\/filters\/\d)/gi);
|
||||
const p = buildPath(s[1], href);
|
||||
history.push(p);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -304,7 +321,7 @@ function General() {
|
|||
}
|
||||
)
|
||||
|
||||
let opts = indexers && indexers.length > 0 ? indexers.map(v => ({
|
||||
const opts = indexers && indexers.length > 0 ? indexers.map(v => ({
|
||||
label: v.name,
|
||||
value: {
|
||||
id: v.id,
|
||||
|
@ -581,7 +598,7 @@ function FilterActions({ filter, values }: FilterActionsProps) {
|
|||
}
|
||||
)
|
||||
|
||||
let newAction = {
|
||||
const newAction = {
|
||||
name: "new action",
|
||||
enabled: true,
|
||||
type: "TEST",
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
import { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { Switch } from "@headlessui/react";
|
||||
import { EmptyListState } from "../../components/emptystates";
|
||||
|
||||
import {
|
||||
Link,
|
||||
} from "react-router-dom";
|
||||
import { useToggle } from "../../hooks/hooks";
|
||||
import { useMutation, useQuery } from "react-query";
|
||||
|
||||
import { queryClient } from "../../App";
|
||||
import { classNames } from "../../utils";
|
||||
import { FilterAddForm } from "../../forms";
|
||||
import { useToggle } from "../../hooks/hooks";
|
||||
import APIClient from "../../api/APIClient";
|
||||
import Toast from "../../components/notifications/Toast";
|
||||
import toast from "react-hot-toast";
|
||||
import { queryClient } from "../../App";
|
||||
import { EmptyListState } from "../../components/emptystates";
|
||||
|
||||
export default function Filters() {
|
||||
const [createFilterIsOpen, toggleCreateFilter] = useToggle(false)
|
||||
|
@ -27,7 +25,8 @@ export default function Filters() {
|
|||
return null
|
||||
}
|
||||
|
||||
if (error) return (<p>'An error has occurred: '</p>)
|
||||
if (error)
|
||||
return (<p>An error has occurred: </p>);
|
||||
|
||||
return (
|
||||
<main className="-mt-48 ">
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
import React from "react";
|
||||
|
||||
|
||||
function ActionSettings() {
|
||||
// const [addClientIsOpen, toggleAddClient] = useToggle(false)
|
||||
|
||||
return (
|
||||
<div className="divide-y divide-gray-200 lg:col-span-9">
|
||||
|
||||
|
|
|
@ -58,7 +58,8 @@ function DownloadClientSettings() {
|
|||
refetchOnWindowFocus: false
|
||||
})
|
||||
|
||||
if (error) return (<p>'An error has occurred: '</p>);
|
||||
if (error)
|
||||
return (<p>An error has occurred: </p>);
|
||||
|
||||
return (
|
||||
<div className="divide-y divide-gray-200 lg:col-span-9">
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { useEffect } from "react";
|
||||
import { useToggle } from "../../hooks/hooks";
|
||||
import { useQuery } from "react-query";
|
||||
import { IndexerAddForm, IndexerUpdateForm } from "../../forms";
|
||||
|
@ -52,10 +51,8 @@ function IndexerSettings() {
|
|||
}
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
}, []);
|
||||
|
||||
if (error) return (<p>An error has occurred</p>)
|
||||
if (error)
|
||||
return (<p>An error has occurred</p>);
|
||||
|
||||
return (
|
||||
<div className="divide-y divide-gray-200 lg:col-span-9">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useRef, useState } from "react";
|
||||
import { useRef, useState } from "react";
|
||||
|
||||
export const RegexPlayground = () => {
|
||||
const regexRef = useRef<HTMLInputElement>(null);
|
||||
|
|
|
@ -25,14 +25,10 @@ export function baseUrl() {
|
|||
|
||||
// get sseBaseUrl for SSE
|
||||
export function sseBaseUrl() {
|
||||
let {origin} = window.location
|
||||
if (process.env.NODE_ENV === "development")
|
||||
return `http://localhost:8989/`;
|
||||
|
||||
let env = process.env.NODE_ENV
|
||||
if (env === "development") {
|
||||
return `http://localhost:8989/`
|
||||
}
|
||||
|
||||
return `${origin}${baseUrl()}`
|
||||
return `${window.location.origin}${baseUrl()}`;
|
||||
}
|
||||
|
||||
export function buildPath(...args: string[]): string {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue