mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 08:49:13 +00:00
refactor(web): replace pkg react-query with tanstack/react-query (#868)
* refactor: move to tanstack/react-query and fix cache * refactor(releases): move to tanstack/react-query * refactor(logs): move to tanstack/react-query * refactor(base): move to tanstack/react-query * refactor(base): move to tanstack/react-query * refactor(dashboard): move to tanstack/react-query * refactor(auth): move to tanstack/react-query * refactor(filters): move to tanstack/react-query * refactor(settings): move to tanstack/react-query * chore(pkg): add tanstack/react-query * refactor(filters): move to tanstack/react-query * refactor: move to tanstack/react-query * refactor: invalidate queries * chore(pkg): remove old react-query * chore: change imports to root prefixes * build: remove needs web from test * set enableReinitialize to true to fix formik caching issues * fix all property for apiKeys const * fix toast when enabling/disabling feed --------- Co-authored-by: martylukyy <35452459+martylukyy@users.noreply.github.com>
This commit is contained in:
parent
0be92bef65
commit
6e5385a490
54 changed files with 1101 additions and 1117 deletions
9
.github/workflows/release.yml
vendored
9
.github/workflows/release.yml
vendored
|
@ -44,19 +44,12 @@ jobs:
|
||||||
test:
|
test:
|
||||||
name: Test
|
name: Test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: web
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Download web production build
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: web-dist
|
|
||||||
path: web/dist
|
|
||||||
|
|
||||||
# 1.20 is the last version to support Windows < 10, Server < 2016, and MacOS < 1.15.
|
# 1.20 is the last version to support Windows < 10, Server < 2016, and MacOS < 1.15.
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v4
|
||||||
|
@ -107,7 +100,7 @@ jobs:
|
||||||
path: dist/*
|
path: dist/*
|
||||||
|
|
||||||
goreleaser:
|
goreleaser:
|
||||||
name: Build & publish binaries and images
|
name: Build and publish binaries
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [web, test]
|
needs: [web, test]
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
"@headlessui/react": "^1.6.4",
|
"@headlessui/react": "^1.6.4",
|
||||||
"@heroicons/react": "^2.0.11",
|
"@heroicons/react": "^2.0.11",
|
||||||
"@hookform/error-message": "^2.0.0",
|
"@hookform/error-message": "^2.0.0",
|
||||||
|
"@tanstack/react-query": "^4.29.3",
|
||||||
|
"@tanstack/react-query-devtools": "^4.29.3",
|
||||||
"date-fns": "^2.28.0",
|
"date-fns": "^2.28.0",
|
||||||
"formik": "^2.2.9",
|
"formik": "^2.2.9",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
@ -22,7 +24,6 @@
|
||||||
"react-multi-select-component": "^4.2.9",
|
"react-multi-select-component": "^4.2.9",
|
||||||
"react-popper-tooltip": "^4.4.2",
|
"react-popper-tooltip": "^4.4.2",
|
||||||
"react-portal": "^4.2.2",
|
"react-portal": "^4.2.2",
|
||||||
"react-query": "^3.39.1",
|
|
||||||
"react-ridge-state": "4.2.2",
|
"react-ridge-state": "4.2.2",
|
||||||
"react-router-dom": "^6.3.0",
|
"react-router-dom": "^6.3.0",
|
||||||
"react-select": "^5.3.2",
|
"react-select": "^5.3.2",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { QueryClient, QueryClientProvider, useQueryErrorResetBoundary } from "react-query";
|
import { QueryClient, QueryClientProvider, useQueryErrorResetBoundary } from "@tanstack/react-query";
|
||||||
import { ReactQueryDevtools } from "react-query/devtools";
|
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||||
import { ErrorBoundary } from "react-error-boundary";
|
import { ErrorBoundary } from "react-error-boundary";
|
||||||
import { toast, Toaster } from "react-hot-toast";
|
import { toast, Toaster } from "react-hot-toast";
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,8 @@ export async function HttpClient<T>(
|
||||||
|
|
||||||
// Show an error toast to notify the user what occurred
|
// Show an error toast to notify the user what occurred
|
||||||
return Promise.reject(new Error("Unauthorized"));
|
return Promise.reject(new Error("Unauthorized"));
|
||||||
|
} else if (response.status === 404) {
|
||||||
|
return Promise.reject(new Error("Not found"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject(new Error(await response.text()));
|
return Promise.reject(new Error(await response.text()));
|
||||||
|
@ -50,7 +52,7 @@ export async function HttpClient<T>(
|
||||||
const appClient = {
|
const appClient = {
|
||||||
Get: <T>(endpoint: string) => HttpClient<T>(endpoint, "GET"),
|
Get: <T>(endpoint: string) => HttpClient<T>(endpoint, "GET"),
|
||||||
Post: <T = void>(endpoint: string, data: PostBody = undefined) => HttpClient<T>(endpoint, "POST", { body: data }),
|
Post: <T = void>(endpoint: string, data: PostBody = undefined) => HttpClient<T>(endpoint, "POST", { body: data }),
|
||||||
Put: (endpoint: string, data: PostBody) => HttpClient<void>(endpoint, "PUT", { body: data }),
|
Put: <T = void>(endpoint: string, data: PostBody) => HttpClient<T>(endpoint, "PUT", { body: data }),
|
||||||
Patch: (endpoint: string, data: PostBody = undefined) => HttpClient<void>(endpoint, "PATCH", { body: data }),
|
Patch: (endpoint: string, data: PostBody = undefined) => HttpClient<void>(endpoint, "PATCH", { body: data }),
|
||||||
Delete: (endpoint: string) => HttpClient<void>(endpoint, "DELETE")
|
Delete: (endpoint: string) => HttpClient<void>(endpoint, "DELETE")
|
||||||
};
|
};
|
||||||
|
@ -113,7 +115,7 @@ export const APIClient = {
|
||||||
},
|
},
|
||||||
getByID: (id: number) => appClient.Get<Filter>(`api/filters/${id}`),
|
getByID: (id: number) => appClient.Get<Filter>(`api/filters/${id}`),
|
||||||
create: (filter: Filter) => appClient.Post<Filter>("api/filters", filter),
|
create: (filter: Filter) => appClient.Post<Filter>("api/filters", filter),
|
||||||
update: (filter: Filter) => appClient.Put(`api/filters/${filter.id}`, filter),
|
update: (filter: Filter) => appClient.Put<Filter>(`api/filters/${filter.id}`, filter),
|
||||||
duplicate: (id: number) => appClient.Get<Filter>(`api/filters/${id}/duplicate`),
|
duplicate: (id: number) => appClient.Get<Filter>(`api/filters/${id}/duplicate`),
|
||||||
toggleEnable: (id: number, enabled: boolean) => appClient.Put(`api/filters/${id}/enabled`, { enabled }),
|
toggleEnable: (id: number, enabled: boolean) => appClient.Put(`api/filters/${id}/enabled`, { enabled }),
|
||||||
delete: (id: number) => appClient.Delete(`api/filters/${id}`)
|
delete: (id: number) => appClient.Delete(`api/filters/${id}`)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import logo from "@/logo.png";
|
import logo from "@app/logo.png";
|
||||||
|
|
||||||
export const NotFound = () => {
|
export const NotFound = () => {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { classNames } from "../../utils";
|
import { classNames } from "@utils";
|
||||||
|
|
||||||
interface ButtonProps {
|
interface ButtonProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { formatDistanceToNowStrict } from "date-fns";
|
||||||
import { CheckIcon } from "@heroicons/react/24/solid";
|
import { CheckIcon } from "@heroicons/react/24/solid";
|
||||||
import { ClockIcon, ExclamationCircleIcon, NoSymbolIcon } from "@heroicons/react/24/outline";
|
import { ClockIcon, ExclamationCircleIcon, NoSymbolIcon } from "@heroicons/react/24/outline";
|
||||||
|
|
||||||
import { classNames, simplifyDate } from "../../utils";
|
import { classNames, simplifyDate } from "@utils";
|
||||||
import { Tooltip } from "../tooltips/Tooltip";
|
import { Tooltip } from "../tooltips/Tooltip";
|
||||||
|
|
||||||
interface CellProps {
|
interface CellProps {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { SettingsContext } from "../utils/Context";
|
import { SettingsContext } from "@utils/Context";
|
||||||
|
|
||||||
interface DebugProps {
|
interface DebugProps {
|
||||||
values: unknown;
|
values: unknown;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useToggle } from "../../hooks/hooks";
|
import { useToggle } from "@hooks/hooks";
|
||||||
import { CheckIcon, DocumentDuplicateIcon, EyeIcon, EyeSlashIcon } from "@heroicons/react/24/outline";
|
import { CheckIcon, DocumentDuplicateIcon, EyeIcon, EyeSlashIcon } from "@heroicons/react/24/outline";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Field, FieldProps } from "formik";
|
import { Field, FieldProps } from "formik";
|
||||||
import { classNames } from "../../utils";
|
import { classNames } from "@utils";
|
||||||
import { CustomTooltip } from "../tooltips/CustomTooltip";
|
import { CustomTooltip } from "@components/tooltips/CustomTooltip";
|
||||||
|
|
||||||
interface ErrorFieldProps {
|
interface ErrorFieldProps {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Field, FieldProps, useFormikContext } from "formik";
|
import { Field, FieldProps, useFormikContext } from "formik";
|
||||||
import { classNames } from "../../utils";
|
import { classNames } from "@utils";
|
||||||
import { EyeIcon, EyeSlashIcon, CheckCircleIcon, XCircleIcon } from "@heroicons/react/24/solid";
|
import { EyeIcon, EyeSlashIcon, CheckCircleIcon, XCircleIcon } from "@heroicons/react/24/solid";
|
||||||
import { useToggle } from "../../hooks/hooks";
|
import { useToggle } from "@hooks/hooks";
|
||||||
import { CustomTooltip } from "../tooltips/CustomTooltip";
|
import { CustomTooltip } from "@components/tooltips/CustomTooltip";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
type COL_WIDTHS = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
|
type COL_WIDTHS = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import type { FieldProps, FieldValidator } from "formik";
|
import type { FieldProps, FieldValidator } from "formik";
|
||||||
import { Field } from "formik";
|
import { Field } from "formik";
|
||||||
import { classNames } from "../../utils";
|
import { classNames } from "@utils";
|
||||||
import { useToggle } from "../../hooks/hooks";
|
import { useToggle } from "@hooks/hooks";
|
||||||
import { EyeIcon, EyeSlashIcon } from "@heroicons/react/24/solid";
|
import { EyeIcon, EyeSlashIcon } from "@heroicons/react/24/solid";
|
||||||
import { Switch } from "@headlessui/react";
|
import { Switch } from "@headlessui/react";
|
||||||
import { ErrorField, RequiredField } from "./common";
|
import { ErrorField, RequiredField } from "./common";
|
||||||
import Select, { components, ControlProps, InputProps, MenuProps, OptionProps } from "react-select";
|
import Select, { components, ControlProps, InputProps, MenuProps, OptionProps } from "react-select";
|
||||||
import { SelectFieldProps } from "./select";
|
import { SelectFieldProps } from "./select";
|
||||||
import { CustomTooltip } from "../tooltips/CustomTooltip";
|
import { CustomTooltip } from "@components/tooltips/CustomTooltip";
|
||||||
|
|
||||||
interface TextFieldWideProps {
|
interface TextFieldWideProps {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Field, useFormikContext } from "formik";
|
import { Field, useFormikContext } from "formik";
|
||||||
import { RadioGroup } from "@headlessui/react";
|
import { RadioGroup } from "@headlessui/react";
|
||||||
import { classNames } from "../../utils";
|
import { classNames } from "@utils";
|
||||||
|
|
||||||
export interface radioFieldsetOption {
|
export interface radioFieldsetOption {
|
||||||
label: string;
|
label: string;
|
||||||
|
|
|
@ -4,9 +4,9 @@ import { Listbox, Transition } from "@headlessui/react";
|
||||||
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/react/24/solid";
|
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/react/24/solid";
|
||||||
import { MultiSelect as RMSC } from "react-multi-select-component";
|
import { MultiSelect as RMSC } from "react-multi-select-component";
|
||||||
|
|
||||||
import { classNames, COL_WIDTHS } from "../../utils";
|
import { classNames, COL_WIDTHS } from "@utils";
|
||||||
import { SettingsContext } from "../../utils/Context";
|
import { SettingsContext } from "@utils/Context";
|
||||||
import { CustomTooltip } from "../tooltips/CustomTooltip";
|
import { CustomTooltip } from "@components/tooltips/CustomTooltip";
|
||||||
|
|
||||||
export interface MultiSelectOption {
|
export interface MultiSelectOption {
|
||||||
value: string | number;
|
value: string | number;
|
||||||
|
@ -265,8 +265,8 @@ export const Select = ({
|
||||||
}: SelectFieldProps) => {
|
}: SelectFieldProps) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
columns ? `col-span-${columns}` : "col-span-6"
|
columns ? `col-span-${columns}` : "col-span-6"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Field name={name} type="select">
|
<Field name={name} type="select">
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { FieldProps } from "formik";
|
import type { FieldProps } from "formik";
|
||||||
import { Field } from "formik";
|
import { Field } from "formik";
|
||||||
import Select, { components, ControlProps, InputProps, MenuProps, OptionProps } from "react-select";
|
import Select, { components, ControlProps, InputProps, MenuProps, OptionProps } from "react-select";
|
||||||
import { OptionBasicTyped } from "../../domain/constants";
|
import { OptionBasicTyped } from "@domain/constants";
|
||||||
import CreatableSelect from "react-select/creatable";
|
import CreatableSelect from "react-select/creatable";
|
||||||
import { CustomTooltip } from "../tooltips/CustomTooltip";
|
import { CustomTooltip } from "../tooltips/CustomTooltip";
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,8 @@ import React from "react";
|
||||||
import type { FieldInputProps, FieldMetaProps, FieldProps, FormikProps, FormikValues } from "formik";
|
import type { FieldInputProps, FieldMetaProps, FieldProps, FormikProps, FormikValues } from "formik";
|
||||||
import { Field } from "formik";
|
import { Field } from "formik";
|
||||||
import { Switch as HeadlessSwitch } from "@headlessui/react";
|
import { Switch as HeadlessSwitch } from "@headlessui/react";
|
||||||
import { classNames } from "../../utils";
|
|
||||||
|
import { classNames } from "@utils";
|
||||||
import { CustomTooltip } from "../tooltips/CustomTooltip";
|
import { CustomTooltip } from "../tooltips/CustomTooltip";
|
||||||
|
|
||||||
type SwitchProps<V = unknown> = {
|
type SwitchProps<V = unknown> = {
|
||||||
|
@ -82,7 +83,7 @@ const SwitchGroup = ({
|
||||||
}: SwitchGroupProps) => (
|
}: SwitchGroupProps) => (
|
||||||
<HeadlessSwitch.Group as="ol" className="py-4 flex items-center justify-between">
|
<HeadlessSwitch.Group as="ol" className="py-4 flex items-center justify-between">
|
||||||
{label && <div className="flex flex-col">
|
{label && <div className="flex flex-col">
|
||||||
<HeadlessSwitch.Label as={heading ? "h2" : "p"} className={classNames("flex float-left cursor-default mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide", heading ? "text-lg" : "text-sm")}
|
<HeadlessSwitch.Label as={heading ? "h2" : "span"} className={classNames("flex float-left cursor-default mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide", heading ? "text-lg" : "text-sm")}
|
||||||
passive>
|
passive>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
{label}
|
{label}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { FC, forwardRef, ReactNode } from "react";
|
import React, { FC, forwardRef, ReactNode } from "react";
|
||||||
import { DeepMap, FieldError, Path, RegisterOptions, UseFormRegister } from "react-hook-form";
|
import { DeepMap, FieldError, Path, RegisterOptions, UseFormRegister } from "react-hook-form";
|
||||||
import { classNames, get } from "../../utils";
|
import { classNames, get } from "@utils";
|
||||||
import { useToggle } from "../../hooks/hooks";
|
import { useToggle } from "@hooks/hooks";
|
||||||
import { EyeIcon, EyeSlashIcon } from "@heroicons/react/24/solid";
|
import { EyeIcon, EyeSlashIcon } from "@heroicons/react/24/solid";
|
||||||
import { ErrorMessage } from "@hookform/error-message";
|
import { ErrorMessage } from "@hookform/error-message";
|
||||||
import type { FieldValues } from "react-hook-form";
|
import type { FieldValues } from "react-hook-form";
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { CheckCircleIcon, ExclamationCircleIcon, ExclamationTriangleIcon, XMarkIcon } from "@heroicons/react/24/solid";
|
import { CheckCircleIcon, ExclamationCircleIcon, ExclamationTriangleIcon, XMarkIcon } from "@heroicons/react/24/solid";
|
||||||
import { toast, Toast as Tooast } from "react-hot-toast";
|
import { toast, Toast as Tooast } from "react-hot-toast";
|
||||||
import { classNames } from "../../utils";
|
import { classNames } from "@utils";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
type: "error" | "success" | "warning"
|
type: "error" | "success" | "warning"
|
||||||
|
@ -26,7 +26,7 @@ const Toast: FC<Props> = ({ type, body, t }) => (
|
||||||
{type === "error" && "Error"}
|
{type === "error" && "Error"}
|
||||||
{type === "warning" && "Warning"}
|
{type === "warning" && "Warning"}
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">{body}</p>
|
<span className="mt-1 text-sm text-gray-500 dark:text-gray-400">{body}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-4 flex-shrink-0 flex">
|
<div className="ml-4 flex-shrink-0 flex">
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -3,10 +3,11 @@ import { XMarkIcon } from "@heroicons/react/24/solid";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { Form, Formik } from "formik";
|
import { Form, Formik } from "formik";
|
||||||
import type { FormikValues } from "formik";
|
import type { FormikValues } from "formik";
|
||||||
|
|
||||||
import DEBUG from "../debug";
|
import DEBUG from "../debug";
|
||||||
import { useToggle } from "../../hooks/hooks";
|
import { useToggle } from "@hooks/hooks";
|
||||||
import { DeleteModal } from "../modals";
|
import { DeleteModal } from "../modals";
|
||||||
import { classNames } from "../../utils";
|
import { classNames } from "@utils";
|
||||||
|
|
||||||
interface SlideOverProps<DataType> {
|
interface SlideOverProps<DataType> {
|
||||||
title: string;
|
title: string;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { usePopperTooltip } from "react-popper-tooltip";
|
import { usePopperTooltip } from "react-popper-tooltip";
|
||||||
import { classNames } from "../../utils";
|
import { classNames } from "@utils";
|
||||||
|
|
||||||
interface TooltipProps {
|
interface TooltipProps {
|
||||||
label: ReactNode;
|
label: ReactNode;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { MultiSelectOption } from "@/components/inputs/select";
|
import { MultiSelectOption } from "@components/inputs/select";
|
||||||
|
|
||||||
export const resolutions = [
|
export const resolutions = [
|
||||||
"2160p",
|
"2160p",
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { BrowserRouter, Route, Routes } from "react-router-dom";
|
import { BrowserRouter, Route, Routes } from "react-router-dom";
|
||||||
|
|
||||||
import { Login } from "../screens/auth/login";
|
import { Login } from "@screens/auth/login";
|
||||||
import { Onboarding } from "../screens/auth/onboarding";
|
import { Onboarding } from "@screens/auth/onboarding";
|
||||||
import Base from "../screens/Base";
|
import Base from "@screens/Base";
|
||||||
import { Dashboard } from "../screens/dashboard";
|
import { Dashboard } from "@screens/dashboard";
|
||||||
import { FilterDetails, Filters } from "../screens/filters";
|
import { FilterDetails, Filters } from "@screens/filters";
|
||||||
import { Logs } from "../screens/Logs";
|
import { Logs } from "@screens/Logs";
|
||||||
import { Releases } from "../screens/releases";
|
import { Releases } from "@screens/releases";
|
||||||
import Settings from "../screens/Settings";
|
import Settings from "@screens/Settings";
|
||||||
import {
|
import {
|
||||||
APISettings,
|
APISettings,
|
||||||
ApplicationSettings,
|
ApplicationSettings,
|
||||||
|
@ -18,11 +18,11 @@ import {
|
||||||
LogSettings,
|
LogSettings,
|
||||||
NotificationSettings,
|
NotificationSettings,
|
||||||
ReleaseSettings
|
ReleaseSettings
|
||||||
} from "../screens/settings/index";
|
} from "@screens/settings/index";
|
||||||
import { RegexPlayground } from "../screens/settings/RegexPlayground";
|
import { RegexPlayground } from "@screens/settings/RegexPlayground";
|
||||||
import { NotFound } from "@/components/alerts/NotFound";
|
import { NotFound } from "@components/alerts/NotFound";
|
||||||
|
|
||||||
import { baseUrl } from "../utils";
|
import { baseUrl } from "@utils";
|
||||||
|
|
||||||
export const LocalRouter = ({ isLoggedIn }: { isLoggedIn: boolean }) => (
|
export const LocalRouter = ({ isLoggedIn }: { isLoggedIn: boolean }) => (
|
||||||
<BrowserRouter basename={baseUrl()}>
|
<BrowserRouter basename={baseUrl()}>
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
import { useMutation, useQueryClient } from "react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import { XMarkIcon } from "@heroicons/react/24/solid";
|
import { XMarkIcon } from "@heroicons/react/24/solid";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import type { FieldProps } from "formik";
|
import type { FieldProps } from "formik";
|
||||||
import { Field, Form, Formik, FormikErrors, FormikValues } from "formik";
|
import { Field, Form, Formik, FormikErrors, FormikValues } from "formik";
|
||||||
|
|
||||||
import { APIClient } from "../../api/APIClient";
|
|
||||||
import DEBUG from "../../components/debug";
|
|
||||||
import Toast from "../../components/notifications/Toast";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
import { APIClient } from "@api/APIClient";
|
||||||
|
import DEBUG from "@components/debug";
|
||||||
|
import Toast from "@components/notifications/Toast";
|
||||||
|
import { filterKeys } from "@screens/filters/list";
|
||||||
|
|
||||||
interface filterAddFormProps {
|
interface filterAddFormProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
toggle: () => void;
|
toggle: () => void;
|
||||||
|
@ -19,22 +20,22 @@ interface filterAddFormProps {
|
||||||
function FilterAddForm({ isOpen, toggle }: filterAddFormProps) {
|
function FilterAddForm({ isOpen, toggle }: filterAddFormProps) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const mutation = useMutation(
|
const mutation = useMutation({
|
||||||
(filter: Filter) => APIClient.filters.create(filter),
|
mutationFn: (filter: Filter) => APIClient.filters.create(filter),
|
||||||
{
|
onSuccess: (filter) => {
|
||||||
onSuccess: (filter) => {
|
queryClient.invalidateQueries({ queryKey: filterKeys.lists() });
|
||||||
queryClient.invalidateQueries("filters");
|
|
||||||
toast.custom((t) => <Toast type="success" body={`Filter ${filter.name} was added`} t={t} />);
|
|
||||||
|
|
||||||
toggle();
|
toast.custom((t) => <Toast type="success" body={`Filter ${filter.name} was added`} t={t} />);
|
||||||
if (filter.id) {
|
|
||||||
navigate(filter.id.toString());
|
toggle();
|
||||||
}
|
if (filter.id) {
|
||||||
|
navigate(filter.id.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
const handleSubmit = (data: unknown) => mutation.mutate(data as Filter);
|
const handleSubmit = (data: unknown) => mutation.mutate(data as Filter);
|
||||||
|
|
||||||
const validate = (values: FormikValues) => {
|
const validate = (values: FormikValues) => {
|
||||||
const errors = {} as FormikErrors<FormikValues>;
|
const errors = {} as FormikErrors<FormikValues>;
|
||||||
if (!values.name) {
|
if (!values.name) {
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
import { useMutation, useQueryClient } from "react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import { XMarkIcon } from "@heroicons/react/24/solid";
|
import { XMarkIcon } from "@heroicons/react/24/solid";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import type { FieldProps } from "formik";
|
import type { FieldProps } from "formik";
|
||||||
import { Field, Form, Formik, FormikErrors, FormikValues } from "formik";
|
import { Field, Form, Formik, FormikErrors, FormikValues } from "formik";
|
||||||
|
|
||||||
import { APIClient } from "../../api/APIClient";
|
import { APIClient } from "@api/APIClient";
|
||||||
import DEBUG from "../../components/debug";
|
import DEBUG from "@components/debug";
|
||||||
import Toast from "../../components/notifications/Toast";
|
import Toast from "@components/notifications/Toast";
|
||||||
|
import { apiKeys } from "@screens/settings/Api";
|
||||||
|
|
||||||
interface apiKeyAddFormProps {
|
interface apiKeyAddFormProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
@ -18,17 +19,16 @@ interface apiKeyAddFormProps {
|
||||||
function APIKeyAddForm({ isOpen, toggle }: apiKeyAddFormProps) {
|
function APIKeyAddForm({ isOpen, toggle }: apiKeyAddFormProps) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const mutation = useMutation(
|
const mutation = useMutation({
|
||||||
(apikey: APIKey) => APIClient.apikeys.create(apikey),
|
mutationFn: (apikey: APIKey) => APIClient.apikeys.create(apikey),
|
||||||
{
|
onSuccess: (_, key) => {
|
||||||
onSuccess: (_, key) => {
|
queryClient.invalidateQueries({ queryKey: apiKeys.lists() });
|
||||||
queryClient.invalidateQueries("apikeys");
|
|
||||||
toast.custom((t) => <Toast type="success" body={`API key ${key.name} was added`} t={t}/>);
|
toast.custom((t) => <Toast type="success" body={`API key ${key.name} was added`} t={t}/>);
|
||||||
|
|
||||||
toggle();
|
toggle();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
const handleSubmit = (data: unknown) => mutation.mutate(data as APIKey);
|
const handleSubmit = (data: unknown) => mutation.mutate(data as APIKey);
|
||||||
const validate = (values: FormikValues) => {
|
const validate = (values: FormikValues) => {
|
||||||
|
@ -56,7 +56,6 @@ function APIKeyAddForm({ isOpen, toggle }: apiKeyAddFormProps) {
|
||||||
leaveTo="translate-x-full"
|
leaveTo="translate-x-full"
|
||||||
>
|
>
|
||||||
<div className="w-screen max-w-2xl border-l dark:border-gray-700">
|
<div className="w-screen max-w-2xl border-l dark:border-gray-700">
|
||||||
|
|
||||||
<Formik
|
<Formik
|
||||||
initialValues={{
|
initialValues={{
|
||||||
name: "",
|
name: "",
|
||||||
|
@ -114,10 +113,7 @@ function APIKeyAddForm({ isOpen, toggle }: apiKeyAddFormProps) {
|
||||||
type="text"
|
type="text"
|
||||||
className="block w-full shadow-sm dark:bg-gray-800 border-gray-300 dark:border-gray-700 sm:text-sm dark:text-white focus:ring-blue-500 dark:focus:ring-blue-500 focus:border-blue-500 dark:focus:border-blue-500 rounded-md"
|
className="block w-full shadow-sm dark:bg-gray-800 border-gray-300 dark:border-gray-700 sm:text-sm dark:text-white focus:ring-blue-500 dark:focus:ring-blue-500 focus:border-blue-500 dark:focus:border-blue-500 rounded-md"
|
||||||
/>
|
/>
|
||||||
|
{meta.touched && meta.error && <span className="block mt-2 text-red-500">{meta.error}</span>}
|
||||||
{meta.touched && meta.error &&
|
|
||||||
<span className="block mt-2 text-red-500">{meta.error}</span>}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Field>
|
</Field>
|
||||||
|
|
|
@ -1,26 +1,26 @@
|
||||||
import React, { Fragment, useRef, useState } from "react";
|
import React, { Fragment, useRef, useState } from "react";
|
||||||
import { useMutation, useQueryClient } from "react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { XMarkIcon } from "@heroicons/react/24/solid";
|
import { XMarkIcon } from "@heroicons/react/24/solid";
|
||||||
import { classNames, sleep } from "../../utils";
|
|
||||||
import { Form, Formik, useFormikContext } from "formik";
|
import { Form, Formik, useFormikContext } from "formik";
|
||||||
import DEBUG from "../../components/debug";
|
|
||||||
import { APIClient } from "../../api/APIClient";
|
|
||||||
import { DownloadClientTypeOptions, DownloadRuleConditionOptions } from "../../domain/constants";
|
|
||||||
|
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import Toast from "../../components/notifications/Toast";
|
|
||||||
import { useToggle } from "../../hooks/hooks";
|
import { classNames, sleep } from "@utils";
|
||||||
import { DeleteModal } from "../../components/modals";
|
import DEBUG from "@components/debug";
|
||||||
|
import { APIClient } from "@api/APIClient";
|
||||||
|
import { DownloadClientTypeOptions, DownloadRuleConditionOptions } from "@domain/constants";
|
||||||
|
import Toast from "@components/notifications/Toast";
|
||||||
|
import { useToggle } from "@hooks/hooks";
|
||||||
|
import { DeleteModal } from "@components/modals";
|
||||||
import {
|
import {
|
||||||
NumberFieldWide,
|
NumberFieldWide,
|
||||||
PasswordFieldWide,
|
PasswordFieldWide,
|
||||||
RadioFieldsetWide,
|
RadioFieldsetWide,
|
||||||
SwitchGroupWide,
|
SwitchGroupWide,
|
||||||
TextFieldWide
|
TextFieldWide
|
||||||
} from "../../components/inputs";
|
} from "@components/inputs";
|
||||||
import DownloadClient from "../../screens/settings/DownloadClient";
|
import DownloadClient, { clientKeys } from "@screens/settings/DownloadClient";
|
||||||
import { SelectFieldWide } from "../../components/inputs/input_wide";
|
import { SelectFieldWide } from "@components/inputs/input_wide";
|
||||||
|
|
||||||
interface InitialValuesSettings {
|
interface InitialValuesSettings {
|
||||||
basic?: {
|
basic?: {
|
||||||
|
@ -517,59 +517,51 @@ export function DownloadClientAddForm({ isOpen, toggle }: formProps) {
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const mutation = useMutation(
|
const addMutation = useMutation({
|
||||||
(client: DownloadClient) => APIClient.download_clients.create(client),
|
mutationFn: (client: DownloadClient) => APIClient.download_clients.create(client),
|
||||||
{
|
onSuccess: () => {
|
||||||
onSuccess: () => {
|
queryClient.invalidateQueries({ queryKey: clientKeys.lists() });
|
||||||
queryClient.invalidateQueries(["downloadClients"]);
|
toast.custom((t) => <Toast type="success" body="Client was added" t={t}/>);
|
||||||
toast.custom((t) => <Toast type="success" body="Client was added" t={t}/>);
|
|
||||||
|
|
||||||
toggle();
|
toggle();
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
toast.custom((t) => <Toast type="error" body="Client could not be added" t={t}/>);
|
toast.custom((t) => <Toast type="error" body="Client could not be added" t={t}/>);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
const testClientMutation = useMutation(
|
const onSubmit = (data: unknown) => addMutation.mutate(data as DownloadClient);
|
||||||
(client: DownloadClient) => APIClient.download_clients.test(client),
|
|
||||||
{
|
const testClientMutation = useMutation({
|
||||||
onMutate: () => {
|
mutationFn: (client: DownloadClient) => APIClient.download_clients.test(client),
|
||||||
setIsTesting(true);
|
onMutate: () => {
|
||||||
setIsErrorTest(false);
|
setIsTesting(true);
|
||||||
setIsSuccessfulTest(false);
|
setIsErrorTest(false);
|
||||||
},
|
setIsSuccessfulTest(false);
|
||||||
onSuccess: () => {
|
},
|
||||||
sleep(1000)
|
onSuccess: () => {
|
||||||
.then(() => {
|
sleep(1000)
|
||||||
setIsTesting(false);
|
.then(() => {
|
||||||
setIsSuccessfulTest(true);
|
setIsTesting(false);
|
||||||
})
|
setIsSuccessfulTest(true);
|
||||||
.then(() => {
|
})
|
||||||
sleep(2500).then(() => {
|
.then(() => {
|
||||||
setIsSuccessfulTest(false);
|
sleep(2500).then(() => {
|
||||||
});
|
setIsSuccessfulTest(false);
|
||||||
});
|
});
|
||||||
},
|
|
||||||
onError: () => {
|
|
||||||
console.log("not added");
|
|
||||||
setIsTesting(false);
|
|
||||||
setIsErrorTest(true);
|
|
||||||
sleep(2500).then(() => {
|
|
||||||
setIsErrorTest(false);
|
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
onError: () => {
|
||||||
|
console.log("not added");
|
||||||
|
setIsTesting(false);
|
||||||
|
setIsErrorTest(true);
|
||||||
|
sleep(2500).then(() => {
|
||||||
|
setIsErrorTest(false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
const onSubmit = (data: unknown) => {
|
const testClient = (data: unknown) => testClientMutation.mutate(data as DownloadClient);
|
||||||
mutation.mutate(data as DownloadClient);
|
|
||||||
};
|
|
||||||
|
|
||||||
const testClient = (data: unknown) => {
|
|
||||||
testClientMutation.mutate(data as DownloadClient);
|
|
||||||
};
|
|
||||||
|
|
||||||
const initialValues: InitialValues = {
|
const initialValues: InitialValues = {
|
||||||
name: "",
|
name: "",
|
||||||
|
@ -692,74 +684,67 @@ export function DownloadClientUpdateForm({ client, isOpen, toggle }: updateFormP
|
||||||
const [isErrorTest, setIsErrorTest] = useState(false);
|
const [isErrorTest, setIsErrorTest] = useState(false);
|
||||||
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false);
|
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false);
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
const mutation = useMutation(
|
|
||||||
(client: DownloadClient) => APIClient.download_clients.update(client),
|
|
||||||
{
|
|
||||||
onSuccess: () => {
|
|
||||||
queryClient.invalidateQueries(["downloadClients"]);
|
|
||||||
toast.custom((t) => <Toast type="success" body={`${client.name} was updated successfully`} t={t}/>);
|
|
||||||
toggle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const deleteMutation = useMutation(
|
|
||||||
(clientID: number) => APIClient.download_clients.delete(clientID),
|
|
||||||
{
|
|
||||||
onSuccess: () => {
|
|
||||||
queryClient.invalidateQueries();
|
|
||||||
toast.custom((t) => <Toast type="success" body={`${client.name} was deleted.`} t={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 cancelButtonRef = useRef(null);
|
||||||
const cancelModalButtonRef = useRef(null);
|
const cancelModalButtonRef = useRef(null);
|
||||||
|
|
||||||
const deleteAction = () => {
|
const queryClient = useQueryClient();
|
||||||
deleteMutation.mutate(client.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
const testClient = (data: unknown) => {
|
const mutation = useMutation({
|
||||||
testClientMutation.mutate(data as DownloadClient);
|
mutationFn: (client: DownloadClient) => APIClient.download_clients.update(client),
|
||||||
};
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: clientKeys.lists() });
|
||||||
|
queryClient.invalidateQueries({ queryKey: clientKeys.detail(client.id) });
|
||||||
|
|
||||||
|
toast.custom((t) => <Toast type="success" body={`${client.name} was updated successfully`} t={t}/>);
|
||||||
|
toggle();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = (data: unknown) => mutation.mutate(data as DownloadClient);
|
||||||
|
|
||||||
|
const deleteMutation = useMutation({
|
||||||
|
mutationFn: (clientID: number) => APIClient.download_clients.delete(clientID),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: clientKeys.lists() });
|
||||||
|
queryClient.invalidateQueries({ queryKey: clientKeys.detail(client.id) });
|
||||||
|
|
||||||
|
toast.custom((t) => <Toast type="success" body={`${client.name} was deleted.`} t={t}/>);
|
||||||
|
toggleDeleteModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const deleteAction = () => deleteMutation.mutate(client.id);
|
||||||
|
|
||||||
|
|
||||||
|
const testClientMutation = useMutation({
|
||||||
|
mutationFn: (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 testClient = (data: unknown) => testClientMutation.mutate(data as DownloadClient);
|
||||||
|
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
id: client.id,
|
id: client.id,
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
import { useMutation, useQueryClient } from "react-query";
|
|
||||||
import { APIClient } from "../../api/APIClient";
|
|
||||||
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 { SelectFieldBasic } from "../../components/inputs/select_wide";
|
|
||||||
import { componentMapType } from "./DownloadClientForms";
|
|
||||||
import { sleep } from "../../utils";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { ImplementationBadges } from "../../screens/settings/Indexer";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { toast } from "react-hot-toast";
|
||||||
import { useFormikContext } from "formik";
|
import { useFormikContext } from "formik";
|
||||||
import { FeedDownloadTypeOptions } from "../../domain/constants";
|
|
||||||
|
import { APIClient } from "@api/APIClient";
|
||||||
|
import Toast from "@components/notifications/Toast";
|
||||||
|
import { SlideOver } from "@components/panels";
|
||||||
|
import { NumberFieldWide, PasswordFieldWide, SwitchGroupWide, TextFieldWide } from "@components/inputs";
|
||||||
|
import { SelectFieldBasic } from "@components/inputs/select_wide";
|
||||||
|
import { componentMapType } from "./DownloadClientForms";
|
||||||
|
import { sleep } from "@utils";
|
||||||
|
import { ImplementationBadges } from "@screens/settings/Indexer";
|
||||||
|
import { FeedDownloadTypeOptions } from "@domain/constants";
|
||||||
|
import { feedKeys } from "@screens/settings/Feed";
|
||||||
|
|
||||||
interface UpdateProps {
|
interface UpdateProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
@ -40,68 +42,58 @@ export function FeedUpdateForm({ isOpen, toggle, feed }: UpdateProps) {
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const mutation = useMutation(
|
const mutation = useMutation({
|
||||||
(feed: Feed) => APIClient.feeds.update(feed),
|
mutationFn: (feed: Feed) => APIClient.feeds.update(feed),
|
||||||
{
|
onSuccess: () => {
|
||||||
onSuccess: () => {
|
queryClient.invalidateQueries({ queryKey: feedKeys.lists() });
|
||||||
queryClient.invalidateQueries(["feeds"]);
|
|
||||||
toast.custom((t) => <Toast type="success" body={`${feed.name} was updated successfully`} t={t} />);
|
toast.custom((t) => <Toast type="success" body={`${feed.name} was updated successfully`} t={t} />);
|
||||||
toggle();
|
toggle();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
const onSubmit = (formData: unknown) => {
|
const onSubmit = (formData: unknown) => mutation.mutate(formData as Feed);
|
||||||
mutation.mutate(formData as Feed);
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteMutation = useMutation(
|
const deleteMutation = useMutation({
|
||||||
(feedID: number) => APIClient.feeds.delete(feedID),
|
mutationFn: (feedID: number) => APIClient.feeds.delete(feedID),
|
||||||
{
|
onSuccess: () => {
|
||||||
onSuccess: () => {
|
queryClient.invalidateQueries({ queryKey: feedKeys.lists() });
|
||||||
queryClient.invalidateQueries(["feeds"]);
|
|
||||||
toast.custom((t) => <Toast type="success" body={`${feed.name} was deleted.`} t={t} />);
|
toast.custom((t) => <Toast type="success" body={`${feed.name} was deleted.`} t={t} />);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
const deleteAction = () => {
|
const deleteAction = () => deleteMutation.mutate(feed.id);
|
||||||
deleteMutation.mutate(feed.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
const testFeedMutation = useMutation(
|
const testFeedMutation = useMutation({
|
||||||
(feed: Feed) => APIClient.feeds.test(feed),
|
mutationFn: (feed: Feed) => APIClient.feeds.test(feed),
|
||||||
{
|
onMutate: () => {
|
||||||
onMutate: () => {
|
setIsTesting(true);
|
||||||
setIsTesting(true);
|
setIsErrorTest(false);
|
||||||
setIsErrorTest(false);
|
setIsSuccessfulTest(false);
|
||||||
setIsSuccessfulTest(false);
|
},
|
||||||
},
|
onSuccess: () => {
|
||||||
onSuccess: () => {
|
sleep(1000)
|
||||||
sleep(1000)
|
.then(() => {
|
||||||
.then(() => {
|
setIsTesting(false);
|
||||||
setIsTesting(false);
|
setIsSuccessfulTest(true);
|
||||||
setIsSuccessfulTest(true);
|
})
|
||||||
})
|
.then(() => {
|
||||||
.then(() => {
|
sleep(2500).then(() => {
|
||||||
sleep(2500).then(() => {
|
setIsSuccessfulTest(false);
|
||||||
setIsSuccessfulTest(false);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
|
||||||
onError: () => {
|
|
||||||
setIsTesting(false);
|
|
||||||
setIsErrorTest(true);
|
|
||||||
sleep(2500).then(() => {
|
|
||||||
setIsErrorTest(false);
|
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
onError: () => {
|
||||||
|
setIsTesting(false);
|
||||||
|
setIsErrorTest(true);
|
||||||
|
sleep(2500).then(() => {
|
||||||
|
setIsErrorTest(false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
const testFeed = (data: unknown) => {
|
const testFeed = (data: unknown) => testFeedMutation.mutate(data as Feed);
|
||||||
testFeedMutation.mutate(data as Feed);
|
|
||||||
};
|
|
||||||
|
|
||||||
const initialValues: InitialValues = {
|
const initialValues: InitialValues = {
|
||||||
id: feed.id,
|
id: feed.id,
|
||||||
|
|
|
@ -1,20 +1,23 @@
|
||||||
import React, { Fragment, useState } from "react";
|
import React, { Fragment, useState } from "react";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import Select, { components, ControlProps, InputProps, MenuProps, OptionProps } from "react-select";
|
import Select, { components, ControlProps, InputProps, MenuProps, OptionProps } from "react-select";
|
||||||
import type { FieldProps } from "formik";
|
import type { FieldProps } from "formik";
|
||||||
import { Field, Form, Formik, FormikValues } from "formik";
|
import { Field, Form, Formik, FormikValues } from "formik";
|
||||||
import { XMarkIcon } from "@heroicons/react/24/solid";
|
import { XMarkIcon } from "@heroicons/react/24/solid";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { classNames, sleep } from "../../utils";
|
|
||||||
import DEBUG from "../../components/debug";
|
import { classNames, sleep } from "@utils";
|
||||||
import { APIClient } from "../../api/APIClient";
|
import DEBUG from "@components/debug";
|
||||||
import { PasswordFieldWide, SwitchGroupWide, TextFieldWide } from "../../components/inputs";
|
import { APIClient } from "@api/APIClient";
|
||||||
import { SlideOver } from "../../components/panels";
|
import { PasswordFieldWide, SwitchGroupWide, TextFieldWide } from "@components/inputs";
|
||||||
import Toast from "../../components/notifications/Toast";
|
import { SlideOver } from "@components/panels";
|
||||||
import { SelectFieldBasic, SelectFieldCreatable } from "../../components/inputs/select_wide";
|
import Toast from "@components/notifications/Toast";
|
||||||
import { CustomTooltip } from "../../components/tooltips/CustomTooltip";
|
import { SelectFieldBasic, SelectFieldCreatable } from "@components/inputs/select_wide";
|
||||||
import { FeedDownloadTypeOptions } from "../../domain/constants";
|
import { CustomTooltip } from "@components/tooltips/CustomTooltip";
|
||||||
|
import { FeedDownloadTypeOptions } from "@domain/constants";
|
||||||
|
import { feedKeys } from "@screens/settings/Feed";
|
||||||
|
import { indexerKeys } from "@screens/settings/Indexer";
|
||||||
|
|
||||||
const Input = (props: InputProps) => (
|
const Input = (props: InputProps) => (
|
||||||
<components.Input
|
<components.Input
|
||||||
|
@ -244,35 +247,37 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
|
||||||
const [indexer, setIndexer] = useState<IndexerDefinition>({} as IndexerDefinition);
|
const [indexer, setIndexer] = useState<IndexerDefinition>({} as IndexerDefinition);
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { data } = useQuery(
|
const { data } = useQuery({
|
||||||
"indexerDefinition",
|
queryKey: ["indexerDefinition"],
|
||||||
() => APIClient.indexers.getSchema(),
|
queryFn: APIClient.indexers.getSchema,
|
||||||
{
|
enabled: isOpen,
|
||||||
enabled: isOpen,
|
refetchOnWindowFocus: false
|
||||||
refetchOnWindowFocus: false
|
});
|
||||||
|
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationFn: (indexer: Indexer) => APIClient.indexers.create(indexer),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: indexerKeys.lists() });
|
||||||
|
|
||||||
|
toast.custom((t) => <Toast type="success" body="Indexer was added" t={t} />);
|
||||||
|
sleep(1500);
|
||||||
|
toggle();
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
toast.custom((t) => <Toast type="error" body="Indexer could not be added" t={t} />);
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
const mutation = useMutation(
|
const ircMutation = useMutation({
|
||||||
(indexer: Indexer) => APIClient.indexers.create(indexer), {
|
mutationFn: (network: IrcNetworkCreate) => APIClient.irc.createNetwork(network)
|
||||||
onSuccess: () => {
|
});
|
||||||
queryClient.invalidateQueries(["indexer"]);
|
|
||||||
toast.custom((t) => <Toast type="success" body="Indexer was added" t={t} />);
|
|
||||||
sleep(1500);
|
|
||||||
toggle();
|
|
||||||
},
|
|
||||||
onError: () => {
|
|
||||||
toast.custom((t) => <Toast type="error" body="Indexer could not be added" t={t} />);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const ircMutation = useMutation(
|
const feedMutation = useMutation({
|
||||||
(network: IrcNetworkCreate) => APIClient.irc.createNetwork(network)
|
mutationFn: (feed: FeedCreate) => APIClient.feeds.create(feed),
|
||||||
);
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: feedKeys.lists() });
|
||||||
const feedMutation = useMutation(
|
}
|
||||||
(feed: FeedCreate) => APIClient.feeds.create(feed)
|
});
|
||||||
);
|
|
||||||
|
|
||||||
const onSubmit = (formData: FormikValues) => {
|
const onSubmit = (formData: FormikValues) => {
|
||||||
const ind = data && data.find(i => i.identifier === formData.identifier);
|
const ind = data && data.find(i => i.identifier === formData.identifier);
|
||||||
|
@ -587,39 +592,37 @@ function TestApiButton({ values, show }: TestApiButtonProps) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const testApiMutation = useMutation(
|
const testApiMutation = useMutation({
|
||||||
(req: IndexerTestApiReq) => APIClient.indexers.testApi(req),
|
mutationFn: (req: IndexerTestApiReq) => APIClient.indexers.testApi(req),
|
||||||
{
|
onMutate: () => {
|
||||||
onMutate: () => {
|
setIsTesting(true);
|
||||||
setIsTesting(true);
|
setIsErrorTest(false);
|
||||||
setIsErrorTest(false);
|
setIsSuccessfulTest(false);
|
||||||
setIsSuccessfulTest(false);
|
},
|
||||||
},
|
onSuccess: () => {
|
||||||
onSuccess: () => {
|
toast.custom((t) => <Toast type="success" body="API test successful!" t={t} />);
|
||||||
toast.custom((t) => <Toast type="success" body="API test successful!" t={t} />);
|
|
||||||
|
|
||||||
sleep(1000)
|
sleep(1000)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setIsTesting(false);
|
setIsTesting(false);
|
||||||
setIsSuccessfulTest(true);
|
setIsSuccessfulTest(true);
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
sleep(2500).then(() => {
|
sleep(2500).then(() => {
|
||||||
setIsSuccessfulTest(false);
|
setIsSuccessfulTest(false);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
|
||||||
onError: (error: Error) => {
|
|
||||||
toast.custom((t) => <Toast type="error" body={error.message} t={t} />);
|
|
||||||
|
|
||||||
setIsTesting(false);
|
|
||||||
setIsErrorTest(true);
|
|
||||||
sleep(2500).then(() => {
|
|
||||||
setIsErrorTest(false);
|
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
onError: (error: Error) => {
|
||||||
|
toast.custom((t) => <Toast type="error" body={error.message} t={t} />);
|
||||||
|
|
||||||
|
setIsTesting(false);
|
||||||
|
setIsErrorTest(true);
|
||||||
|
sleep(2500).then(() => {
|
||||||
|
setIsErrorTest(false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
const testApi = () => {
|
const testApi = () => {
|
||||||
const req: IndexerTestApiReq = {
|
const req: IndexerTestApiReq = {
|
||||||
|
@ -706,9 +709,11 @@ interface UpdateProps {
|
||||||
export function IndexerUpdateForm({ isOpen, toggle, indexer }: UpdateProps) {
|
export function IndexerUpdateForm({ isOpen, toggle, indexer }: UpdateProps) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const mutation = useMutation((indexer: Indexer) => APIClient.indexers.update(indexer), {
|
const mutation = useMutation({
|
||||||
|
mutationFn: (indexer: Indexer) => APIClient.indexers.update(indexer),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(["indexer"]);
|
queryClient.invalidateQueries({ queryKey: indexerKeys.lists() });
|
||||||
|
|
||||||
toast.custom((t) => <Toast type="success" body={`${indexer.name} was updated successfully`} t={t} />);
|
toast.custom((t) => <Toast type="success" body={`${indexer.name} was updated successfully`} t={t} />);
|
||||||
sleep(1500);
|
sleep(1500);
|
||||||
|
|
||||||
|
@ -716,23 +721,23 @@ export function IndexerUpdateForm({ isOpen, toggle, indexer }: UpdateProps) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const deleteMutation = useMutation((id: number) => APIClient.indexers.delete(id), {
|
|
||||||
onSuccess: () => {
|
|
||||||
queryClient.invalidateQueries(["indexer"]);
|
|
||||||
toast.custom((t) => <Toast type="success" body={`${indexer.name} was deleted.`} t={t} />);
|
|
||||||
|
|
||||||
toggle();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const onSubmit = (data: unknown) => {
|
const onSubmit = (data: unknown) => {
|
||||||
// TODO clear data depending on type
|
// TODO clear data depending on type
|
||||||
mutation.mutate(data as Indexer);
|
mutation.mutate(data as Indexer);
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteAction = () => {
|
const deleteMutation = useMutation({
|
||||||
deleteMutation.mutate(indexer.id ?? 0);
|
mutationFn: (id: number) => APIClient.indexers.delete(id),
|
||||||
};
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: indexerKeys.lists() });
|
||||||
|
|
||||||
|
toast.custom((t) => <Toast type="success" body={`${indexer.name} was deleted.`} t={t} />);
|
||||||
|
|
||||||
|
toggle();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const deleteAction = () => deleteMutation.mutate(indexer.id ?? 0);
|
||||||
|
|
||||||
const renderSettingFields = (settings: IndexerSetting[]) => {
|
const renderSettingFields = (settings: IndexerSetting[]) => {
|
||||||
if (settings === undefined) {
|
if (settings === undefined) {
|
||||||
|
|
|
@ -1,13 +1,18 @@
|
||||||
import { useMutation, useQueryClient } from "react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import { XMarkIcon } from "@heroicons/react/24/solid";
|
import { XMarkIcon } from "@heroicons/react/24/solid";
|
||||||
import type { FieldProps } from "formik";
|
import type { FieldProps } from "formik";
|
||||||
import { Field, FieldArray, FormikErrors, FormikValues } from "formik";
|
import { Field, FieldArray, FormikErrors, FormikValues } from "formik";
|
||||||
import { APIClient } from "../../api/APIClient";
|
|
||||||
import { NumberFieldWide, PasswordFieldWide, SwitchGroupWide, SwitchGroupWideRed, TextFieldWide } from "../../components/inputs";
|
|
||||||
import { SlideOver } from "../../components/panels";
|
|
||||||
import Toast from "../../components/notifications/Toast";
|
|
||||||
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
|
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
|
||||||
|
import Select, { components, ControlProps, InputProps, MenuProps, OptionProps } from "react-select";
|
||||||
|
import { Dialog } from "@headlessui/react";
|
||||||
|
|
||||||
|
import { IrcAuthMechanismTypeOptions, OptionBasicTyped } from "@domain/constants";
|
||||||
|
import { ircKeys } from "@screens/settings/Irc";
|
||||||
|
import { APIClient } from "@api/APIClient";
|
||||||
|
import { NumberFieldWide, PasswordFieldWide, SwitchGroupWide, SwitchGroupWideRed, TextFieldWide } from "@components/inputs";
|
||||||
|
import { SlideOver } from "@components/panels";
|
||||||
|
import Toast from "@components/notifications/Toast";
|
||||||
|
|
||||||
interface ChannelsFieldArrayProps {
|
interface ChannelsFieldArrayProps {
|
||||||
channels: IrcChannel[];
|
channels: IrcChannel[];
|
||||||
|
@ -96,43 +101,21 @@ interface AddFormProps {
|
||||||
|
|
||||||
export function IrcNetworkAddForm({ isOpen, toggle }: AddFormProps) {
|
export function IrcNetworkAddForm({ isOpen, toggle }: AddFormProps) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const mutation = useMutation(
|
|
||||||
(network: IrcNetwork) => APIClient.irc.createNetwork(network),
|
const mutation = useMutation({
|
||||||
{
|
mutationFn: (network: IrcNetwork) => APIClient.irc.createNetwork(network),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(["networks"]);
|
queryClient.invalidateQueries({ queryKey: ircKeys.lists() });
|
||||||
toast.custom((t) => <Toast type="success" body="IRC Network added. Please allow up to 30 seconds for the network to come online." t={t} />);
|
|
||||||
toggle();
|
toast.custom((t) => <Toast type="success" body="IRC Network added. Please allow up to 30 seconds for the network to come online." t={t} />);
|
||||||
},
|
toggle();
|
||||||
onError: () => {
|
},
|
||||||
toast.custom((t) => <Toast type="error" body="IRC Network could not be added" t={t} />);
|
onError: () => {
|
||||||
}
|
toast.custom((t) => <Toast type="error" body="IRC Network could not be added" t={t} />);
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
const onSubmit = (data: unknown) => {
|
const onSubmit = (data: unknown) => mutation.mutate(data as IrcNetwork);
|
||||||
mutation.mutate(data as IrcNetwork);
|
|
||||||
};
|
|
||||||
|
|
||||||
const validate = (values: FormikValues) => {
|
|
||||||
const errors = {} as FormikErrors<FormikValues>;
|
|
||||||
if (!values.name)
|
|
||||||
errors.name = "Required";
|
|
||||||
|
|
||||||
if (!values.port)
|
|
||||||
errors.port = "Required";
|
|
||||||
|
|
||||||
if (!values.server)
|
|
||||||
errors.server = "Required";
|
|
||||||
|
|
||||||
if (!values.nick)
|
|
||||||
errors.nick = "Required";
|
|
||||||
|
|
||||||
// if (!values.auth || !values.auth.account)
|
|
||||||
// errors.auth = { account: "Required" };
|
|
||||||
|
|
||||||
return errors;
|
|
||||||
};
|
|
||||||
|
|
||||||
const initialValues: IrcNetworkAddFormValues = {
|
const initialValues: IrcNetworkAddFormValues = {
|
||||||
name: "",
|
name: "",
|
||||||
|
@ -157,7 +140,7 @@ export function IrcNetworkAddForm({ isOpen, toggle }: AddFormProps) {
|
||||||
toggle={toggle}
|
toggle={toggle}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
initialValues={initialValues}
|
initialValues={initialValues}
|
||||||
validate={validate}
|
validate={validateNetwork}
|
||||||
>
|
>
|
||||||
{(values) => (
|
{(values) => (
|
||||||
<div className="flex flex-col space-y-4 px-1 py-6 sm:py-0 sm:space-y-0">
|
<div className="flex flex-col space-y-4 px-1 py-6 sm:py-0 sm:space-y-0">
|
||||||
|
@ -214,6 +197,28 @@ export function IrcNetworkAddForm({ isOpen, toggle }: AddFormProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const validateNetwork = (values: FormikValues) => {
|
||||||
|
const errors = {} as FormikErrors<FormikValues>;
|
||||||
|
|
||||||
|
if (!values.name) {
|
||||||
|
errors.name = "Required";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!values.server) {
|
||||||
|
errors.server = "Required";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!values.port) {
|
||||||
|
errors.port = "Required";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!values.nick) {
|
||||||
|
errors.nick = "Required";
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
};
|
||||||
|
|
||||||
interface IrcNetworkUpdateFormValues {
|
interface IrcNetworkUpdateFormValues {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -241,53 +246,31 @@ export function IrcNetworkUpdateForm({
|
||||||
}: IrcNetworkUpdateFormProps) {
|
}: IrcNetworkUpdateFormProps) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const mutation = useMutation((network: IrcNetwork) => APIClient.irc.updateNetwork(network), {
|
const updateMutation = useMutation({
|
||||||
|
mutationFn: (network: IrcNetwork) => APIClient.irc.updateNetwork(network),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(["networks"]);
|
queryClient.invalidateQueries({ queryKey: ircKeys.lists() });
|
||||||
|
|
||||||
toast.custom((t) => <Toast type="success" body={`${network.name} was updated successfully`} t={t} />);
|
toast.custom((t) => <Toast type="success" body={`${network.name} was updated successfully`} t={t} />);
|
||||||
|
|
||||||
toggle();
|
toggle();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const deleteMutation = useMutation((id: number) => APIClient.irc.deleteNetwork(id), {
|
const onSubmit = (data: unknown) => updateMutation.mutate(data as IrcNetwork);
|
||||||
|
|
||||||
|
const deleteMutation = useMutation({
|
||||||
|
mutationFn: (id: number) => APIClient.irc.deleteNetwork(id),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(["networks"]);
|
queryClient.invalidateQueries({ queryKey: ircKeys.lists() });
|
||||||
|
|
||||||
toast.custom((t) => <Toast type="success" body={`${network.name} was deleted.`} t={t} />);
|
toast.custom((t) => <Toast type="success" body={`${network.name} was deleted.`} t={t} />);
|
||||||
|
|
||||||
toggle();
|
toggle();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = (data: unknown) => {
|
const deleteAction = () => deleteMutation.mutate(network.id);
|
||||||
console.log("submit: ", data);
|
|
||||||
mutation.mutate(data as IrcNetwork);
|
|
||||||
};
|
|
||||||
|
|
||||||
const validate = (values: FormikValues) => {
|
|
||||||
const errors = {} as FormikErrors<FormikValues>;
|
|
||||||
|
|
||||||
if (!values.name) {
|
|
||||||
errors.name = "Required";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!values.server) {
|
|
||||||
errors.server = "Required";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!values.port) {
|
|
||||||
errors.port = "Required";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!values.nick) {
|
|
||||||
errors.nick = "Required";
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors;
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteAction = () => {
|
|
||||||
deleteMutation.mutate(network.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
const initialValues: IrcNetworkUpdateFormValues = {
|
const initialValues: IrcNetworkUpdateFormValues = {
|
||||||
id: network.id,
|
id: network.id,
|
||||||
|
@ -312,7 +295,7 @@ export function IrcNetworkUpdateForm({
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
deleteAction={deleteAction}
|
deleteAction={deleteAction}
|
||||||
initialValues={initialValues}
|
initialValues={initialValues}
|
||||||
validate={validate}
|
validate={validateNetwork}
|
||||||
>
|
>
|
||||||
{(values) => (
|
{(values) => (
|
||||||
<div className="flex flex-col space-y-4 px-1 py-6 sm:py-0 sm:space-y-0">
|
<div className="flex flex-col space-y-4 px-1 py-6 sm:py-0 sm:space-y-0">
|
||||||
|
@ -459,10 +442,6 @@ function SelectField<T>({ name, label, options }: SelectFieldProps<T>) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
import Select, { components, ControlProps, InputProps, MenuProps, OptionProps } from "react-select";
|
|
||||||
import { IrcAuthMechanismTypeOptions, OptionBasicTyped } from "../../domain/constants";
|
|
||||||
import { Dialog } from "@headlessui/react";
|
|
||||||
|
|
||||||
const Input = (props: InputProps) => {
|
const Input = (props: InputProps) => {
|
||||||
return (
|
return (
|
||||||
<components.Input
|
<components.Input
|
||||||
|
|
|
@ -4,15 +4,17 @@ import type { FieldProps } from "formik";
|
||||||
import { Field, Form, Formik, FormikErrors, FormikValues } from "formik";
|
import { Field, Form, Formik, FormikErrors, FormikValues } from "formik";
|
||||||
import { XMarkIcon } from "@heroicons/react/24/solid";
|
import { XMarkIcon } from "@heroicons/react/24/solid";
|
||||||
import Select, { components, ControlProps, InputProps, MenuProps, OptionProps } from "react-select";
|
import Select, { components, ControlProps, InputProps, MenuProps, OptionProps } from "react-select";
|
||||||
import { PasswordFieldWide, SwitchGroupWide, TextFieldWide } from "../../components/inputs";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import DEBUG from "../../components/debug";
|
|
||||||
import { EventOptions, NotificationTypeOptions, SelectOption } from "../../domain/constants";
|
|
||||||
import { useMutation, useQueryClient } from "react-query";
|
|
||||||
import { APIClient } from "../../api/APIClient";
|
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import Toast from "../../components/notifications/Toast";
|
|
||||||
import { SlideOver } from "../../components/panels";
|
import { PasswordFieldWide, SwitchGroupWide, TextFieldWide } from "@components/inputs";
|
||||||
|
import DEBUG from "@components/debug";
|
||||||
|
import { EventOptions, NotificationTypeOptions, SelectOption } from "@domain/constants";
|
||||||
|
import { APIClient } from "@api/APIClient";
|
||||||
|
import Toast from "@components/notifications/Toast";
|
||||||
|
import { SlideOver } from "@components/panels";
|
||||||
import { componentMapType } from "./DownloadClientForms";
|
import { componentMapType } from "./DownloadClientForms";
|
||||||
|
import { notificationKeys } from "@screens/settings/Notifications";
|
||||||
|
|
||||||
const Input = (props: InputProps) => {
|
const Input = (props: InputProps) => {
|
||||||
return (
|
return (
|
||||||
|
@ -137,36 +139,29 @@ interface AddProps {
|
||||||
export function NotificationAddForm({ isOpen, toggle }: AddProps) {
|
export function NotificationAddForm({ isOpen, toggle }: AddProps) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const mutation = useMutation(
|
const createMutation = useMutation({
|
||||||
(notification: Notification) => APIClient.notifications.create(notification),
|
mutationFn: (notification: Notification) => APIClient.notifications.create(notification),
|
||||||
{
|
onSuccess: () => {
|
||||||
onSuccess: () => {
|
queryClient.invalidateQueries({ queryKey: notificationKeys.lists() });
|
||||||
queryClient.invalidateQueries(["notifications"]);
|
|
||||||
toast.custom((t) => <Toast type="success" body="Notification added!" t={t} />);
|
toast.custom((t) => <Toast type="success" body="Notification added!" t={t} />);
|
||||||
toggle();
|
toggle();
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
toast.custom((t) => <Toast type="error" body="Notification could not be added" t={t} />);
|
toast.custom((t) => <Toast type="error" body="Notification could not be added" t={t} />);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
const onSubmit = (formData: unknown) => {
|
const onSubmit = (formData: unknown) => createMutation.mutate(formData as Notification);
|
||||||
mutation.mutate(formData as Notification);
|
|
||||||
};
|
|
||||||
|
|
||||||
const testMutation = useMutation(
|
const testMutation = useMutation({
|
||||||
(n: Notification) => APIClient.notifications.test(n),
|
mutationFn: (n: Notification) => APIClient.notifications.test(n),
|
||||||
{
|
onError: (err) => {
|
||||||
onError: (err) => {
|
console.error(err);
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
const testNotification = (data: unknown) => {
|
const testNotification = (data: unknown) => testMutation.mutate(data as Notification);
|
||||||
testMutation.mutate(data as Notification);
|
|
||||||
};
|
|
||||||
|
|
||||||
const validate = (values: NotificationAddFormValues) => {
|
const validate = (values: NotificationAddFormValues) => {
|
||||||
const errors = {} as FormikErrors<FormikValues>;
|
const errors = {} as FormikErrors<FormikValues>;
|
||||||
|
@ -410,47 +405,37 @@ interface InitialValues {
|
||||||
export function NotificationUpdateForm({ isOpen, toggle, notification }: UpdateProps) {
|
export function NotificationUpdateForm({ isOpen, toggle, notification }: UpdateProps) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const mutation = useMutation(
|
const mutation = useMutation({
|
||||||
(notification: Notification) => APIClient.notifications.update(notification),
|
mutationFn: (notification: Notification) => APIClient.notifications.update(notification),
|
||||||
{
|
onSuccess: () => {
|
||||||
onSuccess: () => {
|
queryClient.invalidateQueries({ queryKey: notificationKeys.lists() });
|
||||||
queryClient.invalidateQueries(["notifications"]);
|
|
||||||
toast.custom((t) => <Toast type="success" body={`${notification.name} was updated successfully`} t={t}/>);
|
toast.custom((t) => <Toast type="success" body={`${notification.name} was updated successfully`} t={t}/>);
|
||||||
toggle();
|
toggle();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
const deleteMutation = useMutation(
|
const onSubmit = (formData: unknown) => mutation.mutate(formData as Notification);
|
||||||
(notificationID: number) => APIClient.notifications.delete(notificationID),
|
|
||||||
{
|
const deleteMutation = useMutation({
|
||||||
onSuccess: () => {
|
mutationFn: (notificationID: number) => APIClient.notifications.delete(notificationID),
|
||||||
queryClient.invalidateQueries(["notifications"]);
|
onSuccess: () => {
|
||||||
toast.custom((t) => <Toast type="success" body={`${notification.name} was deleted.`} t={t}/>);
|
queryClient.invalidateQueries({ queryKey: notificationKeys.lists() });
|
||||||
}
|
|
||||||
|
toast.custom((t) => <Toast type="success" body={`${notification.name} was deleted.`} t={t}/>);
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
const onSubmit = (formData: unknown) => {
|
const deleteAction = () => deleteMutation.mutate(notification.id);
|
||||||
mutation.mutate(formData as Notification);
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteAction = () => {
|
const testMutation = useMutation({
|
||||||
deleteMutation.mutate(notification.id);
|
mutationFn: (n: Notification) => APIClient.notifications.test(n),
|
||||||
};
|
onError: (err) => {
|
||||||
|
console.error(err);
|
||||||
const testMutation = useMutation(
|
|
||||||
(n: Notification) => APIClient.notifications.test(n),
|
|
||||||
{
|
|
||||||
onError: (err) => {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
const testNotification = (data: unknown) => {
|
const testNotification = (data: unknown) => testMutation.mutate(data as Notification);
|
||||||
testMutation.mutate(data as Notification);
|
|
||||||
};
|
|
||||||
|
|
||||||
const initialValues: InitialValues = {
|
const initialValues: InitialValues = {
|
||||||
id: notification.id,
|
id: notification.id,
|
||||||
|
|
|
@ -1,26 +1,22 @@
|
||||||
import { Fragment } from "react";
|
import React, { Fragment } from "react";
|
||||||
import { Link, NavLink, Outlet } from "react-router-dom";
|
import { Link, NavLink, Outlet } from "react-router-dom";
|
||||||
import { Disclosure, Menu, Transition } from "@headlessui/react";
|
import { Disclosure, Menu, Transition } from "@headlessui/react";
|
||||||
import { ArrowTopRightOnSquareIcon, UserIcon } from "@heroicons/react/24/solid";
|
import { ArrowTopRightOnSquareIcon, UserIcon } from "@heroicons/react/24/solid";
|
||||||
import { Bars3Icon, XMarkIcon, MegaphoneIcon } from "@heroicons/react/24/outline";
|
import { Bars3Icon, XMarkIcon, MegaphoneIcon } from "@heroicons/react/24/outline";
|
||||||
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||||
import { AuthContext } from "../utils/Context";
|
|
||||||
|
|
||||||
import logo from "../logo.png";
|
|
||||||
import { useQuery } from "react-query";
|
|
||||||
import { APIClient } from "../api/APIClient";
|
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import Toast from "@/components/notifications/Toast";
|
|
||||||
|
import { AuthContext } from "@utils/Context";
|
||||||
|
import logo from "@app/logo.png";
|
||||||
|
import { APIClient } from "@api/APIClient";
|
||||||
|
import Toast from "@components/notifications/Toast";
|
||||||
|
import { classNames } from "@utils";
|
||||||
|
|
||||||
interface NavItem {
|
interface NavItem {
|
||||||
name: string;
|
name: string;
|
||||||
path: string;
|
path: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function classNames(...classes: string[]) {
|
|
||||||
return classes.filter(Boolean).join(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
const nav: Array<NavItem> = [
|
const nav: Array<NavItem> = [
|
||||||
{ name: "Dashboard", path: "/" },
|
{ name: "Dashboard", path: "/" },
|
||||||
{ name: "Filters", path: "/filters" },
|
{ name: "Filters", path: "/filters" },
|
||||||
|
@ -32,24 +28,27 @@ const nav: Array<NavItem> = [
|
||||||
export default function Base() {
|
export default function Base() {
|
||||||
const authContext = AuthContext.useValue();
|
const authContext = AuthContext.useValue();
|
||||||
|
|
||||||
const { data } = useQuery(
|
const { data } = useQuery({
|
||||||
["updates"],
|
queryKey: ["updates"],
|
||||||
() => APIClient.updates.getLatestRelease(),
|
queryFn: () => APIClient.updates.getLatestRelease(),
|
||||||
{
|
retry: false,
|
||||||
retry: false,
|
refetchOnWindowFocus: false,
|
||||||
refetchOnWindowFocus: false,
|
onError: err => console.log(err)
|
||||||
onError: err => console.log(err)
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const LogOutUser = () => {
|
const logoutMutation = useMutation( {
|
||||||
APIClient.auth.logout()
|
mutationFn: APIClient.auth.logout,
|
||||||
.then(() => {
|
onSuccess: () => {
|
||||||
AuthContext.reset();
|
AuthContext.reset();
|
||||||
toast.custom((t) => (
|
|
||||||
<Toast type="success" body="You have been logged out. Goodbye!" t={t} />
|
toast.custom((t) => (
|
||||||
));
|
<Toast type="success" body="You have been logged out. Goodbye!" t={t} />
|
||||||
});
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const logoutAction = () => {
|
||||||
|
logoutMutation.mutate();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -172,7 +171,7 @@ export default function Base() {
|
||||||
<Menu.Item>
|
<Menu.Item>
|
||||||
{({ active }) => (
|
{({ active }) => (
|
||||||
<button
|
<button
|
||||||
onClick={LogOutUser}
|
onClick={logoutAction}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
active
|
active
|
||||||
? "bg-gray-100 dark:bg-gray-600"
|
? "bg-gray-100 dark:bg-gray-600"
|
||||||
|
@ -242,7 +241,7 @@ export default function Base() {
|
||||||
</NavLink>
|
</NavLink>
|
||||||
))}
|
))}
|
||||||
<button
|
<button
|
||||||
onClick={LogOutUser}
|
onClick={logoutAction}
|
||||||
className="w-full shadow-sm border bg-gray-100 border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-white block px-3 py-2 rounded-md text-base font-medium text-left"
|
className="w-full shadow-sm border bg-gray-100 border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-white block px-3 py-2 rounded-md text-base font-medium text-left"
|
||||||
>
|
>
|
||||||
Logout
|
Logout
|
||||||
|
|
|
@ -2,20 +2,20 @@ import { Fragment, useEffect, useRef, useState } from "react";
|
||||||
import { ExclamationTriangleIcon } from "@heroicons/react/24/solid";
|
import { ExclamationTriangleIcon } from "@heroicons/react/24/solid";
|
||||||
import format from "date-fns/format";
|
import format from "date-fns/format";
|
||||||
import { DebounceInput } from "react-debounce-input";
|
import { DebounceInput } from "react-debounce-input";
|
||||||
import { APIClient } from "../api/APIClient";
|
|
||||||
import { Checkbox } from "../components/Checkbox";
|
|
||||||
import { classNames, simplifyDate } from "../utils";
|
|
||||||
import { SettingsContext } from "../utils/Context";
|
|
||||||
import { EmptySimple } from "../components/emptystates";
|
|
||||||
import {
|
import {
|
||||||
Cog6ToothIcon,
|
Cog6ToothIcon,
|
||||||
DocumentArrowDownIcon
|
DocumentArrowDownIcon
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { Menu, Transition } from "@headlessui/react";
|
import { Menu, Transition } from "@headlessui/react";
|
||||||
import { baseUrl } from "../utils";
|
|
||||||
import { RingResizeSpinner } from "@/components/Icons";
|
|
||||||
|
|
||||||
|
import { APIClient } from "@api/APIClient";
|
||||||
|
import { Checkbox } from "@components/Checkbox";
|
||||||
|
import { classNames, simplifyDate } from "@utils";
|
||||||
|
import { SettingsContext } from "@utils/Context";
|
||||||
|
import { EmptySimple } from "@components/emptystates";
|
||||||
|
import { baseUrl } from "@utils";
|
||||||
|
import { RingResizeSpinner } from "@components/Icons";
|
||||||
|
|
||||||
type LogEvent = {
|
type LogEvent = {
|
||||||
time: string;
|
time: string;
|
||||||
|
@ -83,7 +83,6 @@ export const Logs = () => {
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|
||||||
<div className="max-w-screen-xl mx-auto pb-12 px-2 sm:px-4 lg:px-8">
|
<div className="max-w-screen-xl mx-auto pb-12 px-2 sm:px-4 lg:px-8">
|
||||||
<div className="flex justify-center py-4">
|
<div className="flex justify-center py-4">
|
||||||
<ExclamationTriangleIcon
|
<ExclamationTriangleIcon
|
||||||
|
@ -158,15 +157,13 @@ export const Logs = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LogFiles = () => {
|
export const LogFiles = () => {
|
||||||
const { isLoading, data } = useQuery(
|
const { isLoading, data } = useQuery({
|
||||||
["log-files"],
|
queryKey: ["log-files"],
|
||||||
() => APIClient.logs.files(),
|
queryFn: () => APIClient.logs.files(),
|
||||||
{
|
retry: false,
|
||||||
retry: false,
|
refetchOnWindowFocus: false,
|
||||||
refetchOnWindowFocus: false,
|
onError: err => console.log(err)
|
||||||
onError: err => console.log(err)
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -224,10 +221,8 @@ const LogFilesItem = ({ file }: LogFilesItemProps) => {
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
setIsDownloading(false);
|
setIsDownloading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<li className="text-gray-500 dark:text-gray-400">
|
<li className="text-gray-500 dark:text-gray-400">
|
||||||
<div className="grid grid-cols-12 items-center py-2">
|
<div className="grid grid-cols-12 items-center py-2">
|
||||||
<div className="col-span-4 sm:col-span-5 px-2 py-0 truncate hidden sm:block sm:text-sm text-md font-medium text-gray-900 dark:text-gray-200">
|
<div className="col-span-4 sm:col-span-5 px-2 py-0 truncate hidden sm:block sm:text-sm text-md font-medium text-gray-900 dark:text-gray-200">
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {
|
||||||
Square3Stack3DIcon
|
Square3Stack3DIcon
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
|
|
||||||
import { classNames } from "../utils";
|
import { classNames } from "@utils";
|
||||||
|
|
||||||
interface NavTabType {
|
interface NavTabType {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useMutation } from "react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import logo from "../../logo.png";
|
|
||||||
import { APIClient } from "../../api/APIClient";
|
|
||||||
import { AuthContext } from "../../utils/Context";
|
|
||||||
import { PasswordInput, TextInput } from "../../components/inputs/text";
|
|
||||||
import { Tooltip } from "react-tooltip";
|
|
||||||
import Toast from "@/components/notifications/Toast";
|
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
import { Tooltip } from "react-tooltip";
|
||||||
|
|
||||||
|
import logo from "@app/logo.png";
|
||||||
|
import { APIClient } from "@api/APIClient";
|
||||||
|
import { AuthContext } from "@utils/Context";
|
||||||
|
import { PasswordInput, TextInput } from "@components/inputs/text";
|
||||||
|
import Toast from "@components/notifications/Toast";
|
||||||
|
|
||||||
type LoginFormFields = {
|
type LoginFormFields = {
|
||||||
username: string;
|
username: string;
|
||||||
|
@ -37,23 +38,21 @@ export const Login = () => {
|
||||||
.catch(() => { /*don't log to console PAHLLEEEASSSE*/ });
|
.catch(() => { /*don't log to console PAHLLEEEASSSE*/ });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const loginMutation = useMutation(
|
const loginMutation = useMutation({
|
||||||
(data: LoginFormFields) => APIClient.auth.login(data.username, data.password),
|
mutationFn: (data: LoginFormFields) => APIClient.auth.login(data.username, data.password),
|
||||||
{
|
onSuccess: (_, variables: LoginFormFields) => {
|
||||||
onSuccess: (_, variables: LoginFormFields) => {
|
setAuthContext({
|
||||||
setAuthContext({
|
username: variables.username,
|
||||||
username: variables.username,
|
isLoggedIn: true
|
||||||
isLoggedIn: true
|
});
|
||||||
});
|
navigate("/");
|
||||||
navigate("/");
|
},
|
||||||
},
|
onError: () => {
|
||||||
onError: () => {
|
toast.custom((t) => (
|
||||||
toast.custom((t) => (
|
<Toast type="error" body="Wrong password or username!" t={t} />
|
||||||
<Toast type="error" body="Wrong password or username!" t={t} />
|
));
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
const onSubmit = (data: LoginFormFields) => loginMutation.mutate(data);
|
const onSubmit = (data: LoginFormFields) => loginMutation.mutate(data);
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { Form, Formik } from "formik";
|
import { Form, Formik } from "formik";
|
||||||
import { useMutation } from "react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { APIClient } from "../../api/APIClient";
|
|
||||||
|
|
||||||
import { TextField, PasswordField } from "../../components/inputs";
|
import { APIClient } from "@api/APIClient";
|
||||||
import logo from "../../logo.png";
|
import { TextField, PasswordField } from "@components/inputs";
|
||||||
|
import logo from "@app/logo.png";
|
||||||
|
|
||||||
interface InputValues {
|
interface InputValues {
|
||||||
username: string;
|
username: string;
|
||||||
|
@ -33,10 +33,10 @@ export const Onboarding = () => {
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const mutation = useMutation(
|
const mutation = useMutation({
|
||||||
(data: InputValues) => APIClient.auth.onboard(data.username, data.password1),
|
mutationFn: (data: InputValues) => APIClient.auth.onboard(data.username, data.password1),
|
||||||
{ onSuccess: () => navigate("/") }
|
onSuccess: () => navigate("/")
|
||||||
);
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex flex-col justify-center py-12 sm:px-6 lg:px-8">
|
<div className="min-h-screen flex flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import {
|
import {
|
||||||
useTable,
|
useTable,
|
||||||
useFilters,
|
useFilters,
|
||||||
|
@ -8,11 +8,10 @@ import {
|
||||||
usePagination, FilterProps, Column
|
usePagination, FilterProps, Column
|
||||||
} from "react-table";
|
} from "react-table";
|
||||||
|
|
||||||
import { APIClient } from "../../api/APIClient";
|
import { APIClient } from "@api/APIClient";
|
||||||
import { EmptyListState } from "../../components/emptystates";
|
import { EmptyListState } from "@components/emptystates";
|
||||||
|
import * as Icons from "@components/Icons";
|
||||||
import * as Icons from "../../components/Icons";
|
import * as DataTable from "@components/data-table";
|
||||||
import * as DataTable from "../../components/data-table";
|
|
||||||
|
|
||||||
// This is a custom filter UI for selecting
|
// This is a custom filter UI for selecting
|
||||||
// a unique option from a list
|
// a unique option from a list
|
||||||
|
@ -74,8 +73,9 @@ function Table({ columns, data }: TableProps) {
|
||||||
usePagination
|
usePagination
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!page.length)
|
if (!page.length) {
|
||||||
return <EmptyListState text="No recent activity" />;
|
return <EmptyListState text="No recent activity" />;
|
||||||
|
}
|
||||||
|
|
||||||
// Render the UI for your table
|
// Render the UI for your table
|
||||||
return (
|
return (
|
||||||
|
@ -178,24 +178,25 @@ export const ActivityTable = () => {
|
||||||
}
|
}
|
||||||
], []);
|
], []);
|
||||||
|
|
||||||
const { isLoading, data } = useQuery(
|
const { isLoading, data } = useQuery({
|
||||||
"dash_recent_releases",
|
queryKey: ["dash_recent_releases"],
|
||||||
() => APIClient.release.findRecent(),
|
queryFn: APIClient.release.findRecent,
|
||||||
{ refetchOnWindowFocus: false }
|
refetchOnWindowFocus: false
|
||||||
);
|
});
|
||||||
|
|
||||||
if (isLoading)
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col mt-12">
|
<div className="flex flex-col mt-12">
|
||||||
<h3 className="text-2xl font-medium leading-6 text-gray-900 dark:text-gray-200">
|
<h3 className="text-2xl font-medium leading-6 text-gray-900 dark:text-gray-200">
|
||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
<div className="animate-pulse text-black dark:text-white">
|
<div className="animate-pulse text-black dark:text-white">
|
||||||
<EmptyListState text="Loading..." />
|
<EmptyListState text="Loading..."/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col mt-12">
|
<div className="flex flex-col mt-12">
|
||||||
<h3 className="text-2xl font-medium leading-6 text-gray-900 dark:text-gray-200">
|
<h3 className="text-2xl font-medium leading-6 text-gray-900 dark:text-gray-200">
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { APIClient } from "../../api/APIClient";
|
import { APIClient } from "@api/APIClient";
|
||||||
import { classNames } from "../../utils";
|
import { classNames } from "@utils";
|
||||||
|
|
||||||
interface StatsItemProps {
|
interface StatsItemProps {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -28,17 +28,18 @@ const StatsItem = ({ name, placeholder, value }: StatsItemProps) => (
|
||||||
);
|
);
|
||||||
|
|
||||||
export const Stats = () => {
|
export const Stats = () => {
|
||||||
const { isLoading, data } = useQuery(
|
const { isLoading, data } = useQuery({
|
||||||
"dash_release_stats",
|
queryKey: ["dash_release_stats"],
|
||||||
() => APIClient.release.stats(),
|
queryFn: APIClient.release.stats,
|
||||||
{ refetchOnWindowFocus: false }
|
refetchOnWindowFocus: false
|
||||||
);
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-black dark:text-white">
|
<h1 className="text-3xl font-bold text-black dark:text-white">
|
||||||
Stats
|
Stats
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<dl className={classNames("grid grid-cols-1 gap-5 mt-5 sm:grid-cols-2 lg:grid-cols-3", isLoading ? "animate-pulse" : "")}>
|
<dl className={classNames("grid grid-cols-1 gap-5 mt-5 sm:grid-cols-2 lg:grid-cols-3", isLoading ? "animate-pulse" : "")}>
|
||||||
<StatsItem name="Filtered Releases" value={data?.filtered_count ?? 0} />
|
<StatsItem name="Filtered Releases" value={data?.filtered_count ?? 0} />
|
||||||
{/* <StatsItem name="Filter Rejected Releases" stat={data?.filter_rejected_count} /> */}
|
{/* <StatsItem name="Filter Rejected Releases" stat={data?.filter_rejected_count} /> */}
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
import { AlertWarning } from "../../components/alerts";
|
import { AlertWarning } from "@components/alerts";
|
||||||
import { DownloadClientSelect, NumberField, Select, SwitchGroup, TextField } from "../../components/inputs";
|
import { DownloadClientSelect, NumberField, Select, SwitchGroup, TextField } from "@components/inputs";
|
||||||
import { ActionContentLayoutOptions, ActionRtorrentRenameOptions, ActionTypeNameMap, ActionTypeOptions } from "../../domain/constants";
|
import { ActionContentLayoutOptions, ActionRtorrentRenameOptions, ActionTypeNameMap, ActionTypeOptions } from "@domain/constants";
|
||||||
import React, { Fragment, useRef, useEffect, useState } from "react";
|
import React, { Fragment, useRef, useEffect, useState } from "react";
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { APIClient } from "../../api/APIClient";
|
import { APIClient } from "@api/APIClient";
|
||||||
import { Field, FieldArray, FieldProps, FormikValues } from "formik";
|
import { Field, FieldArray, FieldProps, FormikValues } from "formik";
|
||||||
import { EmptyListState } from "../../components/emptystates";
|
import { EmptyListState } from "@components/emptystates";
|
||||||
import { useToggle } from "../../hooks/hooks";
|
import { useToggle } from "@hooks/hooks";
|
||||||
import { classNames } from "../../utils";
|
import { classNames } from "@utils";
|
||||||
import { Dialog, Switch as SwitchBasic, Transition } from "@headlessui/react";
|
import { Dialog, Switch as SwitchBasic, Transition } from "@headlessui/react";
|
||||||
import { ChevronRightIcon } from "@heroicons/react/24/solid";
|
import { ChevronRightIcon } from "@heroicons/react/24/solid";
|
||||||
import { DeleteModal } from "../../components/modals";
|
import { DeleteModal } from "@components/modals";
|
||||||
import { CollapsableSection } from "./details";
|
import { CollapsableSection } from "./details";
|
||||||
import { CustomTooltip } from "../../components/tooltips/CustomTooltip";
|
import { CustomTooltip } from "@components/tooltips/CustomTooltip";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { useFormikContext } from "formik";
|
import { useFormikContext } from "formik";
|
||||||
import { TextArea } from "../../components/inputs/input";
|
import { TextArea } from "@components/inputs/input";
|
||||||
|
|
||||||
interface FilterActionsProps {
|
interface FilterActionsProps {
|
||||||
filter: Filter;
|
filter: Filter;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useRef } from "react";
|
import React, { useRef } from "react";
|
||||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { NavLink, Route, Routes, useLocation, useNavigate, useParams } from "react-router-dom";
|
import { NavLink, Route, Routes, useLocation, useNavigate, useParams } from "react-router-dom";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import { Form, Formik, FormikValues, useFormikContext } from "formik";
|
import { Form, Formik, FormikValues, useFormikContext } from "formik";
|
||||||
|
@ -20,10 +20,10 @@ import {
|
||||||
SOURCES_MUSIC_OPTIONS,
|
SOURCES_MUSIC_OPTIONS,
|
||||||
SOURCES_OPTIONS,
|
SOURCES_OPTIONS,
|
||||||
tagsMatchLogicOptions
|
tagsMatchLogicOptions
|
||||||
} from "../../domain/constants";
|
} from "@app/domain/constants";
|
||||||
import { APIClient } from "../../api/APIClient";
|
import { APIClient } from "@api/APIClient";
|
||||||
import { useToggle } from "../../hooks/hooks";
|
import { useToggle } from "@hooks/hooks";
|
||||||
import { classNames } from "../../utils";
|
import { classNames } from "@utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CheckboxField,
|
CheckboxField,
|
||||||
|
@ -34,13 +34,14 @@ import {
|
||||||
SwitchGroup,
|
SwitchGroup,
|
||||||
TextField,
|
TextField,
|
||||||
RegexField
|
RegexField
|
||||||
} from "../../components/inputs";
|
} from "@components/inputs";
|
||||||
import DEBUG from "../../components/debug";
|
import DEBUG from "@components/debug";
|
||||||
import Toast from "../../components/notifications/Toast";
|
import Toast from "@components/notifications/Toast";
|
||||||
import { DeleteModal } from "../../components/modals";
|
import { DeleteModal } from "@components/modals";
|
||||||
import { TitleSubtitle } from "../../components/headings";
|
import { TitleSubtitle } from "@components/headings";
|
||||||
import { TextArea } from "../../components/inputs/input";
|
import { TextArea } from "@components/inputs/input";
|
||||||
import { FilterActions } from "./action";
|
import { FilterActions } from "./action";
|
||||||
|
import { filterKeys } from "./list";
|
||||||
|
|
||||||
interface tabType {
|
interface tabType {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -144,37 +145,49 @@ export default function FilterDetails() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { filterId } = useParams<{ filterId: string }>();
|
const { filterId } = useParams<{ filterId: string }>();
|
||||||
|
|
||||||
const { isLoading, data: filter } = useQuery(
|
if (filterId === "0" || undefined) {
|
||||||
["filters", filterId],
|
navigate("/filters");
|
||||||
() => APIClient.filters.getByID(parseInt(filterId ?? "0")),
|
}
|
||||||
{
|
|
||||||
retry: false,
|
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
onError: () => navigate("./")
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const updateMutation = useMutation(
|
const id = parseInt(filterId!);
|
||||||
(filter: Filter) => APIClient.filters.update(filter),
|
|
||||||
{
|
|
||||||
onSuccess: (_, currentFilter) => {
|
|
||||||
toast.custom((t) => (
|
|
||||||
<Toast type="success" body={`${currentFilter.name} was updated successfully`} t={t} />
|
|
||||||
));
|
|
||||||
queryClient.refetchQueries(["filters"]);
|
|
||||||
// queryClient.invalidateQueries(["filters", currentFilter.id]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const deleteMutation = useMutation((id: number) => APIClient.filters.delete(id), {
|
const { isLoading, data: filter } = useQuery({
|
||||||
|
queryKey: filterKeys.detail(id),
|
||||||
|
queryFn: ({ queryKey }) => APIClient.filters.getByID(queryKey[2]),
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
onError: () => {
|
||||||
|
navigate("/filters");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateMutation = useMutation({
|
||||||
|
mutationFn: (filter: Filter) => APIClient.filters.update(filter),
|
||||||
|
onSuccess: (newFilter, variables) => {
|
||||||
|
queryClient.setQueryData(filterKeys.detail(variables.id), newFilter);
|
||||||
|
|
||||||
|
queryClient.setQueryData<Filter[]>(filterKeys.lists(), (previous) => {
|
||||||
|
if (previous) {
|
||||||
|
return previous.map((filter: Filter) => (filter.id === variables.id ? newFilter : filter));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
toast.custom((t) => (
|
||||||
|
<Toast type="success" body={`${newFilter.name} was updated successfully`} t={t}/>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const deleteMutation = useMutation({
|
||||||
|
mutationFn: (id: number) => APIClient.filters.delete(id),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
// Invalidate filters just in case, most likely not necessary but can't hurt.
|
||||||
|
queryClient.invalidateQueries({ queryKey: filterKeys.lists() });
|
||||||
|
queryClient.invalidateQueries({ queryKey: filterKeys.detail(id) });
|
||||||
|
|
||||||
toast.custom((t) => (
|
toast.custom((t) => (
|
||||||
<Toast type="success" body={`${filter?.name} was deleted`} t={t} />
|
<Toast type="success" body={`${filter?.name} was deleted`} t={t} />
|
||||||
));
|
));
|
||||||
|
|
||||||
// Invalidate filters just in case, most likely not necessary but can't hurt.
|
|
||||||
queryClient.invalidateQueries(["filters"]);
|
|
||||||
|
|
||||||
// redirect
|
// redirect
|
||||||
navigate("/filters");
|
navigate("/filters");
|
||||||
|
@ -234,7 +247,7 @@ export default function FilterDetails() {
|
||||||
initialValues={{
|
initialValues={{
|
||||||
id: filter.id,
|
id: filter.id,
|
||||||
name: filter.name,
|
name: filter.name,
|
||||||
enabled: filter.enabled || false,
|
enabled: filter.enabled,
|
||||||
min_size: filter.min_size,
|
min_size: filter.min_size,
|
||||||
max_size: filter.max_size,
|
max_size: filter.max_size,
|
||||||
delay: filter.delay,
|
delay: filter.delay,
|
||||||
|
@ -298,6 +311,7 @@ export default function FilterDetails() {
|
||||||
external_webhook_expect_status: filter.external_webhook_expect_status || 0
|
external_webhook_expect_status: filter.external_webhook_expect_status || 0
|
||||||
} as Filter}
|
} as Filter}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
|
enableReinitialize={true}
|
||||||
>
|
>
|
||||||
{({ values, dirty, resetForm }) => (
|
{({ values, dirty, resetForm }) => (
|
||||||
<Form>
|
<Form>
|
||||||
|
@ -322,11 +336,11 @@ export default function FilterDetails() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function General(){
|
export function General(){
|
||||||
const { isLoading, data: indexers } = useQuery(
|
const { isLoading, data: indexers } = useQuery({
|
||||||
["filters", "indexer_list"],
|
queryKey: ["filters", "indexer_list"],
|
||||||
() => APIClient.indexers.getOptions(),
|
queryFn: APIClient.indexers.getOptions,
|
||||||
{ refetchOnWindowFocus: false }
|
refetchOnWindowFocus: false
|
||||||
);
|
});
|
||||||
|
|
||||||
const opts = indexers && indexers.length > 0 ? indexers.map(v => ({
|
const opts = indexers && indexers.length > 0 ? indexers.map(v => ({
|
||||||
label: v.name,
|
label: v.name,
|
||||||
|
|
|
@ -2,11 +2,10 @@ import { Dispatch, FC, Fragment, MouseEventHandler, useReducer, useRef, useState
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import { Listbox, Menu, Switch, Transition } from "@headlessui/react";
|
import { Listbox, Menu, Switch, Transition } from "@headlessui/react";
|
||||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { FormikValues } from "formik";
|
import { FormikValues } from "formik";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { Tooltip } from "react-tooltip";
|
import { Tooltip } from "react-tooltip";
|
||||||
import { FilterListContext, FilterListState } from "../../utils/Context";
|
|
||||||
import {
|
import {
|
||||||
ArrowsRightLeftIcon,
|
ArrowsRightLeftIcon,
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
|
@ -18,15 +17,25 @@ import {
|
||||||
ChatBubbleBottomCenterTextIcon,
|
ChatBubbleBottomCenterTextIcon,
|
||||||
TrashIcon
|
TrashIcon
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
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 { EmptyListState } from "../../components/emptystates";
|
|
||||||
import { DeleteModal } from "../../components/modals";
|
|
||||||
import { ArrowDownTrayIcon } from "@heroicons/react/24/solid";
|
import { ArrowDownTrayIcon } from "@heroicons/react/24/solid";
|
||||||
|
|
||||||
|
import { FilterListContext, FilterListState } from "@utils/Context";
|
||||||
|
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 { EmptyListState } from "@components/emptystates";
|
||||||
|
import { DeleteModal } from "@components/modals";
|
||||||
|
|
||||||
|
export const filterKeys = {
|
||||||
|
all: ["filters"] as const,
|
||||||
|
lists: () => [...filterKeys.all, "list"] as const,
|
||||||
|
list: (indexers: string[], sortOrder: string) => [...filterKeys.lists(), { indexers, sortOrder }] as const,
|
||||||
|
details: () => [...filterKeys.all, "detail"] as const,
|
||||||
|
detail: (id: number) => [...filterKeys.details(), id] as const
|
||||||
|
};
|
||||||
|
|
||||||
enum ActionType {
|
enum ActionType {
|
||||||
INDEXER_FILTER_CHANGE = "INDEXER_FILTER_CHANGE",
|
INDEXER_FILTER_CHANGE = "INDEXER_FILTER_CHANGE",
|
||||||
INDEXER_FILTER_RESET = "INDEXER_FILTER_RESET",
|
INDEXER_FILTER_RESET = "INDEXER_FILTER_RESET",
|
||||||
|
@ -63,11 +72,7 @@ const FilterListReducer = (state: FilterListState, action: Actions): FilterListS
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
interface FilterProps {
|
export default function Filters() {
|
||||||
values?: FormikValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Filters({}: FilterProps){
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const [createFilterIsOpen, setCreateFilterIsOpen] = useState(false);
|
const [createFilterIsOpen, setCreateFilterIsOpen] = useState(false);
|
||||||
|
@ -115,7 +120,7 @@ export default function Filters({}: FilterProps){
|
||||||
await APIClient.filters.create(newFilter);
|
await APIClient.filters.create(newFilter);
|
||||||
|
|
||||||
// Update the filter list
|
// Update the filter list
|
||||||
queryClient.invalidateQueries("filters");
|
queryClient.invalidateQueries({ queryKey: filterKeys.lists() });
|
||||||
|
|
||||||
toast.custom((t) => <Toast type="success" body="Filter imported successfully." t={t} />);
|
toast.custom((t) => <Toast type="success" body="Filter imported successfully." t={t} />);
|
||||||
setShowImportModal(false);
|
setShowImportModal(false);
|
||||||
|
@ -146,7 +151,7 @@ export default function Filters({}: FilterProps){
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PlusIcon className="h-5 w-5 mr-1" />
|
<PlusIcon className="h-5 w-5 mr-1" />
|
||||||
Add Filter
|
Add Filter
|
||||||
</button>
|
</button>
|
||||||
<Menu.Button className="relative inline-flex items-center px-2 py-2 border-l border-spacing-1 dark:border-black shadow-sm text-sm font-medium rounded-r-md text-white bg-blue-600 dark:bg-blue-600 hover:bg-blue-700 dark:hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-blue-500">
|
<Menu.Button className="relative inline-flex items-center px-2 py-2 border-l border-spacing-1 dark:border-black shadow-sm text-sm font-medium rounded-r-md text-white bg-blue-600 dark:bg-blue-600 hover:bg-blue-700 dark:hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-blue-500">
|
||||||
<ChevronDownIcon className="h-5 w-5" />
|
<ChevronDownIcon className="h-5 w-5" />
|
||||||
|
@ -172,7 +177,7 @@ export default function Filters({}: FilterProps){
|
||||||
} w-full text-left py-2 px-4 text-sm font-medium text-gray-700 dark:text-gray-200 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-blue-500`}
|
} w-full text-left py-2 px-4 text-sm font-medium text-gray-700 dark:text-gray-200 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-blue-500`}
|
||||||
onClick={() => setShowImportModal(true)}
|
onClick={() => setShowImportModal(true)}
|
||||||
>
|
>
|
||||||
Import Filter
|
Import Filter
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
@ -250,11 +255,11 @@ function FilterList({ toggleCreateFilter }: any) {
|
||||||
filterListState
|
filterListState
|
||||||
);
|
);
|
||||||
|
|
||||||
const { error, data } = useQuery(
|
const { data, error } = useQuery({
|
||||||
["filters", indexerFilter, sortOrder],
|
queryKey: filterKeys.list(indexerFilter, sortOrder),
|
||||||
() => APIClient.filters.find(indexerFilter, sortOrder),
|
queryFn: ({ queryKey }) => APIClient.filters.find(queryKey[2].indexers, queryKey[2].sortOrder),
|
||||||
{ refetchOnWindowFocus: false }
|
refetchOnWindowFocus: false
|
||||||
);
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
FilterListContext.set({ indexerFilter, sortOrder, status });
|
FilterListContext.set({ indexerFilter, sortOrder, status });
|
||||||
|
@ -284,9 +289,13 @@ function FilterList({ toggleCreateFilter }: any) {
|
||||||
|
|
||||||
{data && data.length > 0 ? (
|
{data && data.length > 0 ? (
|
||||||
<ol className="min-w-full">
|
<ol className="min-w-full">
|
||||||
{filtered.filtered.map((filter: Filter, idx) => (
|
{filtered.filtered.length > 0
|
||||||
<FilterListItem filter={filter} values={filter} key={filter.id} idx={idx} />
|
? filtered.filtered.map((filter: Filter, idx) => (
|
||||||
))}
|
<FilterListItem filter={filter} values={filter} key={filter.id} idx={idx} />
|
||||||
|
))
|
||||||
|
|
||||||
|
: <EmptyListState text={`No ${status} filters`} />
|
||||||
|
}
|
||||||
</ol>
|
</ol>
|
||||||
) : (
|
) : (
|
||||||
<EmptyListState text="No filters here.." buttonText="Add new" buttonOnClick={toggleCreateFilter} />
|
<EmptyListState text="No filters here.." buttonText="Add new" buttonOnClick={toggleCreateFilter} />
|
||||||
|
@ -444,28 +453,25 @@ const FilterItemDropdown = ({ filter, onToggle }: FilterItemDropdownProps) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false);
|
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false);
|
||||||
const deleteMutation = useMutation(
|
|
||||||
(id: number) => APIClient.filters.delete(id),
|
|
||||||
{
|
|
||||||
onSuccess: () => {
|
|
||||||
queryClient.invalidateQueries(["filters"]);
|
|
||||||
queryClient.invalidateQueries(["filters", filter.id]);
|
|
||||||
|
|
||||||
toast.custom((t) => <Toast type="success" body={`Filter ${filter?.name} was deleted`} t={t} />);
|
const deleteMutation = useMutation({
|
||||||
}
|
mutationFn: (id: number) => APIClient.filters.delete(id),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: filterKeys.lists() });
|
||||||
|
queryClient.invalidateQueries({ queryKey: filterKeys.detail(filter.id) });
|
||||||
|
|
||||||
|
toast.custom((t) => <Toast type="success" body={`Filter ${filter?.name} was deleted`} t={t} />);
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
const duplicateMutation = useMutation(
|
const duplicateMutation = useMutation({
|
||||||
(id: number) => APIClient.filters.duplicate(id),
|
mutationFn: (id: number) => APIClient.filters.duplicate(id),
|
||||||
{
|
onSuccess: () => {
|
||||||
onSuccess: () => {
|
queryClient.invalidateQueries({ queryKey: filterKeys.lists() });
|
||||||
queryClient.invalidateQueries(["filters"]);
|
|
||||||
|
|
||||||
toast.custom((t) => <Toast type="success" body={`Filter ${filter?.name} duplicated`} t={t} />);
|
toast.custom((t) => <Toast type="success" body={`Filter ${filter?.name} duplicated`} t={t} />);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu as="div">
|
<Menu as="div">
|
||||||
|
@ -634,26 +640,22 @@ interface FilterListItemProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function FilterListItem({ filter, values, idx }: FilterListItemProps) {
|
function FilterListItem({ filter, values, idx }: FilterListItemProps) {
|
||||||
const [enabled, setEnabled] = useState(filter.enabled);
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const updateMutation = useMutation(
|
const updateMutation = useMutation({
|
||||||
(status: boolean) => APIClient.filters.toggleEnable(filter.id, status),
|
mutationFn: (status: boolean) => APIClient.filters.toggleEnable(filter.id, status),
|
||||||
{
|
onSuccess: () => {
|
||||||
onSuccess: () => {
|
toast.custom((t) => <Toast type="success" body={`${filter.name} was ${!filter.enabled ? "disabled" : "enabled"} successfully`} t={t} />);
|
||||||
toast.custom((t) => <Toast type="success" body={`${filter.name} was ${enabled ? "disabled" : "enabled"} successfully`} t={t} />);
|
|
||||||
|
|
||||||
// We need to invalidate both keys here.
|
// We need to invalidate both keys here.
|
||||||
// The filters key is used on the /filters page,
|
// The filters key is used on the /filters page,
|
||||||
// while the ["filter", filter.id] key is used on the details page.
|
// while the ["filter", filter.id] key is used on the details page.
|
||||||
queryClient.invalidateQueries(["filters"]);
|
queryClient.invalidateQueries({ queryKey: filterKeys.lists() });
|
||||||
queryClient.invalidateQueries(["filters", filter?.id]);
|
queryClient.invalidateQueries({ queryKey: filterKeys.detail(filter.id) });
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
const toggleActive = (status: boolean) => {
|
const toggleActive = (status: boolean) => {
|
||||||
setEnabled(status);
|
|
||||||
updateMutation.mutate(status);
|
updateMutation.mutate(status);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -671,10 +673,10 @@ function FilterListItem({ filter, values, idx }: FilterListItemProps) {
|
||||||
className="px-4 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-100"
|
className="px-4 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-100"
|
||||||
>
|
>
|
||||||
<Switch
|
<Switch
|
||||||
checked={enabled}
|
checked={filter.enabled}
|
||||||
onChange={toggleActive}
|
onChange={toggleActive}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
enabled ? "bg-blue-500 dark:bg-blue-500" : "bg-gray-200 dark:bg-gray-700",
|
filter.enabled ? "bg-blue-500 dark:bg-blue-500" : "bg-gray-200 dark:bg-gray-700",
|
||||||
"relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
"relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
@ -682,7 +684,7 @@ function FilterListItem({ filter, values, idx }: FilterListItemProps) {
|
||||||
<span
|
<span
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className={classNames(
|
className={classNames(
|
||||||
enabled ? "translate-x-5" : "translate-x-0",
|
filter.enabled ? "translate-x-5" : "translate-x-0",
|
||||||
"inline-block h-5 w-5 rounded-full bg-white dark:bg-gray-200 shadow transform ring-0 transition ease-in-out duration-200"
|
"inline-block h-5 w-5 rounded-full bg-white dark:bg-gray-200 shadow transform ring-0 transition ease-in-out duration-200"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
@ -730,7 +732,7 @@ function FilterListItem({ filter, values, idx }: FilterListItemProps) {
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span className="text-sm text-gray-800 dark:text-gray-500">
|
<span className="text-sm text-gray-800 dark:text-gray-500">
|
||||||
<Tooltip style={{ width: "350px", fontSize: "12px", textTransform: "none", fontWeight: "normal", borderRadius: "0.375rem", backgroundColor: "#34343A", color: "#fff", opacity: "1", whiteSpace: "pre-wrap", overflow: "hidden", textOverflow: "ellipsis" }} delayShow={100} delayHide={150} data-html={true} place="right" anchorId={`tooltip-actions-${filter.id}`} html="<p>You need to setup an action in the filter otherwise you will not get any snatches.</p>" />
|
<Tooltip style={{ width: "350px", fontSize: "12px", textTransform: "none", fontWeight: "normal", borderRadius: "0.375rem", backgroundColor: "#34343A", color: "#fff", opacity: "1", whiteSpace: "pre-wrap", overflow: "hidden", textOverflow: "ellipsis" }} delayShow={100} delayHide={150} data-html={true} place="right" data-tooltip-id={`tooltip-actions-${filter.id}`} html="<p>You need to setup an action in the filter otherwise you will not get any snatches.</p>" />
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -848,14 +850,12 @@ const ListboxFilter = ({
|
||||||
|
|
||||||
// a unique option from a list
|
// a unique option from a list
|
||||||
const IndexerSelectFilter = ({ dispatch }: any) => {
|
const IndexerSelectFilter = ({ dispatch }: any) => {
|
||||||
const { data, isSuccess } = useQuery(
|
const { data, isSuccess } = useQuery({
|
||||||
"indexers_options",
|
queryKey: ["filters","indexers_options"],
|
||||||
() => APIClient.indexers.getOptions(),
|
queryFn: () => APIClient.indexers.getOptions(),
|
||||||
{
|
keepPreviousData: true,
|
||||||
keepPreviousData: true,
|
staleTime: Infinity
|
||||||
staleTime: Infinity
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const setFilter = (value: string) => {
|
const setFilter = (value: string) => {
|
||||||
if (value == undefined || value == "") {
|
if (value == undefined || value == "") {
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { Listbox, Transition } from "@headlessui/react";
|
import { Listbox, Transition } from "@headlessui/react";
|
||||||
import { CheckIcon, ChevronDownIcon } from "@heroicons/react/24/solid";
|
import { CheckIcon, ChevronDownIcon } from "@heroicons/react/24/solid";
|
||||||
|
|
||||||
import { APIClient } from "../../api/APIClient";
|
import { APIClient } from "@api/APIClient";
|
||||||
import { classNames } from "../../utils";
|
import { classNames } from "@utils";
|
||||||
import { PushStatusOptions } from "../../domain/constants";
|
import { PushStatusOptions } from "@domain/constants";
|
||||||
import { FilterProps } from "react-table";
|
import { FilterProps } from "react-table";
|
||||||
import { DebounceInput } from "react-debounce-input";
|
import { DebounceInput } from "react-debounce-input";
|
||||||
|
|
||||||
|
@ -62,14 +62,12 @@ const ListboxFilter = ({
|
||||||
export const IndexerSelectColumnFilter = ({
|
export const IndexerSelectColumnFilter = ({
|
||||||
column: { filterValue, setFilter, id }
|
column: { filterValue, setFilter, id }
|
||||||
}: FilterProps<object>) => {
|
}: FilterProps<object>) => {
|
||||||
const { data, isSuccess } = useQuery(
|
const { data, isSuccess } = useQuery({
|
||||||
"indexer_options",
|
queryKey: ["indexer_options"],
|
||||||
() => APIClient.release.indexerOptions(),
|
queryFn: () => APIClient.release.indexerOptions(),
|
||||||
{
|
keepPreviousData: true,
|
||||||
keepPreviousData: true,
|
staleTime: Infinity
|
||||||
staleTime: Infinity
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Render a multi-select box
|
// Render a multi-select box
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { CellProps, Column, useFilters, usePagination, useSortBy, useTable } from "react-table";
|
import { CellProps, Column, useFilters, usePagination, useSortBy, useTable } from "react-table";
|
||||||
import {
|
import {
|
||||||
ChevronDoubleLeftIcon,
|
ChevronDoubleLeftIcon,
|
||||||
|
@ -8,16 +8,25 @@ import {
|
||||||
ChevronRightIcon
|
ChevronRightIcon
|
||||||
} from "@heroicons/react/24/solid";
|
} from "@heroicons/react/24/solid";
|
||||||
|
|
||||||
import { APIClient } from "../../api/APIClient";
|
import { APIClient } from "@api/APIClient";
|
||||||
import { EmptyListState } from "../../components/emptystates";
|
import { EmptyListState } from "@components/emptystates";
|
||||||
|
|
||||||
import * as Icons from "../../components/Icons";
|
import * as Icons from "@components/Icons";
|
||||||
import * as DataTable from "../../components/data-table";
|
import * as DataTable from "@components/data-table";
|
||||||
|
|
||||||
import { IndexerSelectColumnFilter, PushStatusSelectColumnFilter, SearchColumnFilter } from "./Filters";
|
import { IndexerSelectColumnFilter, PushStatusSelectColumnFilter, SearchColumnFilter } from "./Filters";
|
||||||
import { classNames } from "../../utils";
|
import { classNames } from "@utils";
|
||||||
import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline";
|
import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline";
|
||||||
import { Tooltip } from "../../components/tooltips/Tooltip";
|
import { Tooltip } from "@components/tooltips/Tooltip";
|
||||||
|
|
||||||
|
export const releaseKeys = {
|
||||||
|
all: ["releases"] as const,
|
||||||
|
lists: () => [...releaseKeys.all, "list"] as const,
|
||||||
|
list: (pageIndex: number, pageSize: number, filters: ReleaseFilter[]) => [...releaseKeys.lists(), { pageIndex, pageSize, filters }] as const,
|
||||||
|
details: () => [...releaseKeys.all, "detail"] as const,
|
||||||
|
detail: (id: number) => [...releaseKeys.details(), id] as const
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
type TableState = {
|
type TableState = {
|
||||||
queryPageIndex: number;
|
queryPageIndex: number;
|
||||||
|
@ -120,14 +129,12 @@ export const ReleaseTable = () => {
|
||||||
const [{ queryPageIndex, queryPageSize, totalCount, queryFilters }, dispatch] =
|
const [{ queryPageIndex, queryPageSize, totalCount, queryFilters }, dispatch] =
|
||||||
React.useReducer(TableReducer, initialState);
|
React.useReducer(TableReducer, initialState);
|
||||||
|
|
||||||
const { isLoading, error, data, isSuccess } = useQuery(
|
const { isLoading, error, data, isSuccess } = useQuery({
|
||||||
["releases", queryPageIndex, queryPageSize, queryFilters],
|
queryKey: releaseKeys.list(queryPageIndex, queryPageSize, queryFilters),
|
||||||
() => APIClient.release.findQuery(queryPageIndex * queryPageSize, queryPageSize, queryFilters),
|
queryFn: () => APIClient.release.findQuery(queryPageIndex * queryPageSize, queryPageSize, queryFilters),
|
||||||
{
|
keepPreviousData: true,
|
||||||
keepPreviousData: true,
|
staleTime: 5000
|
||||||
staleTime: 5000
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Use the state and functions returned from useTable to build your UI
|
// Use the state and functions returned from useTable to build your UI
|
||||||
const {
|
const {
|
||||||
|
@ -192,10 +199,11 @@ export const ReleaseTable = () => {
|
||||||
dispatch({ type: ActionType.FILTER_CHANGED, payload: filters });
|
dispatch({ type: ActionType.FILTER_CHANGED, payload: filters });
|
||||||
}, [filters]);
|
}, [filters]);
|
||||||
|
|
||||||
if (error)
|
if (error) {
|
||||||
return <p>Error</p>;
|
return <p>Error</p>;
|
||||||
|
}
|
||||||
|
|
||||||
if (isLoading)
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col animate-pulse">
|
<div className="flex flex-col animate-pulse">
|
||||||
<div className="flex mb-6 flex-col sm:flex-row">
|
<div className="flex mb-6 flex-col sm:flex-row">
|
||||||
|
@ -211,9 +219,7 @@ export const ReleaseTable = () => {
|
||||||
<table {...getTableProps()} className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
<table {...getTableProps()} className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
<thead className="bg-gray-50 dark:bg-gray-800">
|
<thead className="bg-gray-50 dark:bg-gray-800">
|
||||||
<tr>
|
<tr>
|
||||||
|
|
||||||
<th
|
<th
|
||||||
|
|
||||||
scope="col"
|
scope="col"
|
||||||
className="first:pl-5 pl-3 pr-3 py-3 first:rounded-tl-md last:rounded-tr-md text-xs font-medium tracking-wider text-left text-gray-500 uppercase group"
|
className="first:pl-5 pl-3 pr-3 py-3 first:rounded-tl-md last:rounded-tr-md text-xs font-medium tracking-wider text-left text-gray-500 uppercase group"
|
||||||
|
|
||||||
|
@ -224,28 +230,30 @@ export const ReleaseTable = () => {
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className=" divide-gray-200 dark:divide-gray-700">
|
<tbody className=" divide-gray-200 dark:divide-gray-700">
|
||||||
<tr className="flex justify-between py-4 text-sm font-medium box-content text-gray-900 dark:text-gray-300 max-w-[96px] sm:max-w-[216px] md:max-w-[360px] lg:max-w-[640px] xl:max-w-[840px]">
|
<tr
|
||||||
|
className="flex justify-between py-4 text-sm font-medium box-content text-gray-900 dark:text-gray-300 max-w-[96px] sm:max-w-[216px] md:max-w-[360px] lg:max-w-[640px] xl:max-w-[840px]">
|
||||||
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="flex justify-between py-4 text-sm font-medium box-content text-gray-900 dark:text-gray-300 max-w-[96px] sm:max-w-[216px] md:max-w-[360px] lg:max-w-[640px] xl:max-w-[840px]">
|
<tr
|
||||||
|
className="flex justify-between py-4 text-sm font-medium box-content text-gray-900 dark:text-gray-300 max-w-[96px] sm:max-w-[216px] md:max-w-[360px] lg:max-w-[640px] xl:max-w-[840px]">
|
||||||
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="flex justify-between py-4 text-sm font-medium box-content text-gray-900 dark:text-gray-300 max-w-[96px] sm:max-w-[216px] md:max-w-[360px] lg:max-w-[640px] xl:max-w-[840px]">
|
<tr
|
||||||
|
className="flex justify-between py-4 text-sm font-medium box-content text-gray-900 dark:text-gray-300 max-w-[96px] sm:max-w-[216px] md:max-w-[360px] lg:max-w-[640px] xl:max-w-[840px]">
|
||||||
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap"> </td>
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap"> </td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="flex justify-between py-4 text-sm font-medium box-content text-gray-900 dark:text-gray-300 max-w-[96px] sm:max-w-[216px] md:max-w-[360px] lg:max-w-[640px] xl:max-w-[840px]">
|
<tr
|
||||||
|
className="flex justify-between py-4 text-sm font-medium box-content text-gray-900 dark:text-gray-300 max-w-[96px] sm:max-w-[216px] md:max-w-[360px] lg:max-w-[640px] xl:max-w-[840px]">
|
||||||
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
|
@ -255,27 +263,32 @@ export const ReleaseTable = () => {
|
||||||
<p className="text-black dark:text-white">Loading release table...</p>
|
<p className="text-black dark:text-white">Loading release table...</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="flex justify-between py-3 text-sm font-medium box-content text-gray-900 dark:text-gray-300 max-w-[96px] sm:max-w-[216px] md:max-w-[360px] lg:max-w-[640px] xl:max-w-[840px]">
|
<tr
|
||||||
|
className="flex justify-between py-3 text-sm font-medium box-content text-gray-900 dark:text-gray-300 max-w-[96px] sm:max-w-[216px] md:max-w-[360px] lg:max-w-[640px] xl:max-w-[840px]">
|
||||||
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap"> </td>
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap"> </td>
|
||||||
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="flex justify-between py-3 text-sm font-medium box-content text-gray-900 dark:text-gray-300 max-w-[96px] sm:max-w-[216px] md:max-w-[360px] lg:max-w-[640px] xl:max-w-[840px]">
|
<tr
|
||||||
|
className="flex justify-between py-3 text-sm font-medium box-content text-gray-900 dark:text-gray-300 max-w-[96px] sm:max-w-[216px] md:max-w-[360px] lg:max-w-[640px] xl:max-w-[840px]">
|
||||||
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="flex justify-between py-3 text-sm font-medium box-content text-gray-900 dark:text-gray-300 max-w-[96px] sm:max-w-[216px] md:max-w-[360px] lg:max-w-[640px] xl:max-w-[840px]">
|
<tr
|
||||||
|
className="flex justify-between py-3 text-sm font-medium box-content text-gray-900 dark:text-gray-300 max-w-[96px] sm:max-w-[216px] md:max-w-[360px] lg:max-w-[640px] xl:max-w-[840px]">
|
||||||
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="flex justify-between py-3 text-sm font-medium box-content text-gray-900 dark:text-gray-300 max-w-[96px] sm:max-w-[216px] md:max-w-[360px] lg:max-w-[640px] xl:max-w-[840px]">
|
<tr
|
||||||
|
className="flex justify-between py-3 text-sm font-medium box-content text-gray-900 dark:text-gray-300 max-w-[96px] sm:max-w-[216px] md:max-w-[360px] lg:max-w-[640px] xl:max-w-[840px]">
|
||||||
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="flex justify-between py-3 text-sm font-medium box-content text-gray-900 dark:text-gray-300 max-w-[96px] sm:max-w-[216px] md:max-w-[360px] lg:max-w-[640px] xl:max-w-[840px]">
|
<tr
|
||||||
|
className="flex justify-between py-3 text-sm font-medium box-content text-gray-900 dark:text-gray-300 max-w-[96px] sm:max-w-[216px] md:max-w-[360px] lg:max-w-[640px] xl:max-w-[840px]">
|
||||||
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
|
@ -292,7 +305,8 @@ export const ReleaseTable = () => {
|
||||||
<div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
<div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
||||||
<div className="flex items-baseline gap-x-2">
|
<div className="flex items-baseline gap-x-2">
|
||||||
<span className="text-sm text-gray-700 dark:text-gray-500">
|
<span className="text-sm text-gray-700 dark:text-gray-500">
|
||||||
Page <span className="font-medium">{pageIndex + 1}</span> of <span className="font-medium">{pageOptions.length}</span>
|
Page <span className="font-medium">{pageIndex + 1}</span> of <span
|
||||||
|
className="font-medium">{pageOptions.length}</span>
|
||||||
</span>
|
</span>
|
||||||
<label>
|
<label>
|
||||||
<span className="sr-only bg-gray-700">Items Per Page</span>
|
<span className="sr-only bg-gray-700">Items Per Page</span>
|
||||||
|
@ -305,7 +319,7 @@ export const ReleaseTable = () => {
|
||||||
>
|
>
|
||||||
{[5, 10, 20, 50].map(pageSize => (
|
{[5, 10, 20, 50].map(pageSize => (
|
||||||
<option key={pageSize} value={pageSize}>
|
<option key={pageSize} value={pageSize}>
|
||||||
Show {pageSize}
|
Show {pageSize}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
@ -319,20 +333,20 @@ export const ReleaseTable = () => {
|
||||||
disabled={!canPreviousPage}
|
disabled={!canPreviousPage}
|
||||||
>
|
>
|
||||||
<span className="sr-only text-gray-400 dark:text-gray-500 dark:bg-gray-700">First</span>
|
<span className="sr-only text-gray-400 dark:text-gray-500 dark:bg-gray-700">First</span>
|
||||||
<ChevronDoubleLeftIcon className="w-4 h-4 text-gray-400 dark:text-gray-500" aria-hidden="true" />
|
<ChevronDoubleLeftIcon className="w-4 h-4 text-gray-400 dark:text-gray-500" aria-hidden="true"/>
|
||||||
</DataTable.PageButton>
|
</DataTable.PageButton>
|
||||||
<DataTable.PageButton
|
<DataTable.PageButton
|
||||||
onClick={() => previousPage()}
|
onClick={() => previousPage()}
|
||||||
disabled={!canPreviousPage}
|
disabled={!canPreviousPage}
|
||||||
>
|
>
|
||||||
<span className="sr-only text-gray-400 dark:text-gray-500 dark:bg-gray-700">Previous</span>
|
<span className="sr-only text-gray-400 dark:text-gray-500 dark:bg-gray-700">Previous</span>
|
||||||
<ChevronLeftIcon className="w-4 h-4 text-gray-400 dark:text-gray-500" aria-hidden="true" />
|
<ChevronLeftIcon className="w-4 h-4 text-gray-400 dark:text-gray-500" aria-hidden="true"/>
|
||||||
</DataTable.PageButton>
|
</DataTable.PageButton>
|
||||||
<DataTable.PageButton
|
<DataTable.PageButton
|
||||||
onClick={() => nextPage()}
|
onClick={() => nextPage()}
|
||||||
disabled={!canNextPage}>
|
disabled={!canNextPage}>
|
||||||
<span className="sr-only text-gray-400 dark:text-gray-500 dark:bg-gray-700">Next</span>
|
<span className="sr-only text-gray-400 dark:text-gray-500 dark:bg-gray-700">Next</span>
|
||||||
<ChevronRightIcon className="w-4 h-4 text-gray-400 dark:text-gray-500" aria-hidden="true" />
|
<ChevronRightIcon className="w-4 h-4 text-gray-400 dark:text-gray-500" aria-hidden="true"/>
|
||||||
</DataTable.PageButton>
|
</DataTable.PageButton>
|
||||||
<DataTable.PageButton
|
<DataTable.PageButton
|
||||||
className="rounded-r-md"
|
className="rounded-r-md"
|
||||||
|
@ -340,7 +354,7 @@ export const ReleaseTable = () => {
|
||||||
disabled={!canNextPage}
|
disabled={!canNextPage}
|
||||||
>
|
>
|
||||||
<span className="sr-only text-gray-400 dark:text-gray-500 dark:bg-gray-700">Last</span>
|
<span className="sr-only text-gray-400 dark:text-gray-500 dark:bg-gray-700">Last</span>
|
||||||
<ChevronDoubleRightIcon className="w-4 h-4 text-gray-400 dark:text-gray-500" aria-hidden="true" />
|
<ChevronDoubleRightIcon className="w-4 h-4 text-gray-400 dark:text-gray-500" aria-hidden="true"/>
|
||||||
</DataTable.PageButton>
|
</DataTable.PageButton>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
@ -349,9 +363,11 @@ export const ReleaseTable = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!data)
|
if (!data) {
|
||||||
return <EmptyListState text="No recent activity" />;
|
return <EmptyListState text="No recent activity" />;
|
||||||
|
}
|
||||||
|
|
||||||
// Render the UI for your table
|
// Render the UI for your table
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,20 +1,31 @@
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { KeyField } from "../../components/fields/text";
|
|
||||||
import { DeleteModal } from "../../components/modals";
|
|
||||||
import APIKeyAddForm from "../../forms/settings/APIKeyAddForm";
|
|
||||||
import Toast from "../../components/notifications/Toast";
|
|
||||||
import { APIClient } from "../../api/APIClient";
|
|
||||||
import { useToggle } from "../../hooks/hooks";
|
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import { classNames } from "../../utils";
|
|
||||||
import { TrashIcon } from "@heroicons/react/24/outline";
|
import { TrashIcon } from "@heroicons/react/24/outline";
|
||||||
import { EmptySimple } from "../../components/emptystates";
|
|
||||||
|
import { KeyField } from "@components/fields/text";
|
||||||
|
import { DeleteModal } from "@components/modals";
|
||||||
|
import APIKeyAddForm from "@forms/settings/APIKeyAddForm";
|
||||||
|
import Toast from "@components/notifications/Toast";
|
||||||
|
import { APIClient } from "@api/APIClient";
|
||||||
|
import { useToggle } from "@hooks/hooks";
|
||||||
|
import { classNames } from "@utils";
|
||||||
|
import { EmptySimple } from "@components/emptystates";
|
||||||
|
|
||||||
|
export const apiKeys = {
|
||||||
|
all: ["api_keys"] as const,
|
||||||
|
lists: () => [...apiKeys.all, "list"] as const,
|
||||||
|
details: () => [...apiKeys.all, "detail"] as const,
|
||||||
|
// detail: (id: number) => [...apiKeys.details(), id] as const
|
||||||
|
detail: (id: string) => [...apiKeys.details(), id] as const
|
||||||
|
};
|
||||||
|
|
||||||
function APISettings() {
|
function APISettings() {
|
||||||
const [addFormIsOpen, toggleAddForm] = useToggle(false);
|
const [addFormIsOpen, toggleAddForm] = useToggle(false);
|
||||||
|
|
||||||
const { data } = useQuery(["apikeys"], () => APIClient.apikeys.getAll(), {
|
const { data } = useQuery({
|
||||||
|
queryKey: apiKeys.lists(),
|
||||||
|
queryFn: APIClient.apikeys.getAll,
|
||||||
retry: false,
|
retry: false,
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
onError: (err) => console.log(err)
|
onError: (err) => console.log(err)
|
||||||
|
@ -57,7 +68,7 @@ function APISettings() {
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
{data && data.map((k) => <APIListItem key={k.key} apikey={k} />)}
|
{data && data.map((k, idx) => <APIListItem key={idx} apikey={k} />)}
|
||||||
</ol>
|
</ol>
|
||||||
</section>
|
</section>
|
||||||
) : (
|
) : (
|
||||||
|
@ -83,23 +94,21 @@ function APIListItem({ apikey }: ApiKeyItemProps) {
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const deleteMutation = useMutation(
|
const deleteMutation = useMutation({
|
||||||
(key: string) => APIClient.apikeys.delete(key),
|
mutationFn: (key: string) => APIClient.apikeys.delete(key),
|
||||||
{
|
onSuccess: () => {
|
||||||
onSuccess: () => {
|
queryClient.invalidateQueries({ queryKey: apiKeys.lists() });
|
||||||
queryClient.invalidateQueries(["apikeys"]);
|
queryClient.invalidateQueries({ queryKey: apiKeys.detail(apikey.key) });
|
||||||
queryClient.invalidateQueries(["apikeys", apikey.key]);
|
|
||||||
|
|
||||||
toast.custom((t) => (
|
toast.custom((t) => (
|
||||||
<Toast
|
<Toast
|
||||||
type="success"
|
type="success"
|
||||||
body={`API key ${apikey?.name} was deleted`}
|
body={`API key ${apikey?.name} was deleted`}
|
||||||
t={t}
|
t={t}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className="text-gray-500 dark:text-gray-400">
|
<li className="text-gray-500 dark:text-gray-400">
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { APIClient } from "../../api/APIClient";
|
|
||||||
import { Checkbox } from "../../components/Checkbox";
|
|
||||||
import { SettingsContext } from "../../utils/Context";
|
|
||||||
import { GithubRelease } from "../../types/Update";
|
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import Toast from "../../components/notifications/Toast";
|
|
||||||
|
import { APIClient } from "@api/APIClient";
|
||||||
|
import { Checkbox } from "@components/Checkbox";
|
||||||
|
import { SettingsContext } from "@utils/Context";
|
||||||
|
import { GithubRelease } from "@app/types/Update";
|
||||||
|
import Toast from "@components/notifications/Toast";
|
||||||
|
|
||||||
interface RowItemProps {
|
interface RowItemProps {
|
||||||
label: string;
|
label: string;
|
||||||
|
@ -47,8 +48,9 @@ const RowItemNumber = ({ label, value, title, unit }: RowItemNumberProps) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const RowItemVersion = ({ label, value, title, newUpdate }: RowItemProps) => {
|
const RowItemVersion = ({ label, value, title, newUpdate }: RowItemProps) => {
|
||||||
if (!value)
|
if (!value) {
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="py-4 sm:py-5 sm:grid sm:grid-cols-4 sm:gap-4 sm:px-6">
|
<div className="py-4 sm:py-5 sm:grid sm:grid-cols-4 sm:gap-4 sm:px-6">
|
||||||
|
@ -68,49 +70,41 @@ const RowItemVersion = ({ label, value, title, newUpdate }: RowItemProps) => {
|
||||||
function ApplicationSettings() {
|
function ApplicationSettings() {
|
||||||
const [settings, setSettings] = SettingsContext.use();
|
const [settings, setSettings] = SettingsContext.use();
|
||||||
|
|
||||||
const { isLoading, data } = useQuery(
|
const { isLoading, data } = useQuery({
|
||||||
["config"],
|
queryKey: ["config"],
|
||||||
() => APIClient.config.get(),
|
queryFn: APIClient.config.get,
|
||||||
{
|
retry: false,
|
||||||
retry: false,
|
refetchOnWindowFocus: false,
|
||||||
refetchOnWindowFocus: false,
|
onError: err => console.log(err)
|
||||||
onError: err => console.log(err)
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: updateData } = useQuery(
|
const { data: updateData } = useQuery({
|
||||||
["updates"],
|
queryKey: ["updates"],
|
||||||
() => APIClient.updates.getLatestRelease(),
|
queryFn: APIClient.updates.getLatestRelease,
|
||||||
{
|
retry: false,
|
||||||
retry: false,
|
refetchOnWindowFocus: false,
|
||||||
refetchOnWindowFocus: false,
|
onError: err => console.log(err)
|
||||||
onError: err => console.log(err)
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const checkUpdateMutation = useMutation(
|
const checkUpdateMutation = useMutation({
|
||||||
() => APIClient.updates.check(),
|
mutationFn: APIClient.updates.check,
|
||||||
{
|
onSuccess: () => {
|
||||||
onSuccess: () => {
|
queryClient.invalidateQueries({ queryKey: ["updates"] });
|
||||||
queryClient.invalidateQueries(["updates"]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
const toggleCheckUpdateMutation = useMutation(
|
const toggleCheckUpdateMutation = useMutation({
|
||||||
(value: boolean) => APIClient.config.update({ check_for_updates: value }),
|
mutationFn: (value: boolean) => APIClient.config.update({ check_for_updates: value }),
|
||||||
{
|
onSuccess: () => {
|
||||||
onSuccess: () => {
|
toast.custom((t) => <Toast type="success" body={"Config successfully updated!"} t={t}/>);
|
||||||
toast.custom((t) => <Toast type="success" body={"Config successfully updated!"} t={t}/>);
|
|
||||||
|
|
||||||
queryClient.invalidateQueries(["config"]);
|
queryClient.invalidateQueries({ queryKey: ["config"] });
|
||||||
|
|
||||||
checkUpdateMutation.mutate();
|
checkUpdateMutation.mutate();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="divide-y divide-gray-200 dark:divide-gray-700 lg:col-span-9">
|
<div className="divide-y divide-gray-200 dark:divide-gray-700 lg:col-span-9">
|
||||||
|
|
|
@ -1,14 +1,23 @@
|
||||||
import { useToggle } from "../../hooks/hooks";
|
|
||||||
import { Switch } from "@headlessui/react";
|
|
||||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
|
||||||
import { classNames } from "../../utils";
|
|
||||||
import { DownloadClientAddForm, DownloadClientUpdateForm } from "../../forms";
|
|
||||||
import { EmptySimple } from "../../components/emptystates";
|
|
||||||
import { APIClient } from "../../api/APIClient";
|
|
||||||
import { DownloadClientTypeNameMap } from "../../domain/constants";
|
|
||||||
import toast from "react-hot-toast";
|
|
||||||
import Toast from "../../components/notifications/Toast";
|
|
||||||
import { useState, useMemo } from "react";
|
import { useState, useMemo } from "react";
|
||||||
|
import { Switch } from "@headlessui/react";
|
||||||
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
|
import { useToggle } from "@hooks/hooks";
|
||||||
|
import { classNames } from "@utils";
|
||||||
|
import { DownloadClientAddForm, DownloadClientUpdateForm } from "@forms";
|
||||||
|
import { EmptySimple } from "@components/emptystates";
|
||||||
|
import { APIClient } from "@api/APIClient";
|
||||||
|
import { DownloadClientTypeNameMap } from "@domain/constants";
|
||||||
|
import Toast from "@components/notifications/Toast";
|
||||||
|
|
||||||
|
export const clientKeys = {
|
||||||
|
all: ["download_clients"] as const,
|
||||||
|
lists: () => [...clientKeys.all, "list"] as const,
|
||||||
|
// list: (indexers: string[], sortOrder: string) => [...clientKeys.lists(), { indexers, sortOrder }] as const,
|
||||||
|
details: () => [...clientKeys.all, "detail"] as const,
|
||||||
|
detail: (id: number) => [...clientKeys.details(), id] as const
|
||||||
|
};
|
||||||
|
|
||||||
interface DLSettingsItemProps {
|
interface DLSettingsItemProps {
|
||||||
client: DownloadClient;
|
client: DownloadClient;
|
||||||
|
@ -61,7 +70,6 @@ function useSort(items: ListItemProps["clients"][], config?: SortConfig) {
|
||||||
}
|
}
|
||||||
setSortConfig({ key, direction });
|
setSortConfig({ key, direction });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const getSortIndicator = (key: keyof ListItemProps["clients"]) => {
|
const getSortIndicator = (key: keyof ListItemProps["clients"]) => {
|
||||||
if (!sortConfig || sortConfig.key !== key) {
|
if (!sortConfig || sortConfig.key !== key) {
|
||||||
|
@ -79,15 +87,14 @@ function DownloadClientSettingsListItem({ client }: DLSettingsItemProps) {
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const mutation = useMutation(
|
const mutation = useMutation({
|
||||||
(client: DownloadClient) => APIClient.download_clients.update(client),
|
mutationFn: (client: DownloadClient) => APIClient.download_clients.update(client),
|
||||||
{
|
onSuccess: () => {
|
||||||
onSuccess: () => {
|
toast.custom((t) => <Toast type="success" body={`${client.name} was updated successfully`} t={t}/>);
|
||||||
queryClient.invalidateQueries(["downloadClients"]);
|
|
||||||
toast.custom((t) => <Toast type="success" body={`${client.name} was updated successfully`} t={t}/>);
|
queryClient.invalidateQueries({ queryKey: clientKeys.lists() });
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
const onToggleMutation = (newState: boolean) => {
|
const onToggleMutation = (newState: boolean) => {
|
||||||
mutation.mutate({
|
mutation.mutate({
|
||||||
|
@ -139,11 +146,11 @@ function DownloadClientSettingsListItem({ client }: DLSettingsItemProps) {
|
||||||
function DownloadClientSettings() {
|
function DownloadClientSettings() {
|
||||||
const [addClientIsOpen, toggleAddClient] = useToggle(false);
|
const [addClientIsOpen, toggleAddClient] = useToggle(false);
|
||||||
|
|
||||||
const { error, data } = useQuery(
|
const { error, data } = useQuery({
|
||||||
"downloadClients",
|
queryKey: clientKeys.lists(),
|
||||||
() => APIClient.download_clients.getAll(),
|
queryFn: APIClient.download_clients.getAll,
|
||||||
{ refetchOnWindowFocus: false }
|
refetchOnWindowFocus: false
|
||||||
);
|
});
|
||||||
|
|
||||||
const sortedClients = useSort(data || []);
|
const sortedClients = useSort(data || []);
|
||||||
|
|
||||||
|
@ -151,10 +158,8 @@ function DownloadClientSettings() {
|
||||||
return <p>Failed to fetch download clients</p>;
|
return <p>Failed to fetch download clients</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="lg:col-span-9">
|
<div className="lg:col-span-9">
|
||||||
|
|
||||||
<DownloadClientAddForm isOpen={addClientIsOpen} toggle={toggleAddClient} />
|
<DownloadClientAddForm isOpen={addClientIsOpen} toggle={toggleAddClient} />
|
||||||
|
|
||||||
<div className="py-6 px-2 lg:pb-8">
|
<div className="py-6 px-2 lg:pb-8">
|
||||||
|
@ -177,8 +182,8 @@ function DownloadClientSettings() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col mt-6 px-4">
|
<div className="flex flex-col mt-6 px-4">
|
||||||
{sortedClients.items.length > 0 ?
|
{sortedClients.items.length > 0
|
||||||
<section className="light:bg-white dark:bg-gray-800 light:shadow sm:rounded-sm">
|
? <section className="light:bg-white dark:bg-gray-800 light:shadow sm:rounded-sm">
|
||||||
<ol className="min-w-full relative">
|
<ol className="min-w-full relative">
|
||||||
<li className="grid grid-cols-12 border-b border-gray-200 dark:border-gray-700">
|
<li className="grid grid-cols-12 border-b border-gray-200 dark:border-gray-700">
|
||||||
<div className="flex col-span-2 sm:col-span-1 px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer"
|
<div className="flex col-span-2 sm:col-span-1 px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer"
|
||||||
|
@ -213,7 +218,6 @@ function DownloadClientSettings() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,7 @@
|
||||||
import { useToggle } from "../../hooks/hooks";
|
|
||||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
|
||||||
import { APIClient } from "../../api/APIClient";
|
|
||||||
import { Menu, Switch, Transition } from "@headlessui/react";
|
|
||||||
|
|
||||||
import { baseUrl, classNames, IsEmptyDate, simplifyDate } from "../../utils";
|
|
||||||
import { Fragment, useRef, useState, useMemo } from "react";
|
import { Fragment, useRef, useState, useMemo } from "react";
|
||||||
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { Menu, Switch, Transition } from "@headlessui/react";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import Toast from "../../components/notifications/Toast";
|
|
||||||
import { DeleteModal } from "../../components/modals";
|
|
||||||
import {
|
import {
|
||||||
ArrowsRightLeftIcon,
|
ArrowsRightLeftIcon,
|
||||||
DocumentTextIcon,
|
DocumentTextIcon,
|
||||||
|
@ -15,10 +9,24 @@ import {
|
||||||
PencilSquareIcon,
|
PencilSquareIcon,
|
||||||
TrashIcon
|
TrashIcon
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
import { FeedUpdateForm } from "../../forms/settings/FeedForms";
|
|
||||||
import { EmptySimple } from "../../components/emptystates";
|
import { APIClient } from "@api/APIClient";
|
||||||
|
import { useToggle } from "@hooks/hooks";
|
||||||
|
import { baseUrl, classNames, IsEmptyDate, simplifyDate } from "@utils";
|
||||||
|
import Toast from "@components/notifications/Toast";
|
||||||
|
import { DeleteModal } from "@components/modals";
|
||||||
|
import { FeedUpdateForm } from "@forms/settings/FeedForms";
|
||||||
|
import { EmptySimple } from "@components/emptystates";
|
||||||
import { ImplementationBadges } from "./Indexer";
|
import { ImplementationBadges } from "./Indexer";
|
||||||
|
|
||||||
|
export const feedKeys = {
|
||||||
|
all: ["feeds"] as const,
|
||||||
|
lists: () => [...feedKeys.all, "list"] as const,
|
||||||
|
// list: (indexers: string[], sortOrder: string) => [...feedKeys.lists(), { indexers, sortOrder }] as const,
|
||||||
|
details: () => [...feedKeys.all, "detail"] as const,
|
||||||
|
detail: (id: number) => [...feedKeys.details(), id] as const
|
||||||
|
};
|
||||||
|
|
||||||
interface SortConfig {
|
interface SortConfig {
|
||||||
key: keyof ListItemProps["feed"] | "enabled";
|
key: keyof ListItemProps["feed"] | "enabled";
|
||||||
direction: "ascending" | "descending";
|
direction: "ascending" | "descending";
|
||||||
|
@ -27,8 +35,6 @@ interface SortConfig {
|
||||||
function useSort(items: ListItemProps["feed"][], config?: SortConfig) {
|
function useSort(items: ListItemProps["feed"][], config?: SortConfig) {
|
||||||
const [sortConfig, setSortConfig] = useState(config);
|
const [sortConfig, setSortConfig] = useState(config);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const sortedItems = useMemo(() => {
|
const sortedItems = useMemo(() => {
|
||||||
if (!sortConfig) {
|
if (!sortConfig) {
|
||||||
return items;
|
return items;
|
||||||
|
@ -76,11 +82,11 @@ function useSort(items: ListItemProps["feed"][], config?: SortConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function FeedSettings() {
|
function FeedSettings() {
|
||||||
const { data } = useQuery(
|
const { data } = useQuery({
|
||||||
"feeds",
|
queryKey: feedKeys.lists(),
|
||||||
() => APIClient.feeds.find(),
|
queryFn: APIClient.feeds.find,
|
||||||
{ refetchOnWindowFocus: false }
|
refetchOnWindowFocus: false
|
||||||
);
|
});
|
||||||
|
|
||||||
const sortedFeeds = useSort(data || []);
|
const sortedFeeds = useSort(data || []);
|
||||||
|
|
||||||
|
@ -142,19 +148,15 @@ function ListItem({ feed }: ListItemProps) {
|
||||||
const [enabled, setEnabled] = useState(feed.enabled);
|
const [enabled, setEnabled] = useState(feed.enabled);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const updateMutation = useMutation(
|
const updateMutation = useMutation({
|
||||||
(status: boolean) => APIClient.feeds.toggleEnable(feed.id, status),
|
mutationFn: (status: boolean) => APIClient.feeds.toggleEnable(feed.id, status),
|
||||||
{
|
onSuccess: () => {
|
||||||
onSuccess: () => {
|
queryClient.invalidateQueries({ queryKey: feedKeys.lists() });
|
||||||
toast.custom((t) => <Toast type="success"
|
queryClient.invalidateQueries({ queryKey: feedKeys.detail(feed.id) });
|
||||||
body={`${feed.name} was ${enabled ? "disabled" : "enabled"} successfully`}
|
|
||||||
t={t}/>);
|
|
||||||
|
|
||||||
queryClient.invalidateQueries(["feeds"]);
|
toast.custom((t) => <Toast type="success" body={`${feed.name} was ${!enabled ? "disabled" : "enabled"} successfully`} t={t}/>);
|
||||||
queryClient.invalidateQueries(["feeds", feed?.id]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
const toggleActive = (status: boolean) => {
|
const toggleActive = (status: boolean) => {
|
||||||
setEnabled(status);
|
setEnabled(status);
|
||||||
|
@ -227,17 +229,15 @@ const FeedItemDropdown = ({
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false);
|
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false);
|
||||||
const deleteMutation = useMutation(
|
const deleteMutation = useMutation({
|
||||||
(id: number) => APIClient.feeds.delete(id),
|
mutationFn: (id: number) => APIClient.feeds.delete(id),
|
||||||
{
|
onSuccess: () => {
|
||||||
onSuccess: () => {
|
queryClient.invalidateQueries({ queryKey: feedKeys.lists() });
|
||||||
queryClient.invalidateQueries(["feeds"]);
|
queryClient.invalidateQueries({ queryKey: feedKeys.detail(feed.id) });
|
||||||
queryClient.invalidateQueries(["feeds", feed.id]);
|
|
||||||
|
|
||||||
toast.custom((t) => <Toast type="success" body={`Feed ${feed?.name} was deleted`} t={t}/>);
|
toast.custom((t) => <Toast type="success" body={`Feed ${feed?.name} was deleted`} t={t}/>);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu as="div">
|
<Menu as="div">
|
||||||
|
|
|
@ -1,12 +1,21 @@
|
||||||
import { useToggle } from "../../hooks/hooks";
|
|
||||||
import { useQuery } from "react-query";
|
|
||||||
import { IndexerAddForm, IndexerUpdateForm } from "../../forms";
|
|
||||||
import { Switch } from "@headlessui/react";
|
|
||||||
import { classNames } from "../../utils";
|
|
||||||
import { EmptySimple } from "../../components/emptystates";
|
|
||||||
import { APIClient } from "../../api/APIClient";
|
|
||||||
import { componentMapType } from "../../forms/settings/DownloadClientForms";
|
|
||||||
import { useState, useMemo } from "react";
|
import { useState, useMemo } from "react";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { Switch } from "@headlessui/react";
|
||||||
|
|
||||||
|
import { IndexerAddForm, IndexerUpdateForm } from "@forms";
|
||||||
|
import { useToggle } from "@hooks/hooks";
|
||||||
|
import { classNames } from "@utils";
|
||||||
|
import { EmptySimple } from "@components/emptystates";
|
||||||
|
import { APIClient } from "@api/APIClient";
|
||||||
|
import { componentMapType } from "@forms/settings/DownloadClientForms";
|
||||||
|
|
||||||
|
export const indexerKeys = {
|
||||||
|
all: ["indexers"] as const,
|
||||||
|
lists: () => [...indexerKeys.all, "list"] as const,
|
||||||
|
// list: (indexers: string[], sortOrder: string) => [...indexerKeys.lists(), { indexers, sortOrder }] as const,
|
||||||
|
details: () => [...indexerKeys.all, "detail"] as const,
|
||||||
|
detail: (id: number) => [...indexerKeys.details(), id] as const
|
||||||
|
};
|
||||||
|
|
||||||
interface SortConfig {
|
interface SortConfig {
|
||||||
key: keyof ListItemProps["indexer"] | "enabled";
|
key: keyof ListItemProps["indexer"] | "enabled";
|
||||||
|
@ -149,16 +158,17 @@ const ListItem = ({ indexer }: ListItemProps) => {
|
||||||
function IndexerSettings() {
|
function IndexerSettings() {
|
||||||
const [addIndexerIsOpen, toggleAddIndexer] = useToggle(false);
|
const [addIndexerIsOpen, toggleAddIndexer] = useToggle(false);
|
||||||
|
|
||||||
const { error, data } = useQuery(
|
const { error, data } = useQuery({
|
||||||
"indexer",
|
queryKey: indexerKeys.lists(),
|
||||||
() => APIClient.indexers.getAll(),
|
queryFn: APIClient.indexers.getAll,
|
||||||
{ refetchOnWindowFocus: false }
|
refetchOnWindowFocus: false
|
||||||
);
|
});
|
||||||
|
|
||||||
const sortedIndexers = useSort(data || []);
|
const sortedIndexers = useSort(data || []);
|
||||||
|
|
||||||
if (error)
|
if (error) {
|
||||||
return (<p>An error has occurred</p>);
|
return (<p>An error has occurred</p>);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="lg:col-span-9">
|
<div className="lg:col-span-9">
|
||||||
|
|
|
@ -1,16 +1,8 @@
|
||||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
import { Fragment, useRef, useState, useMemo } from "react";
|
||||||
import { classNames, IsEmptyDate, simplifyDate } from "../../utils";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { IrcNetworkAddForm, IrcNetworkUpdateForm } from "../../forms";
|
|
||||||
import { useToggle } from "../../hooks/hooks";
|
|
||||||
import { APIClient } from "../../api/APIClient";
|
|
||||||
import { EmptySimple } from "../../components/emptystates";
|
|
||||||
import { LockClosedIcon, LockOpenIcon } from "@heroicons/react/24/solid";
|
import { LockClosedIcon, LockOpenIcon } from "@heroicons/react/24/solid";
|
||||||
import { Menu, Switch, Transition } from "@headlessui/react";
|
import { Menu, Switch, Transition } from "@headlessui/react";
|
||||||
import { Fragment, useRef } from "react";
|
|
||||||
import { DeleteModal } from "../../components/modals";
|
|
||||||
import { useState, useMemo } from "react";
|
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import Toast from "../../components/notifications/Toast";
|
|
||||||
import {
|
import {
|
||||||
ArrowsPointingInIcon,
|
ArrowsPointingInIcon,
|
||||||
ArrowsPointingOutIcon,
|
ArrowsPointingOutIcon,
|
||||||
|
@ -20,6 +12,22 @@ import {
|
||||||
TrashIcon
|
TrashIcon
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
|
|
||||||
|
import { classNames, IsEmptyDate, simplifyDate } from "@utils";
|
||||||
|
import { IrcNetworkAddForm, IrcNetworkUpdateForm } from "@forms";
|
||||||
|
import { useToggle } from "@hooks/hooks";
|
||||||
|
import { APIClient } from "@api/APIClient";
|
||||||
|
import { EmptySimple } from "@components/emptystates";
|
||||||
|
import { DeleteModal } from "@components/modals";
|
||||||
|
import Toast from "@components/notifications/Toast";
|
||||||
|
|
||||||
|
export const ircKeys = {
|
||||||
|
all: ["irc_networks"] as const,
|
||||||
|
lists: () => [...ircKeys.all, "list"] as const,
|
||||||
|
// list: (indexers: string[], sortOrder: string) => [...ircKeys.lists(), { indexers, sortOrder }] as const,
|
||||||
|
details: () => [...ircKeys.all, "detail"] as const,
|
||||||
|
detail: (id: number) => [...ircKeys.details(), id] as const
|
||||||
|
};
|
||||||
|
|
||||||
interface SortConfig {
|
interface SortConfig {
|
||||||
key: keyof ListItemProps["network"] | "enabled";
|
key: keyof ListItemProps["network"] | "enabled";
|
||||||
direction: "ascending" | "descending";
|
direction: "ascending" | "descending";
|
||||||
|
@ -79,10 +87,11 @@ const IrcSettings = () => {
|
||||||
const [expandNetworks, toggleExpand] = useToggle(false);
|
const [expandNetworks, toggleExpand] = useToggle(false);
|
||||||
const [addNetworkIsOpen, toggleAddNetwork] = useToggle(false);
|
const [addNetworkIsOpen, toggleAddNetwork] = useToggle(false);
|
||||||
|
|
||||||
const { data } = useQuery("networks", () => APIClient.irc.getNetworks(), {
|
const { data } = useQuery({
|
||||||
|
queryKey: ircKeys.lists(),
|
||||||
|
queryFn: APIClient.irc.getNetworks,
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
// Refetch every 3 seconds
|
refetchInterval: 3000 // Refetch every 3 seconds
|
||||||
refetchInterval: 3000
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const sortedNetworks = useSort(data || []);
|
const sortedNetworks = useSort(data || []);
|
||||||
|
@ -204,18 +213,17 @@ const ListItem = ({ idx, network, expanded }: ListItemProps) => {
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const mutation = useMutation(
|
const updateMutation = useMutation({
|
||||||
(network: IrcNetwork) => APIClient.irc.updateNetwork(network),
|
mutationFn: (network: IrcNetwork) => APIClient.irc.updateNetwork(network),
|
||||||
{
|
onSuccess: () => {
|
||||||
onSuccess: () => {
|
queryClient.invalidateQueries({ queryKey: ircKeys.lists() });
|
||||||
queryClient.invalidateQueries(["networks"]);
|
|
||||||
toast.custom((t) => <Toast type="success" body={`${network.name} was updated successfully`} t={t}/>);
|
toast.custom((t) => <Toast type="success" body={`${network.name} was updated successfully`} t={t}/>);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
const onToggleMutation = (newState: boolean) => {
|
const onToggleMutation = (newState: boolean) => {
|
||||||
mutation.mutate({
|
updateMutation.mutate({
|
||||||
...network,
|
...network,
|
||||||
enabled: newState
|
enabled: newState
|
||||||
});
|
});
|
||||||
|
@ -399,37 +407,30 @@ const ListItemDropdown = ({
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false);
|
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false);
|
||||||
const deleteMutation = useMutation(
|
|
||||||
(id: number) => APIClient.irc.deleteNetwork(id),
|
|
||||||
{
|
|
||||||
onSuccess: () => {
|
|
||||||
queryClient.invalidateQueries(["networks"]);
|
|
||||||
queryClient.invalidateQueries(["networks", network.id]);
|
|
||||||
|
|
||||||
toast.custom((t) => <Toast type="success" body={`Network ${network.name} was deleted`} t={t}/>);
|
const deleteMutation = useMutation({
|
||||||
|
mutationFn: (id: number) => APIClient.irc.deleteNetwork(id),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ircKeys.lists() });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ircKeys.detail(network.id) });
|
||||||
|
|
||||||
toggleDeleteModal();
|
toast.custom((t) => <Toast type="success" body={`Network ${network.name} was deleted`} t={t}/>);
|
||||||
}
|
|
||||||
|
toggleDeleteModal();
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
const restartMutation = useMutation(
|
const restartMutation = useMutation({
|
||||||
(id: number) => APIClient.irc.restartNetwork(id),
|
mutationFn: (id: number) => APIClient.irc.restartNetwork(id),
|
||||||
{
|
onSuccess: () => {
|
||||||
onSuccess: () => {
|
queryClient.invalidateQueries({ queryKey: ircKeys.lists() });
|
||||||
toast.custom((t) => <Toast type="success"
|
queryClient.invalidateQueries({ queryKey: ircKeys.detail(network.id) });
|
||||||
body={`${network.name} was successfully restarted`}
|
|
||||||
t={t}/>);
|
|
||||||
|
|
||||||
queryClient.invalidateQueries(["networks"]);
|
toast.custom((t) => <Toast type="success" body={`${network.name} was successfully restarted`} t={t}/>);
|
||||||
queryClient.invalidateQueries(["networks", network.id]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
const restart = (id: number) => {
|
const restart = (id: number) => restartMutation.mutate(id);
|
||||||
restartMutation.mutate(id);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu as="div">
|
<Menu as="div">
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { APIClient } from "../../api/APIClient";
|
|
||||||
import { GithubRelease } from "../../types/Update";
|
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import Toast from "../../components/notifications/Toast";
|
|
||||||
import Select, { components, ControlProps, InputProps, MenuProps, OptionProps } from "react-select";
|
import Select, { components, ControlProps, InputProps, MenuProps, OptionProps } from "react-select";
|
||||||
import { LogLevelOptions, SelectOption } from "../../domain/constants";
|
|
||||||
|
import { APIClient } from "@api/APIClient";
|
||||||
|
import { GithubRelease } from "@app/types/Update";
|
||||||
|
import Toast from "@components/notifications/Toast";
|
||||||
|
import { LogLevelOptions, SelectOption } from "@domain/constants";
|
||||||
import { LogFiles } from "../Logs";
|
import { LogFiles } from "../Logs";
|
||||||
|
|
||||||
interface RowItemProps {
|
interface RowItemProps {
|
||||||
|
@ -121,28 +122,24 @@ const RowItemSelect = ({ id, title, label, value, options, onChange }: any) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
function LogSettings() {
|
function LogSettings() {
|
||||||
const { isLoading, data } = useQuery(
|
const { isLoading, data } = useQuery({
|
||||||
["config"],
|
queryKey: ["config"],
|
||||||
() => APIClient.config.get(),
|
queryFn: APIClient.config.get,
|
||||||
{
|
retry: false,
|
||||||
retry: false,
|
refetchOnWindowFocus: false,
|
||||||
refetchOnWindowFocus: false,
|
onError: err => console.log(err)
|
||||||
onError: err => console.log(err)
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const setLogLevelUpdateMutation = useMutation(
|
const setLogLevelUpdateMutation = useMutation({
|
||||||
(value: string) => APIClient.config.update({ log_level: value }),
|
mutationFn: (value: string) => APIClient.config.update({ log_level: value }),
|
||||||
{
|
onSuccess: () => {
|
||||||
onSuccess: () => {
|
toast.custom((t) => <Toast type="success" body={"Config successfully updated!"} t={t}/>);
|
||||||
toast.custom((t) => <Toast type="success" body={"Config successfully updated!"} t={t}/>);
|
|
||||||
|
|
||||||
queryClient.invalidateQueries(["config"]);
|
queryClient.invalidateQueries({ queryKey: ["config"] });
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="divide-y divide-gray-200 dark:divide-gray-700 lg:col-span-9">
|
<div className="divide-y divide-gray-200 dark:divide-gray-700 lg:col-span-9">
|
||||||
|
|
|
@ -1,19 +1,27 @@
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { APIClient } from "../../api/APIClient";
|
|
||||||
import { EmptySimple } from "../../components/emptystates";
|
|
||||||
import { useToggle } from "../../hooks/hooks";
|
|
||||||
import { NotificationAddForm, NotificationUpdateForm } from "../../forms/settings/NotificationForms";
|
|
||||||
import { Switch } from "@headlessui/react";
|
import { Switch } from "@headlessui/react";
|
||||||
import { classNames } from "../../utils";
|
|
||||||
import { componentMapType } from "../../forms/settings/DownloadClientForms";
|
import { APIClient } from "@api/APIClient";
|
||||||
|
import { EmptySimple } from "@components/emptystates";
|
||||||
|
import { useToggle } from "@hooks/hooks";
|
||||||
|
import { NotificationAddForm, NotificationUpdateForm } from "@forms/settings/NotificationForms";
|
||||||
|
import { classNames } from "@utils";
|
||||||
|
import { componentMapType } from "@forms/settings/DownloadClientForms";
|
||||||
|
|
||||||
|
export const notificationKeys = {
|
||||||
|
all: ["notifications"] as const,
|
||||||
|
lists: () => [...notificationKeys.all, "list"] as const,
|
||||||
|
details: () => [...notificationKeys.all, "detail"] as const,
|
||||||
|
detail: (id: number) => [...notificationKeys.details(), id] as const
|
||||||
|
};
|
||||||
|
|
||||||
function NotificationSettings() {
|
function NotificationSettings() {
|
||||||
const [addNotificationsIsOpen, toggleAddNotifications] = useToggle(false);
|
const [addNotificationsIsOpen, toggleAddNotifications] = useToggle(false);
|
||||||
|
|
||||||
const { data } = useQuery(
|
const { data } = useQuery({
|
||||||
"notifications",
|
queryKey: notificationKeys.lists(),
|
||||||
() => APIClient.notifications.getAll(),
|
queryFn: APIClient.notifications.getAll,
|
||||||
{ refetchOnWindowFocus: false }
|
refetchOnWindowFocus: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,29 +1,30 @@
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import { useMutation, useQueryClient } from "react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import { APIClient } from "../../api/APIClient";
|
|
||||||
import Toast from "../../components/notifications/Toast";
|
import { APIClient } from "@api/APIClient";
|
||||||
import { useToggle } from "../../hooks/hooks";
|
import Toast from "@components/notifications/Toast";
|
||||||
import { DeleteModal } from "../../components/modals";
|
import { useToggle } from "@hooks/hooks";
|
||||||
|
import { DeleteModal } from "@components/modals";
|
||||||
|
import { releaseKeys } from "@screens/releases/ReleaseTable";
|
||||||
|
|
||||||
function ReleaseSettings() {
|
function ReleaseSettings() {
|
||||||
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false);
|
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const deleteMutation = useMutation(() => APIClient.release.delete(), {
|
const deleteMutation = useMutation({
|
||||||
|
mutationFn: APIClient.release.delete,
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.custom((t) => (
|
toast.custom((t) => (
|
||||||
<Toast type="success" body={"All releases were deleted"} t={t}/>
|
<Toast type="success" body={"All releases were deleted"} t={t}/>
|
||||||
));
|
));
|
||||||
|
|
||||||
// Invalidate filters just in case, most likely not necessary but can't hurt.
|
// Invalidate filters just in case, most likely not necessary but can't hurt.
|
||||||
queryClient.invalidateQueries("releases");
|
queryClient.invalidateQueries({ queryKey: releaseKeys.lists() });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const deleteAction = () => {
|
const deleteAction = () => deleteMutation.mutate();
|
||||||
deleteMutation.mutate();
|
|
||||||
};
|
|
||||||
|
|
||||||
const cancelModalButtonRef = useRef(null);
|
const cancelModalButtonRef = useRef(null);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { newRidgeState } from "react-ridge-state";
|
import { newRidgeState } from "react-ridge-state";
|
||||||
|
|
||||||
|
|
||||||
export const InitializeGlobalContext = () => {
|
export const InitializeGlobalContext = () => {
|
||||||
const auth_ctx = localStorage.getItem("auth");
|
const auth_ctx = localStorage.getItem("auth");
|
||||||
if (auth_ctx)
|
if (auth_ctx)
|
||||||
|
|
|
@ -21,7 +21,8 @@
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@*": ["./src/*"],
|
||||||
|
"@app*": ["./src/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
|
|
|
@ -12,9 +12,18 @@ export default ({ mode }: { mode: any }) => {
|
||||||
base: "",
|
base: "",
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: [
|
||||||
"@": fileURLToPath(new URL("./src", import.meta.url))
|
{ find: "@", replacement: fileURLToPath(new URL("./src/", import.meta.url)) },
|
||||||
}
|
{ find: "@app", replacement: fileURLToPath(new URL("./src/", import.meta.url)) },
|
||||||
|
{ find: "@components", replacement: fileURLToPath(new URL("./src/components", import.meta.url)) },
|
||||||
|
{ find: "@forms", replacement: fileURLToPath(new URL("./src/forms", import.meta.url)) },
|
||||||
|
{ find: "@hooks", replacement: fileURLToPath(new URL("./src/hooks", import.meta.url)) },
|
||||||
|
{ find: "@api", replacement: fileURLToPath(new URL("./src/api", import.meta.url)) },
|
||||||
|
{ find: "@screens", replacement: fileURLToPath(new URL("./src/screens", import.meta.url)) },
|
||||||
|
{ find: "@utils", replacement: fileURLToPath(new URL("./src/utils", import.meta.url)) },
|
||||||
|
{ find: "@types", replacement: fileURLToPath(new URL("./src/types", import.meta.url)) },
|
||||||
|
{ find: "@domain", replacement: fileURLToPath(new URL("./src/domain", import.meta.url)) }
|
||||||
|
]
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
port: 3000,
|
port: 3000,
|
||||||
|
|
189
web/yarn.lock
189
web/yarn.lock
|
@ -48,7 +48,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.2, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.8.7":
|
"@babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7":
|
||||||
version: 7.21.0
|
version: 7.21.0
|
||||||
resolution: "@babel/runtime@npm:7.21.0"
|
resolution: "@babel/runtime@npm:7.21.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -703,6 +703,56 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@tanstack/match-sorter-utils@npm:^8.7.0":
|
||||||
|
version: 8.8.4
|
||||||
|
resolution: "@tanstack/match-sorter-utils@npm:8.8.4"
|
||||||
|
dependencies:
|
||||||
|
remove-accents: 0.4.2
|
||||||
|
checksum: d005f500754f52ef94966cbbe4217f26e7e3c07291faa2578b06bca9a5abe01689569994c37a1d01c6e783addf5ffbb28fa82eba7961d36eabf43ec43d1e496b
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@tanstack/query-core@npm:4.29.1":
|
||||||
|
version: 4.29.1
|
||||||
|
resolution: "@tanstack/query-core@npm:4.29.1"
|
||||||
|
checksum: e7a5a73e5e743411e39348bee3ded4b6c5dd3a70009e72e067e257d5f3d612bfe8df0998cddff6ad7a078e0bcf7a3c92ded669758ca697055472ce910e23a368
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@tanstack/react-query-devtools@npm:^4.29.3":
|
||||||
|
version: 4.29.3
|
||||||
|
resolution: "@tanstack/react-query-devtools@npm:4.29.3"
|
||||||
|
dependencies:
|
||||||
|
"@tanstack/match-sorter-utils": ^8.7.0
|
||||||
|
superjson: ^1.10.0
|
||||||
|
use-sync-external-store: ^1.2.0
|
||||||
|
peerDependencies:
|
||||||
|
"@tanstack/react-query": 4.29.3
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
checksum: fd52531b4ef8776fdb7f6957216a1a933f3507d7e6b20ca63d6a2fe78e25658b6d1a712a71cca04cd29715bc66c2952312434190faedab1b3702cb71527460ee
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@tanstack/react-query@npm:^4.29.3":
|
||||||
|
version: 4.29.3
|
||||||
|
resolution: "@tanstack/react-query@npm:4.29.3"
|
||||||
|
dependencies:
|
||||||
|
"@tanstack/query-core": 4.29.1
|
||||||
|
use-sync-external-store: ^1.2.0
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
react-native: "*"
|
||||||
|
peerDependenciesMeta:
|
||||||
|
react-dom:
|
||||||
|
optional: true
|
||||||
|
react-native:
|
||||||
|
optional: true
|
||||||
|
checksum: 7d33fe001ba6ec2332c28749312e5268e6bc060ea0e7c3faa37a28a12fd4b4fddd3803fdfb4e6551997f06d4cd32568e39d9cc9a26c19f40cfef276fad1eaaee
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@tootallnate/once@npm:2":
|
"@tootallnate/once@npm:2":
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
resolution: "@tootallnate/once@npm:2.0.0"
|
resolution: "@tootallnate/once@npm:2.0.0"
|
||||||
|
@ -1234,13 +1284,6 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"big-integer@npm:^1.6.16":
|
|
||||||
version: 1.6.51
|
|
||||||
resolution: "big-integer@npm:1.6.51"
|
|
||||||
checksum: 3d444173d1b2e20747e2c175568bedeebd8315b0637ea95d75fd27830d3b8e8ba36c6af40374f36bdaea7b5de376dcada1b07587cb2a79a928fccdb6e6e3c518
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"binary-extensions@npm:^2.0.0":
|
"binary-extensions@npm:^2.0.0":
|
||||||
version: 2.2.0
|
version: 2.2.0
|
||||||
resolution: "binary-extensions@npm:2.2.0"
|
resolution: "binary-extensions@npm:2.2.0"
|
||||||
|
@ -1276,22 +1319,6 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"broadcast-channel@npm:^3.4.1":
|
|
||||||
version: 3.7.0
|
|
||||||
resolution: "broadcast-channel@npm:3.7.0"
|
|
||||||
dependencies:
|
|
||||||
"@babel/runtime": ^7.7.2
|
|
||||||
detect-node: ^2.1.0
|
|
||||||
js-sha3: 0.8.0
|
|
||||||
microseconds: 0.2.0
|
|
||||||
nano-time: 1.0.0
|
|
||||||
oblivious-set: 1.0.0
|
|
||||||
rimraf: 3.0.2
|
|
||||||
unload: 2.2.0
|
|
||||||
checksum: 803794c48dcce7f03aca69797430bd8b1c4cfd70b7de22079cd89567eeffaa126a1db98c7c2d86af8131d9bb41ed367c0fef96dfb446151c927b831572c621fc
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"browserslist@npm:^4.21.5":
|
"browserslist@npm:^4.21.5":
|
||||||
version: 4.21.5
|
version: 4.21.5
|
||||||
resolution: "browserslist@npm:4.21.5"
|
resolution: "browserslist@npm:4.21.5"
|
||||||
|
@ -1500,6 +1527,15 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"copy-anything@npm:^3.0.2":
|
||||||
|
version: 3.0.3
|
||||||
|
resolution: "copy-anything@npm:3.0.3"
|
||||||
|
dependencies:
|
||||||
|
is-what: ^4.1.8
|
||||||
|
checksum: d456dc5ec98dee7c7cf87d809eac30dc2ac942acd4cf970fab394e280ceb6dd7a8a7a5a44fcbcc50e0206658de3cc20b92863562f5797930bb2619f164f4c182
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"cosmiconfig@npm:^7.0.0":
|
"cosmiconfig@npm:^7.0.0":
|
||||||
version: 7.1.0
|
version: 7.1.0
|
||||||
resolution: "cosmiconfig@npm:7.1.0"
|
resolution: "cosmiconfig@npm:7.1.0"
|
||||||
|
@ -1613,13 +1649,6 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"detect-node@npm:^2.0.4, detect-node@npm:^2.1.0":
|
|
||||||
version: 2.1.0
|
|
||||||
resolution: "detect-node@npm:2.1.0"
|
|
||||||
checksum: 832184ec458353e41533ac9c622f16c19f7c02d8b10c303dfd3a756f56be93e903616c0bb2d4226183c9351c15fc0b3dba41a17a2308262afabcfa3776e6ae6e
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"didyoumean@npm:^1.2.2":
|
"didyoumean@npm:^1.2.2":
|
||||||
version: 1.2.2
|
version: 1.2.2
|
||||||
resolution: "didyoumean@npm:1.2.2"
|
resolution: "didyoumean@npm:1.2.2"
|
||||||
|
@ -2956,6 +2985,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"is-what@npm:^4.1.8":
|
||||||
|
version: 4.1.8
|
||||||
|
resolution: "is-what@npm:4.1.8"
|
||||||
|
checksum: b9bec3acff102d14ad467f4c74c9886af310fa160e07a63292c8c181e6768c7c4c1054644e13d67185b963644e4a513bce8c6b8ce3d3ca6f9488a69fccad5f97
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"isexe@npm:^2.0.0":
|
"isexe@npm:^2.0.0":
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
resolution: "isexe@npm:2.0.0"
|
resolution: "isexe@npm:2.0.0"
|
||||||
|
@ -2979,13 +3015,6 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"js-sha3@npm:0.8.0":
|
|
||||||
version: 0.8.0
|
|
||||||
resolution: "js-sha3@npm:0.8.0"
|
|
||||||
checksum: 75df77c1fc266973f06cce8309ce010e9e9f07ec35ab12022ed29b7f0d9c8757f5a73e1b35aa24840dced0dea7059085aa143d817aea9e188e2a80d569d9adce
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0":
|
"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0":
|
||||||
version: 4.0.0
|
version: 4.0.0
|
||||||
resolution: "js-tokens@npm:4.0.0"
|
resolution: "js-tokens@npm:4.0.0"
|
||||||
|
@ -3193,16 +3222,6 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"match-sorter@npm:^6.0.2":
|
|
||||||
version: 6.3.1
|
|
||||||
resolution: "match-sorter@npm:6.3.1"
|
|
||||||
dependencies:
|
|
||||||
"@babel/runtime": ^7.12.5
|
|
||||||
remove-accents: 0.4.2
|
|
||||||
checksum: a4b02b676ac4ce64a89a091539ee4a70a802684713bcf06f2b70787927f510fe8a2adc849f9288857a90906083ad303467e530e8723b4a9756df9994fc164550
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"memoize-one@npm:^6.0.0":
|
"memoize-one@npm:^6.0.0":
|
||||||
version: 6.0.0
|
version: 6.0.0
|
||||||
resolution: "memoize-one@npm:6.0.0"
|
resolution: "memoize-one@npm:6.0.0"
|
||||||
|
@ -3234,13 +3253,6 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"microseconds@npm:0.2.0":
|
|
||||||
version: 0.2.0
|
|
||||||
resolution: "microseconds@npm:0.2.0"
|
|
||||||
checksum: 22bfa8553f92c7d95afff6de0aeb2aecf750680d41b8c72b02098ccc5bbbb0a384380ff539292dbd3788f5dfc298682f9d38a2b4c101f5ee2c9471d53934c5fa
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"mimic-fn@npm:^2.1.0":
|
"mimic-fn@npm:^2.1.0":
|
||||||
version: 2.1.0
|
version: 2.1.0
|
||||||
resolution: "mimic-fn@npm:2.1.0"
|
resolution: "mimic-fn@npm:2.1.0"
|
||||||
|
@ -3393,15 +3405,6 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"nano-time@npm:1.0.0":
|
|
||||||
version: 1.0.0
|
|
||||||
resolution: "nano-time@npm:1.0.0"
|
|
||||||
dependencies:
|
|
||||||
big-integer: ^1.6.16
|
|
||||||
checksum: eef8548546cc1020625f8e44751a7263e9eddf0412a6a1a6c80a8d2be2ea7973622804a977cdfe796807b85b20ff6c8ba340e8dd20effcc7078193ed5edbb5d4
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"nanoid@npm:^3.3.6":
|
"nanoid@npm:^3.3.6":
|
||||||
version: 3.3.6
|
version: 3.3.6
|
||||||
resolution: "nanoid@npm:3.3.6"
|
resolution: "nanoid@npm:3.3.6"
|
||||||
|
@ -3588,13 +3591,6 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"oblivious-set@npm:1.0.0":
|
|
||||||
version: 1.0.0
|
|
||||||
resolution: "oblivious-set@npm:1.0.0"
|
|
||||||
checksum: f31740ea9c3a8242ad2324e4ebb9a35359fbc2e6e7131731a0fc1c8b7b1238eb07e4c8c631a38535243a7b8e3042b7e89f7dc2a95d2989afd6f80bd5793b0aab
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"once@npm:^1.3.0":
|
"once@npm:^1.3.0":
|
||||||
version: 1.4.0
|
version: 1.4.0
|
||||||
resolution: "once@npm:1.4.0"
|
resolution: "once@npm:1.4.0"
|
||||||
|
@ -4009,24 +4005,6 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"react-query@npm:^3.39.1":
|
|
||||||
version: 3.39.3
|
|
||||||
resolution: "react-query@npm:3.39.3"
|
|
||||||
dependencies:
|
|
||||||
"@babel/runtime": ^7.5.5
|
|
||||||
broadcast-channel: ^3.4.1
|
|
||||||
match-sorter: ^6.0.2
|
|
||||||
peerDependencies:
|
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
|
||||||
peerDependenciesMeta:
|
|
||||||
react-dom:
|
|
||||||
optional: true
|
|
||||||
react-native:
|
|
||||||
optional: true
|
|
||||||
checksum: d2de6a0992dbf039ff2de564de1ae6361f8ac7310159dae42ec16f833b79c05caedced187235c42373ac331cc5f2fe9e2b31b14ae75a815e86d86e30ca9887ad
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"react-ridge-state@npm:4.2.2":
|
"react-ridge-state@npm:4.2.2":
|
||||||
version: 4.2.2
|
version: 4.2.2
|
||||||
resolution: "react-ridge-state@npm:4.2.2"
|
resolution: "react-ridge-state@npm:4.2.2"
|
||||||
|
@ -4260,7 +4238,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"rimraf@npm:3.0.2, rimraf@npm:^3.0.2":
|
"rimraf@npm:^3.0.2":
|
||||||
version: 3.0.2
|
version: 3.0.2
|
||||||
resolution: "rimraf@npm:3.0.2"
|
resolution: "rimraf@npm:3.0.2"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -4588,6 +4566,15 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"superjson@npm:^1.10.0":
|
||||||
|
version: 1.12.3
|
||||||
|
resolution: "superjson@npm:1.12.3"
|
||||||
|
dependencies:
|
||||||
|
copy-anything: ^3.0.2
|
||||||
|
checksum: 3549cc1d03e93745632d8114f91ed1668d81a0cf4c618f8f89a1b06f426a9cd1a2879f0e79469a6a193fd19dcea9a8fecff6215d12527b98c40c67cd98f185d3
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"supports-color@npm:^5.3.0":
|
"supports-color@npm:^5.3.0":
|
||||||
version: 5.5.0
|
version: 5.5.0
|
||||||
resolution: "supports-color@npm:5.5.0"
|
resolution: "supports-color@npm:5.5.0"
|
||||||
|
@ -4826,16 +4813,6 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"unload@npm:2.2.0":
|
|
||||||
version: 2.2.0
|
|
||||||
resolution: "unload@npm:2.2.0"
|
|
||||||
dependencies:
|
|
||||||
"@babel/runtime": ^7.6.2
|
|
||||||
detect-node: ^2.0.4
|
|
||||||
checksum: 88ba950c5ff83ab4f9bbd8f63bbf19ba09687ed3c434efd43b7338cc595bc574df8f9b155ee6eee7a435de3d3a4a226726988428977a68ba4907045f1fac5d41
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"update-browserslist-db@npm:^1.0.10":
|
"update-browserslist-db@npm:^1.0.10":
|
||||||
version: 1.0.11
|
version: 1.0.11
|
||||||
resolution: "update-browserslist-db@npm:1.0.11"
|
resolution: "update-browserslist-db@npm:1.0.11"
|
||||||
|
@ -4871,6 +4848,15 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"use-sync-external-store@npm:^1.2.0":
|
||||||
|
version: 1.2.0
|
||||||
|
resolution: "use-sync-external-store@npm:1.2.0"
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
checksum: 5c639e0f8da3521d605f59ce5be9e094ca772bd44a4ce7322b055a6f58eeed8dda3c94cabd90c7a41fb6fa852210092008afe48f7038792fd47501f33299116a
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2":
|
"util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2":
|
||||||
version: 1.0.2
|
version: 1.0.2
|
||||||
resolution: "util-deprecate@npm:1.0.2"
|
resolution: "util-deprecate@npm:1.0.2"
|
||||||
|
@ -4934,6 +4920,8 @@ __metadata:
|
||||||
"@heroicons/react": ^2.0.11
|
"@heroicons/react": ^2.0.11
|
||||||
"@hookform/error-message": ^2.0.0
|
"@hookform/error-message": ^2.0.0
|
||||||
"@tailwindcss/forms": ^0.5.2
|
"@tailwindcss/forms": ^0.5.2
|
||||||
|
"@tanstack/react-query": ^4.29.3
|
||||||
|
"@tanstack/react-query-devtools": ^4.29.3
|
||||||
"@types/node": ^18.0.0
|
"@types/node": ^18.0.0
|
||||||
"@types/react": ^18.0.12
|
"@types/react": ^18.0.12
|
||||||
"@types/react-dom": ^18.0.5
|
"@types/react-dom": ^18.0.5
|
||||||
|
@ -4962,7 +4950,6 @@ __metadata:
|
||||||
react-multi-select-component: ^4.2.9
|
react-multi-select-component: ^4.2.9
|
||||||
react-popper-tooltip: ^4.4.2
|
react-popper-tooltip: ^4.4.2
|
||||||
react-portal: ^4.2.2
|
react-portal: ^4.2.2
|
||||||
react-query: ^3.39.1
|
|
||||||
react-ridge-state: 4.2.2
|
react-ridge-state: 4.2.2
|
||||||
react-router-dom: ^6.3.0
|
react-router-dom: ^6.3.0
|
||||||
react-select: ^5.3.2
|
react-select: ^5.3.2
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue