mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 00:39:13 +00:00
refactor: web api client and cleanup (#128)
refactor: refactored APIClient.ts with a new fetch wrapper and changed it into an explicit-import. chore: modified package.json not to start browser on "npm run start" chore: cleaned up code, deleted 2mo+ useless old comments. fix: fixed parameter collision in screens/filters/details.tsx fix: override react-select's Select component style to make it consistent. addresses #116 Co-authored-by: anonymous <anonymous>
This commit is contained in:
parent
6d68a5c3b7
commit
b60e5f61c6
17 changed files with 381 additions and 793 deletions
|
@ -23,7 +23,7 @@
|
||||||
"web-vitals": "^1.0.1"
|
"web-vitals": "^1.0.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "BROWSER=none react-scripts start",
|
||||||
"build": "react-scripts build",
|
"build": "react-scripts build",
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
|
|
|
@ -1,72 +1,55 @@
|
||||||
import {baseUrl, sseBaseUrl} from "../utils";
|
import {baseUrl, sseBaseUrl} from "../utils";
|
||||||
|
|
||||||
function baseClient(endpoint: string, method: string, { body, ...customConfig}: any = {}) {
|
interface ConfigType {
|
||||||
const baseURL = baseUrl()
|
body?: BodyInit | Record<string, unknown> | null;
|
||||||
|
headers?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
const headers = {'content-type': 'application/json'}
|
export async function HttpClient<T>(
|
||||||
|
endpoint: string,
|
||||||
|
method: string,
|
||||||
|
{ body, ...customConfig }: ConfigType = {}
|
||||||
|
): Promise<T> {
|
||||||
const config = {
|
const config = {
|
||||||
method: method,
|
method: method,
|
||||||
...customConfig,
|
body: body ? JSON.stringify(body) : null,
|
||||||
headers: {
|
headers: {
|
||||||
...headers,
|
"Content-Type": "application/json"
|
||||||
...customConfig.headers,
|
|
||||||
},
|
},
|
||||||
}
|
// NOTE: customConfig can override the above defined settings
|
||||||
|
...customConfig
|
||||||
|
} as RequestInit;
|
||||||
|
|
||||||
if (body) {
|
return window.fetch(`${baseUrl()}${endpoint}`, config)
|
||||||
config.body = JSON.stringify(body)
|
|
||||||
}
|
|
||||||
|
|
||||||
return window.fetch(`${baseURL}${endpoint}`, config)
|
|
||||||
.then(async response => {
|
.then(async response => {
|
||||||
if (response.status === 401) {
|
if ([401, 403, 404].includes(response.status))
|
||||||
// unauthorized
|
return Promise.reject(new Error(response.statusText));
|
||||||
// window.location.assign(window.location)
|
|
||||||
|
|
||||||
return Promise.reject(new Error(response.statusText))
|
if ([201, 204].includes(response.status))
|
||||||
}
|
return Promise.resolve(response);
|
||||||
|
|
||||||
if (response.status === 403) {
|
|
||||||
// window.location.assign("/login")
|
|
||||||
return Promise.reject(new Error(response.statusText))
|
|
||||||
// return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.status === 404) {
|
|
||||||
return Promise.reject(new Error(response.statusText))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.status === 201) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.status === 204) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return await response.json()
|
return await response.json();
|
||||||
} else {
|
} else {
|
||||||
const errorMessage = await response.text()
|
const errorMessage = await response.text();
|
||||||
|
return Promise.reject(new Error(errorMessage));
|
||||||
return Promise.reject(new Error(errorMessage))
|
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const appClient = {
|
const appClient = {
|
||||||
Get: (endpoint: string) => baseClient(endpoint, "GET"),
|
Get: <T>(endpoint: string) => HttpClient<T>(endpoint, "GET"),
|
||||||
Post: (endpoint: string, data: any) => baseClient(endpoint, "POST", { body: data }),
|
Post: (endpoint: string, data: any) => HttpClient<void>(endpoint, "POST", { body: data }),
|
||||||
Put: (endpoint: string, data: any) => baseClient(endpoint, "PUT", { body: data }),
|
Put: (endpoint: string, data: any) => HttpClient<void>(endpoint, "PUT", { body: data }),
|
||||||
Patch: (endpoint: string, data: any) => baseClient(endpoint, "PATCH", { body: data }),
|
Patch: (endpoint: string, data: any) => HttpClient<void>(endpoint, "PATCH", { body: data }),
|
||||||
Delete: (endpoint: string) => baseClient(endpoint, "DELETE"),
|
Delete: (endpoint: string) => HttpClient<void>(endpoint, "DELETE")
|
||||||
}
|
}
|
||||||
|
|
||||||
const APIClient = {
|
export const APIClient = {
|
||||||
auth: {
|
auth: {
|
||||||
login: (username: string, password: string) => appClient.Post("api/auth/login", {username: username, password: password}),
|
login: (username: string, password: string) => appClient.Post("api/auth/login", { username: username, password: password }),
|
||||||
logout: () => appClient.Post(`api/auth/logout`, null),
|
logout: () => appClient.Post("api/auth/logout", null),
|
||||||
test: () => appClient.Get(`api/auth/test`),
|
test: () => appClient.Get<void>("api/auth/test"),
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
create: (action: Action) => appClient.Post("api/actions", action),
|
create: (action: Action) => appClient.Post("api/actions", action),
|
||||||
|
@ -75,34 +58,37 @@ const APIClient = {
|
||||||
toggleEnable: (id: number) => appClient.Patch(`api/actions/${id}/toggleEnabled`, null),
|
toggleEnable: (id: number) => appClient.Patch(`api/actions/${id}/toggleEnabled`, null),
|
||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
get: () => appClient.Get("api/config")
|
get: () => appClient.Get<Config>("api/config")
|
||||||
},
|
},
|
||||||
download_clients: {
|
download_clients: {
|
||||||
getAll: () => appClient.Get("api/download_clients"),
|
getAll: () => appClient.Get<DownloadClient[]>("api/download_clients"),
|
||||||
create: (dc: DownloadClient) => appClient.Post(`api/download_clients`, dc),
|
create: (dc: DownloadClient) => appClient.Post("api/download_clients", dc),
|
||||||
update: (dc: DownloadClient) => appClient.Put(`api/download_clients`, dc),
|
update: (dc: DownloadClient) => appClient.Put("api/download_clients", dc),
|
||||||
delete: (id: number) => appClient.Delete(`api/download_clients/${id}`),
|
delete: (id: number) => appClient.Delete(`api/download_clients/${id}`),
|
||||||
test: (dc: DownloadClient) => appClient.Post(`api/download_clients/test`, dc),
|
test: (dc: DownloadClient) => appClient.Post("api/download_clients/test", dc),
|
||||||
},
|
},
|
||||||
filters: {
|
filters: {
|
||||||
getAll: () => appClient.Get("api/filters"),
|
getAll: () => appClient.Get<Filter[]>("api/filters"),
|
||||||
getByID: (id: number) => appClient.Get(`api/filters/${id}`),
|
getByID: (id: number) => appClient.Get<Filter>(`api/filters/${id}`),
|
||||||
create: (filter: Filter) => appClient.Post(`api/filters`, filter),
|
create: (filter: Filter) => appClient.Post("api/filters", filter),
|
||||||
update: (filter: Filter) => appClient.Put(`api/filters/${filter.id}`, filter),
|
update: (filter: Filter) => appClient.Put(`api/filters/${filter.id}`, filter),
|
||||||
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}`),
|
||||||
},
|
},
|
||||||
indexers: {
|
indexers: {
|
||||||
getOptions: () => appClient.Get("api/indexer/options"),
|
// returns indexer options for all currently present/enabled indexers
|
||||||
getAll: () => appClient.Get("api/indexer"),
|
getOptions: () => appClient.Get<Indexer[]>("api/indexer/options"),
|
||||||
getSchema: () => appClient.Get("api/indexer/schema"),
|
// returns indexer definitions for all currently present/enabled indexers
|
||||||
create: (indexer: Indexer) => appClient.Post(`api/indexer`, indexer),
|
getAll: () => appClient.Get<IndexerDefinition[]>("api/indexer"),
|
||||||
update: (indexer: Indexer) => appClient.Put(`api/indexer`, indexer),
|
// returns all possible indexer definitions
|
||||||
|
getSchema: () => appClient.Get<IndexerDefinition[]>("api/indexer/schema"),
|
||||||
|
create: (indexer: Indexer) => appClient.Post("api/indexer", indexer),
|
||||||
|
update: (indexer: Indexer) => appClient.Put("api/indexer", indexer),
|
||||||
delete: (id: number) => appClient.Delete(`api/indexer/${id}`),
|
delete: (id: number) => appClient.Delete(`api/indexer/${id}`),
|
||||||
},
|
},
|
||||||
irc: {
|
irc: {
|
||||||
getNetworks: () => appClient.Get("api/irc"),
|
getNetworks: () => appClient.Get<IrcNetwork[]>("api/irc"),
|
||||||
createNetwork: (network: Network) => appClient.Post(`api/irc`, network),
|
createNetwork: (network: Network) => appClient.Post("api/irc", network),
|
||||||
updateNetwork: (network: Network) => appClient.Put(`api/irc/network/${network.id}`, network),
|
updateNetwork: (network: Network) => appClient.Put(`api/irc/network/${network.id}`, network),
|
||||||
deleteNetwork: (id: number) => appClient.Delete(`api/irc/network/${id}`),
|
deleteNetwork: (id: number) => appClient.Delete(`api/irc/network/${id}`),
|
||||||
},
|
},
|
||||||
|
@ -110,9 +96,7 @@ const APIClient = {
|
||||||
logs: () => new EventSource(`${sseBaseUrl()}api/events?stream=logs`, { withCredentials: true })
|
logs: () => new EventSource(`${sseBaseUrl()}api/events?stream=logs`, { withCredentials: true })
|
||||||
},
|
},
|
||||||
release: {
|
release: {
|
||||||
find: (query?: string) => appClient.Get(`api/release${query}`),
|
find: (query?: string) => appClient.Get<ReleaseFindResponse>(`api/release${query}`),
|
||||||
stats: () => appClient.Get(`api/release/stats`)
|
stats: () => appClient.Get<ReleaseStats>("api/release/stats")
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export default APIClient;
|
|
|
@ -7,7 +7,7 @@ import { Field, Form, Formik } from "formik";
|
||||||
import type { FieldProps } from "formik";
|
import type { FieldProps } from "formik";
|
||||||
|
|
||||||
import { queryClient } from "../../App";
|
import { queryClient } from "../../App";
|
||||||
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';
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { sleep, classNames } from "../../utils";
|
||||||
import { Form, Formik, useFormikContext } from "formik";
|
import { Form, Formik, useFormikContext } from "formik";
|
||||||
import DEBUG from "../../components/debug";
|
import DEBUG from "../../components/debug";
|
||||||
import { queryClient } from "../../App";
|
import { queryClient } from "../../App";
|
||||||
import APIClient from "../../api/APIClient";
|
import { APIClient } from "../../api/APIClient";
|
||||||
import { DownloadClientTypeOptions } from "../../domain/constants";
|
import { DownloadClientTypeOptions } from "../../domain/constants";
|
||||||
|
|
||||||
import { toast } from 'react-hot-toast'
|
import { toast } from 'react-hot-toast'
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { sleep } from "../../utils";
|
import { sleep } from "../../utils";
|
||||||
import { queryClient } from "../../App";
|
import { queryClient } from "../../App";
|
||||||
import DEBUG from "../../components/debug";
|
import DEBUG from "../../components/debug";
|
||||||
import APIClient from "../../api/APIClient";
|
import { APIClient } from "../../api/APIClient";
|
||||||
import {
|
import {
|
||||||
TextFieldWide,
|
TextFieldWide,
|
||||||
PasswordFieldWide,
|
PasswordFieldWide,
|
||||||
|
@ -48,13 +48,22 @@ const Menu = (props: any) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Option = (props: any) => {
|
||||||
|
return (
|
||||||
|
<components.Option
|
||||||
|
{...props}
|
||||||
|
className="dark:text-gray-400 dark:bg-gray-800 dark:hover:bg-gray-900 dark:focus:bg-gray-900"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
interface AddProps {
|
interface AddProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
toggle: any;
|
toggle: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function IndexerAddForm({ isOpen, toggle }: AddProps) {
|
export function IndexerAddForm({ isOpen, toggle }: AddProps) {
|
||||||
const { data } = useQuery<IndexerDefinition[], Error>('indexerDefinition', APIClient.indexers.getSchema,
|
const { data } = useQuery('indexerDefinition', APIClient.indexers.getSchema,
|
||||||
{
|
{
|
||||||
enabled: isOpen,
|
enabled: isOpen,
|
||||||
refetchOnWindowFocus: false
|
refetchOnWindowFocus: false
|
||||||
|
@ -237,8 +246,14 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
|
||||||
<Select {...field}
|
<Select {...field}
|
||||||
isClearable={true}
|
isClearable={true}
|
||||||
isSearchable={true}
|
isSearchable={true}
|
||||||
components={{ Input, Control, Menu }}
|
components={{ Input, Control, Menu, Option }}
|
||||||
placeholder="Choose an indexer"
|
placeholder="Choose an indexer"
|
||||||
|
styles={{
|
||||||
|
singleValue: (base) => ({
|
||||||
|
...base,
|
||||||
|
color: "unset"
|
||||||
|
})
|
||||||
|
}}
|
||||||
value={field?.value && field.value.value}
|
value={field?.value && field.value.value}
|
||||||
onChange={(option: any) => {
|
onChange={(option: any) => {
|
||||||
setFieldValue("name", option?.label ?? "")
|
setFieldValue("name", option?.label ?? "")
|
||||||
|
@ -248,7 +263,7 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
|
||||||
label: v.name,
|
label: v.name,
|
||||||
value: v.identifier
|
value: v.identifier
|
||||||
}))}
|
}))}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { Field, FieldArray } from "formik";
|
||||||
import type { FieldProps } from "formik";
|
import type { FieldProps } from "formik";
|
||||||
|
|
||||||
import { queryClient } from "../../App";
|
import { queryClient } from "../../App";
|
||||||
import APIClient from "../../api/APIClient";
|
import { APIClient } from "../../api/APIClient";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
TextFieldWide,
|
TextFieldWide,
|
||||||
|
|
|
@ -9,397 +9,127 @@ import {
|
||||||
usePagination
|
usePagination
|
||||||
} 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 { ReleaseStatusCell } from "./Releases";
|
import { ReleaseStatusCell } from "./Releases";
|
||||||
|
|
||||||
export function Dashboard() {
|
export function Dashboard() {
|
||||||
return (
|
return (
|
||||||
<main className="py-10 -mt-48">
|
<main className="py-10 -mt-48">
|
||||||
<div className="px-4 pb-8 mx-auto max-w-7xl sm:px-6 lg:px-8">
|
<div className="px-4 pb-8 mx-auto max-w-7xl sm:px-6 lg:px-8">
|
||||||
<Stats />
|
<Stats />
|
||||||
<DataTablee />
|
<DataTable />
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const StatsItem = ({ name, stat }: any) => (
|
const StatsItem = ({ name, stat }: any) => (
|
||||||
<div
|
<div
|
||||||
className="relative px-4 pt-5 pb-2 overflow-hidden bg-white rounded-lg shadow dark:bg-gray-800 sm:pt-6 sm:px-6"
|
className="relative px-4 pt-5 pb-2 overflow-hidden bg-white rounded-lg shadow dark:bg-gray-800 sm:pt-6 sm:px-6"
|
||||||
title="All time"
|
title="All time"
|
||||||
>
|
>
|
||||||
<dt>
|
<dt>
|
||||||
<p className="pb-1 text-sm font-medium text-gray-500 truncate dark:text-gray-600">{name}</p>
|
<p className="pb-1 text-sm font-medium text-gray-500 truncate dark:text-gray-600">{name}</p>
|
||||||
</dt>
|
</dt>
|
||||||
|
|
||||||
<dd className="flex items-baseline pb-6 sm:pb-7">
|
<dd className="flex items-baseline pb-6 sm:pb-7">
|
||||||
<p className="text-2xl font-semibold text-gray-900 dark:text-gray-300">{stat}</p>
|
<p className="text-2xl font-semibold text-gray-900 dark:text-gray-300">{stat}</p>
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
function Stats() {
|
function Stats() {
|
||||||
const { isLoading, data } = useQuery<ReleaseStats, Error>('dash_release_staats', () => APIClient.release.stats(),
|
const { isLoading, data } = useQuery(
|
||||||
{
|
'dash_release_stats',
|
||||||
refetchOnWindowFocus: false
|
() => APIClient.release.stats(),
|
||||||
}
|
{ refetchOnWindowFocus: false }
|
||||||
)
|
);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading)
|
||||||
return null
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-medium leading-6 text-gray-900 dark:text-gray-600">Stats</h3>
|
<h3 className="text-lg font-medium leading-6 text-gray-900 dark:text-gray-600">Stats</h3>
|
||||||
|
|
||||||
<dl className="grid grid-cols-1 gap-5 mt-5 sm:grid-cols-2 lg:grid-cols-3">
|
<dl className="grid grid-cols-1 gap-5 mt-5 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
<StatsItem name="Filtered Releases" stat={data?.filtered_count} />
|
<StatsItem name="Filtered Releases" stat={data?.filtered_count} />
|
||||||
{/* <StatsItem name="Filter Rejected Releases" stat={data?.filter_rejected_count} /> */}
|
{/* <StatsItem name="Filter Rejected Releases" stat={data?.filter_rejected_count} /> */}
|
||||||
<StatsItem name="Rejected Pushes" stat={data?.push_rejected_count} />
|
<StatsItem name="Rejected Pushes" stat={data?.push_rejected_count} />
|
||||||
<StatsItem name="Approved Pushes" stat={data?.push_approved_count} />
|
<StatsItem name="Approved Pushes" stat={data?.push_approved_count} />
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* function RecentActivity() {
|
|
||||||
let data: any[] = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
status: "FILTERED",
|
|
||||||
created_at: "2021-10-16 20:25:26",
|
|
||||||
indexer: "tl",
|
|
||||||
title: "That movie 2019 1080p x264-GROUP",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
status: "PUSH_APPROVED",
|
|
||||||
created_at: "2021-10-15 16:16:23",
|
|
||||||
indexer: "tl",
|
|
||||||
title: "That great movie 2009 1080p x264-1GROUP",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
status: "FILTER_REJECTED",
|
|
||||||
created_at: "2021-10-15 10:16:23",
|
|
||||||
indexer: "tl",
|
|
||||||
title: "Movie 1 2002 720p x264-1GROUP",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
status: "PUSH_APPROVED",
|
|
||||||
created_at: "2021-10-14 16:16:23",
|
|
||||||
indexer: "tl",
|
|
||||||
title: "That bad movie 2019 2160p x265-1GROUP",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
status: "PUSH_REJECTED",
|
|
||||||
created_at: "2021-10-13 16:16:23",
|
|
||||||
indexer: "tl",
|
|
||||||
title: "That really bad movie 20010 1080p x264-GROUP2",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col mt-12">
|
|
||||||
<h3 className="text-lg font-medium leading-6 text-gray-900 dark:text-gray-600">Recent activity</h3>
|
|
||||||
|
|
||||||
<div className="mt-3 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
|
||||||
<div className="inline-block min-w-full py-2 sm:px-6 lg:px-8">
|
|
||||||
<div className="overflow-hidden light:shadow light:border-b light:border-gray-200 sm:rounded-lg">
|
|
||||||
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
||||||
<thead className="light:bg-gray-50 dark:bg-gray-800">
|
|
||||||
<tr>
|
|
||||||
<th
|
|
||||||
scope="col"
|
|
||||||
className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase dark:text-gray-400"
|
|
||||||
>
|
|
||||||
Age
|
|
||||||
</th>
|
|
||||||
<th
|
|
||||||
scope="col"
|
|
||||||
className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase dark:text-gray-400"
|
|
||||||
>
|
|
||||||
Release
|
|
||||||
</th>
|
|
||||||
<th
|
|
||||||
scope="col"
|
|
||||||
className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase dark:text-gray-400"
|
|
||||||
>
|
|
||||||
Status
|
|
||||||
</th>
|
|
||||||
<th
|
|
||||||
scope="col"
|
|
||||||
className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase dark:text-gray-400"
|
|
||||||
>
|
|
||||||
Indexer
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="bg-gray-800 divide-y divide-gray-200 light:bg-white dark:divide-gray-700">
|
|
||||||
{data && data.length > 0 ?
|
|
||||||
data.map((release: any, idx) => (
|
|
||||||
<ListItem key={idx} idx={idx} release={release} />
|
|
||||||
))
|
|
||||||
: <span>No recent activity</span>}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<nav
|
|
||||||
className="flex items-center justify-between px-4 py-3 bg-white border-t border-gray-200 dark:bg-gray-800 dark:border-gray-700 sm:px-6"
|
|
||||||
aria-label="Pagination"
|
|
||||||
>
|
|
||||||
<div className="hidden sm:block">
|
|
||||||
<p className="text-sm text-gray-700 dark:text-gray-500">
|
|
||||||
Showing <span className="font-medium">1</span> to <span className="font-medium">10</span> of{' '}
|
|
||||||
<span className="font-medium">20</span> results
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-between flex-1 sm:justify-end">
|
|
||||||
<p className="relative text-sm text-gray-700 dark:text-gray-500">
|
|
||||||
Show <span className="font-medium">10</span>
|
|
||||||
</p>
|
|
||||||
<Menu as="div" className="relative text-left">
|
|
||||||
<Menu.Button className="flex items-center text-sm font-medium text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-600">
|
|
||||||
<span>Show</span>
|
|
||||||
<ChevronDownIcon className="w-5 h-5 ml-1 text-gray-500" aria-hidden="true" />
|
|
||||||
</Menu.Button>
|
|
||||||
|
|
||||||
<Transition
|
|
||||||
as={Fragment}
|
|
||||||
enter="transition ease-out duration-100"
|
|
||||||
enterFrom="transform opacity-0 scale-95"
|
|
||||||
enterTo="transform opacity-100 scale-100"
|
|
||||||
leave="transition ease-in duration-75"
|
|
||||||
leaveFrom="transform opacity-100 scale-100"
|
|
||||||
leaveTo="transform opacity-0 scale-95"
|
|
||||||
>
|
|
||||||
<Menu.Items className="absolute right-0 z-30 w-40 mt-2 origin-top-right bg-white rounded-md shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
|
|
||||||
<div className="py-1">
|
|
||||||
{[5, 10, 25, 50].map((child) => (
|
|
||||||
<Menu.Item key={child}>
|
|
||||||
{({ active }) => (
|
|
||||||
<a
|
|
||||||
// href={child.href}
|
|
||||||
className={classNames(
|
|
||||||
active ? 'bg-gray-100' : '',
|
|
||||||
'block px-4 py-2 text-sm text-gray-700'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{child}
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
</Menu.Item>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</Menu.Items>
|
|
||||||
</Transition>
|
|
||||||
</Menu>
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
// className="px-4 py-2 mr-4 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md shadow-sm dark:bg-gray-700 dark:border-gray-600 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-blue-500"
|
|
||||||
className="relative inline-flex items-center px-4 py-2 ml-5 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md dark:border-gray-600 dark:text-gray-400 dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600"
|
|
||||||
>
|
|
||||||
Previous
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
// className="relative inline-flex items-center px-4 py-2 ml-3 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50"
|
|
||||||
className="relative inline-flex items-center px-4 py-2 ml-3 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md dark:border-gray-600 dark:text-gray-400 dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600"
|
|
||||||
>
|
|
||||||
Next
|
|
||||||
</a>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)
|
||||||
|
}
|
||||||
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
} */
|
|
||||||
|
|
||||||
/* const ListItem = ({ idx, release }: any) => {
|
|
||||||
|
|
||||||
const formatDate = formatDistanceToNowStrict(
|
|
||||||
new Date(release.created_at),
|
|
||||||
{ addSuffix: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<tr key={release.id} className={idx % 2 === 0 ? 'light:bg-white' : 'light:bg-gray-50'}>
|
|
||||||
<td className="px-6 py-4 text-sm text-gray-500 whitespace-nowrap dark:text-gray-400" title={release.created_at}>{formatDate}</td>
|
|
||||||
<td className="px-6 py-4 text-sm font-medium text-gray-900 whitespace-nowrap dark:text-gray-300">{release.title}</td>
|
|
||||||
<td className="px-6 py-4 text-sm text-gray-500 whitespace-nowrap dark:text-gray-300">{statusMap[release.status]}</td>
|
|
||||||
<td className="px-6 py-4 text-sm text-gray-500 whitespace-nowrap dark:text-gray-300">{release.indexer}</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
)
|
|
||||||
} */
|
|
||||||
/*
|
|
||||||
const getData = () => {
|
|
||||||
|
|
||||||
const data: any[] = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
status: "FILTERED",
|
|
||||||
created_at: "2021-10-16 20:25:26",
|
|
||||||
indexer: "tl",
|
|
||||||
title: "That movie 2019 1080p x264-GROUP",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
status: "PUSH_APPROVED",
|
|
||||||
created_at: "2021-10-15 16:16:23",
|
|
||||||
indexer: "tl",
|
|
||||||
title: "That great movie 2009 1080p x264-1GROUP",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
status: "FILTER_REJECTED",
|
|
||||||
created_at: "2021-10-15 10:16:23",
|
|
||||||
indexer: "tl",
|
|
||||||
title: "Movie 1 2002 720p x264-1GROUP",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
status: "PUSH_APPROVED",
|
|
||||||
created_at: "2021-10-14 16:16:23",
|
|
||||||
indexer: "tl",
|
|
||||||
title: "That bad movie 2019 2160p x265-1GROUP",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
status: "PUSH_REJECTED",
|
|
||||||
created_at: "2021-10-13 16:16:23",
|
|
||||||
indexer: "tl",
|
|
||||||
title: "That really bad movie 20010 1080p x264-GROUP2",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
return [...data, ...data, ...data]
|
|
||||||
} */
|
|
||||||
|
|
||||||
// Define a default UI for filtering
|
|
||||||
/* function GlobalFilter({
|
|
||||||
preGlobalFilteredRows,
|
|
||||||
globalFilter,
|
|
||||||
setGlobalFilter,
|
|
||||||
}: any) {
|
|
||||||
const count = preGlobalFilteredRows.length
|
|
||||||
const [value, setValue] = React.useState(globalFilter)
|
|
||||||
const onChange = useAsyncDebounce((value: any) => {
|
|
||||||
setGlobalFilter(value || undefined)
|
|
||||||
}, 200)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<label className="flex items-baseline gap-x-2">
|
|
||||||
<span className="text-gray-700">Search: </span>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="border-gray-300 rounded-md shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
|
|
||||||
value={value || ""}
|
|
||||||
onChange={e => {
|
|
||||||
setValue(e.target.value);
|
|
||||||
onChange(e.target.value);
|
|
||||||
}}
|
|
||||||
placeholder={`${count} records...`}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
)
|
|
||||||
} */
|
|
||||||
|
|
||||||
// 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
|
||||||
export function SelectColumnFilter({
|
export function SelectColumnFilter({
|
||||||
column: { filterValue, setFilter, preFilteredRows, id, render },
|
column: { filterValue, setFilter, preFilteredRows, id, render },
|
||||||
}: any) {
|
}: any) {
|
||||||
// Calculate the options for filtering
|
// Calculate the options for filtering
|
||||||
// using the preFilteredRows
|
// using the preFilteredRows
|
||||||
const options = React.useMemo(() => {
|
const options = React.useMemo(() => {
|
||||||
const options: any = new Set()
|
const options: any = new Set()
|
||||||
preFilteredRows.forEach((row: { values: { [x: string]: unknown } }) => {
|
preFilteredRows.forEach((row: { values: { [x: string]: unknown } }) => {
|
||||||
options.add(row.values[id])
|
options.add(row.values[id])
|
||||||
})
|
})
|
||||||
return [...options.values()]
|
return [...options.values()]
|
||||||
}, [id, preFilteredRows])
|
}, [id, preFilteredRows])
|
||||||
|
|
||||||
// Render a multi-select box
|
// Render a multi-select box
|
||||||
return (
|
return (
|
||||||
<label className="flex items-baseline gap-x-2">
|
<label className="flex items-baseline gap-x-2">
|
||||||
<span className="text-gray-700">{render("Header")}: </span>
|
<span className="text-gray-700">{render("Header")}: </span>
|
||||||
<select
|
<select
|
||||||
className="border-gray-300 rounded-md shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
|
className="border-gray-300 rounded-md shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
|
||||||
name={id}
|
name={id}
|
||||||
id={id}
|
id={id}
|
||||||
value={filterValue}
|
value={filterValue}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
setFilter(e.target.value || undefined)
|
setFilter(e.target.value || undefined)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<option value="">All</option>
|
<option value="">All</option>
|
||||||
{options.map((option, i) => (
|
{options.map((option, i) => (
|
||||||
<option key={i} value={option}>
|
<option key={i} value={option}>
|
||||||
{option}
|
{option}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// export function StatusPill({ value }: any) {
|
|
||||||
|
|
||||||
// const status = value ? value.toLowerCase() : "unknown";
|
|
||||||
|
|
||||||
// return (
|
|
||||||
// <span
|
|
||||||
// className={
|
|
||||||
// classNames(
|
|
||||||
// "px-3 py-1 uppercase leading-wide font-bold text-xs rounded-full shadow-sm",
|
|
||||||
// status.startsWith("active") ? "bg-green-100 text-green-800" : "",
|
|
||||||
// status.startsWith("inactive") ? "bg-yellow-100 text-yellow-800" : "",
|
|
||||||
// status.startsWith("offline") ? "bg-red-100 text-red-800" : "",
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// >
|
|
||||||
// {status}
|
|
||||||
// </span>
|
|
||||||
// );
|
|
||||||
// };
|
|
||||||
|
|
||||||
export function StatusPill({ value }: any) {
|
export function StatusPill({ value }: any) {
|
||||||
const statusMap: any = {
|
const statusMap: any = {
|
||||||
"FILTER_APPROVED": <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold uppercase bg-blue-100 text-blue-800 ">Approved</span>,
|
"FILTER_APPROVED": <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold uppercase bg-blue-100 text-blue-800 ">Approved</span>,
|
||||||
"FILTER_REJECTED": <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold uppercase bg-red-100 text-red-800">Rejected</span>,
|
"FILTER_REJECTED": <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold uppercase bg-red-100 text-red-800">Rejected</span>,
|
||||||
"PUSH_REJECTED": <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold uppercase bg-pink-100 text-pink-800">Rejected</span>,
|
"PUSH_REJECTED": <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold uppercase bg-pink-100 text-pink-800">Rejected</span>,
|
||||||
"PUSH_APPROVED": <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold uppercase bg-green-100 text-green-800">Approved</span>,
|
"PUSH_APPROVED": <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold uppercase bg-green-100 text-green-800">Approved</span>,
|
||||||
"PENDING": <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold uppercase bg-yellow-100 text-yellow-800">PENDING</span>,
|
"PENDING": <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold uppercase bg-yellow-100 text-yellow-800">PENDING</span>,
|
||||||
"MIXED": <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold uppercase bg-yellow-100 text-yellow-800">MIXED</span>,
|
"MIXED": <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold uppercase bg-yellow-100 text-yellow-800">MIXED</span>,
|
||||||
}
|
}
|
||||||
|
|
||||||
return statusMap[value];
|
return statusMap[value];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AgeCell({ value }: any) {
|
export function AgeCell({ value }: any) {
|
||||||
const formatDate = formatDistanceToNowStrict(
|
const formatDate = formatDistanceToNowStrict(
|
||||||
new Date(value),
|
new Date(value),
|
||||||
{ addSuffix: true }
|
{ addSuffix: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="text-sm text-gray-500" title={value}>{formatDate}</div>
|
<div className="text-sm text-gray-500" title={value}>{formatDate}</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ReleaseCell({ value }: any) {
|
export function ReleaseCell({ value }: any) {
|
||||||
return (
|
return (
|
||||||
<div className="text-sm font-medium text-gray-900 dark:text-gray-300">{value}</div>
|
<div className="text-sm font-medium text-gray-900 dark:text-gray-300">{value}</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function IndexerCell({ value }: any) {
|
export function IndexerCell({ value }: any) {
|
||||||
|
@ -409,307 +139,183 @@ export function IndexerCell({ value }: any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function Table({ columns, data }: any) {
|
function Table({ columns, data }: any) {
|
||||||
// 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 {
|
||||||
getTableProps,
|
getTableProps,
|
||||||
getTableBodyProps,
|
getTableBodyProps,
|
||||||
headerGroups,
|
headerGroups,
|
||||||
prepareRow,
|
prepareRow,
|
||||||
page, // Instead of using 'rows', we'll use page,
|
page, // Instead of using 'rows', we'll use page,
|
||||||
// which has only the rows for the active page
|
// which has only the rows for the active page
|
||||||
|
|
||||||
// The rest of these things are super handy, too ;)
|
// The rest of these things are super handy, too ;)
|
||||||
// canPreviousPage,
|
// canPreviousPage,
|
||||||
// canNextPage,
|
// canNextPage,
|
||||||
// pageOptions,
|
// pageOptions,
|
||||||
// pageCount,
|
// pageCount,
|
||||||
// gotoPage,
|
// gotoPage,
|
||||||
// nextPage,
|
// nextPage,
|
||||||
// previousPage,
|
// previousPage,
|
||||||
// setPageSize,
|
// setPageSize,
|
||||||
|
|
||||||
// state,
|
// state,
|
||||||
// preGlobalFilteredRows,
|
// preGlobalFilteredRows,
|
||||||
// setGlobalFilter,
|
// setGlobalFilter,
|
||||||
} = useTable({
|
} = useTable({
|
||||||
columns,
|
columns,
|
||||||
data,
|
data,
|
||||||
},
|
},
|
||||||
useFilters, // useFilters!
|
useFilters, // useFilters!
|
||||||
useGlobalFilter,
|
useGlobalFilter,
|
||||||
useSortBy,
|
useSortBy,
|
||||||
usePagination, // new
|
usePagination, // new
|
||||||
)
|
);
|
||||||
|
|
||||||
// Render the UI for your table
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="sm:flex sm:gap-x-2">
|
|
||||||
{/* <GlobalFilter
|
|
||||||
preGlobalFilteredRows={preGlobalFilteredRows}
|
|
||||||
globalFilter={state.globalFilter}
|
|
||||||
setGlobalFilter={setGlobalFilter}
|
|
||||||
/> */}
|
|
||||||
{/* {headerGroups.map((headerGroup: { headers: any[] }) =>
|
|
||||||
headerGroup.headers.map((column) =>
|
|
||||||
column.Filter ? (
|
|
||||||
<div className="mt-2 sm:mt-0" key={column.id}>
|
|
||||||
{column.render("Filter")}
|
|
||||||
</div>
|
|
||||||
) : null
|
|
||||||
)
|
|
||||||
)} */}
|
|
||||||
</div>
|
|
||||||
{page.length > 0 ?
|
|
||||||
<div className="flex flex-col mt-4">
|
|
||||||
<div className="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
|
||||||
<div className="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
|
|
||||||
<div className="overflow-hidden bg-white shadow dark:bg-gray-800 sm:rounded-lg">
|
|
||||||
<table {...getTableProps()} className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
||||||
<thead className="bg-gray-50 dark:bg-gray-800">
|
|
||||||
{headerGroups.map((headerGroup) => {
|
|
||||||
const { key: rowKey, ...rowRest } = headerGroup.getHeaderGroupProps();
|
|
||||||
return (
|
|
||||||
<tr key={rowKey} {...rowRest}>
|
|
||||||
{headerGroup.headers.map((column) => {
|
|
||||||
const { key: columnKey, ...columnRest } = column.getHeaderProps(column.getSortByToggleProps());
|
|
||||||
return (
|
|
||||||
// Add the sorting props to control sorting. For this example
|
|
||||||
// we can add them into the header props
|
|
||||||
<th
|
|
||||||
key={`${rowKey}-${columnKey}`}
|
|
||||||
scope="col"
|
|
||||||
className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase group"
|
|
||||||
{...columnRest}
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
{column.render('Header')}
|
|
||||||
{/* Add a sort direction indicator */}
|
|
||||||
<span>
|
|
||||||
{column.isSorted ? (
|
|
||||||
column.isSortedDesc ? (
|
|
||||||
<SortDownIcon className="w-4 h-4 text-gray-400" />
|
|
||||||
) : (
|
|
||||||
<SortUpIcon className="w-4 h-4 text-gray-400" />
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<SortIcon className="w-4 h-4 text-gray-400 opacity-0 group-hover:opacity-100" />
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</thead>
|
|
||||||
<tbody
|
|
||||||
{...getTableBodyProps()}
|
|
||||||
className="divide-y divide-gray-200 dark:divide-gray-700"
|
|
||||||
>
|
|
||||||
{page.map((row: any) => {
|
|
||||||
prepareRow(row);
|
|
||||||
const { key: bodyRowKey, ...bodyRowRest } = row.getRowProps();
|
|
||||||
return (
|
|
||||||
<tr key={bodyRowKey} {...bodyRowRest}>
|
|
||||||
{row.cells.map((cell: any) => {
|
|
||||||
const { key: cellRowKey, ...cellRowRest } = cell.getCellProps();
|
|
||||||
return (
|
|
||||||
<td
|
|
||||||
key={cellRowKey}
|
|
||||||
className="px-6 py-4 whitespace-nowrap"
|
|
||||||
role="cell"
|
|
||||||
{...cellRowRest}
|
|
||||||
>
|
|
||||||
{cell.column.Cell.name === "defaultRenderer"
|
|
||||||
? <div className="text-sm text-gray-500">{cell.render('Cell')}</div>
|
|
||||||
: cell.render('Cell')
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</tr>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
|
|
||||||
{/* Pagination */}
|
|
||||||
{/* <div className="flex items-center justify-between px-6 py-3 border-t border-gray-200 dark:border-gray-700">
|
|
||||||
<div className="flex justify-between flex-1 sm:hidden">
|
|
||||||
<Button onClick={() => previousPage()} disabled={!canPreviousPage}>Previous</Button>
|
|
||||||
<Button onClick={() => nextPage()} disabled={!canNextPage}>Next</Button>
|
|
||||||
</div>
|
|
||||||
<div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
|
||||||
<div className="flex items-baseline gap-x-2">
|
|
||||||
<span className="text-sm text-gray-700">
|
|
||||||
Page <span className="font-medium">{state.pageIndex + 1}</span> of <span className="font-medium">{pageOptions.length}</span>
|
|
||||||
</span>
|
|
||||||
<label>
|
|
||||||
<span className="sr-only">Items Per Page</span>
|
|
||||||
<select
|
|
||||||
className="block w-full border-gray-300 rounded-md shadow-sm cursor-pointer dark:bg-gray-800 dark:border-gray-800 dark:text-gray-600 dark:hover:text-gray-500 focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50"
|
|
||||||
value={state.pageSize}
|
|
||||||
onChange={e => {
|
|
||||||
setPageSize(Number(e.target.value))
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{[5, 10, 20].map(pageSize => (
|
|
||||||
<option key={pageSize} value={pageSize}>
|
|
||||||
Show {pageSize}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<nav className="relative z-0 inline-flex -space-x-px rounded-md shadow-sm" aria-label="Pagination">
|
|
||||||
<PageButton
|
|
||||||
className="rounded-l-md"
|
|
||||||
onClick={() => gotoPage(0)}
|
|
||||||
disabled={!canPreviousPage}
|
|
||||||
>
|
|
||||||
<span className="sr-only">First</span>
|
|
||||||
<ChevronDoubleLeftIcon className="w-5 h-5 text-gray-400" aria-hidden="true" />
|
|
||||||
</PageButton>
|
|
||||||
<PageButton
|
|
||||||
onClick={() => previousPage()}
|
|
||||||
disabled={!canPreviousPage}
|
|
||||||
>
|
|
||||||
<span className="sr-only">Previous</span>
|
|
||||||
<ChevronLeftIcon className="w-5 h-5 text-gray-400" aria-hidden="true" />
|
|
||||||
</PageButton>
|
|
||||||
<PageButton
|
|
||||||
onClick={() => nextPage()}
|
|
||||||
disabled={!canNextPage
|
|
||||||
}>
|
|
||||||
<span className="sr-only">Next</span>
|
|
||||||
<ChevronRightIcon className="w-5 h-5 text-gray-400" aria-hidden="true" />
|
|
||||||
</PageButton>
|
|
||||||
<PageButton
|
|
||||||
className="rounded-r-md"
|
|
||||||
onClick={() => gotoPage(pageCount - 1)}
|
|
||||||
disabled={!canNextPage}
|
|
||||||
>
|
|
||||||
<span className="sr-only">Last</span>
|
|
||||||
<ChevronDoubleRightIcon className="w-5 h-5 text-gray-400" aria-hidden="true" />
|
|
||||||
</PageButton>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div> */}
|
|
||||||
|
|
||||||
|
if (!page.length)
|
||||||
|
return <EmptyListState text="No recent activity" />;
|
||||||
|
|
||||||
|
// Render the UI for your table
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col mt-4">
|
||||||
|
<div className="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
||||||
|
<div className="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
|
||||||
|
<div className="overflow-hidden bg-white shadow dark:bg-gray-800 sm:rounded-lg">
|
||||||
|
<table {...getTableProps()} className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
|
<thead className="bg-gray-50 dark:bg-gray-800">
|
||||||
|
{headerGroups.map((headerGroup) => {
|
||||||
|
const { key: rowKey, ...rowRest } = headerGroup.getHeaderGroupProps();
|
||||||
|
return (
|
||||||
|
<tr key={rowKey} {...rowRest}>
|
||||||
|
{headerGroup.headers.map((column) => {
|
||||||
|
const { key: columnKey, ...columnRest } = column.getHeaderProps(column.getSortByToggleProps());
|
||||||
|
return (
|
||||||
|
// Add the sorting props to control sorting. For this example
|
||||||
|
// we can add them into the header props
|
||||||
|
<th
|
||||||
|
key={`${rowKey}-${columnKey}`}
|
||||||
|
scope="col"
|
||||||
|
className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase group"
|
||||||
|
{...columnRest}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
{column.render('Header')}
|
||||||
|
{/* Add a sort direction indicator */}
|
||||||
|
<span>
|
||||||
|
{column.isSorted ? (
|
||||||
|
column.isSortedDesc ? (
|
||||||
|
<SortDownIcon className="w-4 h-4 text-gray-400" />
|
||||||
|
) : (
|
||||||
|
<SortUpIcon className="w-4 h-4 text-gray-400" />
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<SortIcon className="w-4 h-4 text-gray-400 opacity-0 group-hover:opacity-100" />
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</thead>
|
||||||
|
<tbody
|
||||||
|
{...getTableBodyProps()}
|
||||||
|
className="divide-y divide-gray-200 dark:divide-gray-700"
|
||||||
|
>
|
||||||
|
{page.map((row: any) => {
|
||||||
|
prepareRow(row);
|
||||||
|
const { key: bodyRowKey, ...bodyRowRest } = row.getRowProps();
|
||||||
|
return (
|
||||||
|
<tr key={bodyRowKey} {...bodyRowRest}>
|
||||||
|
{row.cells.map((cell: any) => {
|
||||||
|
const { key: cellRowKey, ...cellRowRest } = cell.getCellProps();
|
||||||
|
return (
|
||||||
|
<td
|
||||||
|
key={cellRowKey}
|
||||||
|
className="px-6 py-4 whitespace-nowrap"
|
||||||
|
role="cell"
|
||||||
|
{...cellRowRest}
|
||||||
|
>
|
||||||
|
{cell.column.Cell.name === "defaultRenderer"
|
||||||
|
? <div className="text-sm text-gray-500">{cell.render('Cell')}</div>
|
||||||
|
: cell.render('Cell')
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
: <EmptyListState text="No recent activity"/>}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function SortIcon({ className }: any) {
|
function SortIcon({ className }: any) {
|
||||||
return (
|
return (
|
||||||
<svg className={className} stroke="currentColor" fill="currentColor" strokeWidth="0" viewBox="0 0 320 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M41 288h238c21.4 0 32.1 25.9 17 41L177 448c-9.4 9.4-24.6 9.4-33.9 0L24 329c-15.1-15.1-4.4-41 17-41zm255-105L177 64c-9.4-9.4-24.6-9.4-33.9 0L24 183c-15.1 15.1-4.4 41 17 41h238c21.4 0 32.1-25.9 17-41z"></path></svg>
|
<svg className={className} stroke="currentColor" fill="currentColor" strokeWidth="0" viewBox="0 0 320 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M41 288h238c21.4 0 32.1 25.9 17 41L177 448c-9.4 9.4-24.6 9.4-33.9 0L24 329c-15.1-15.1-4.4-41 17-41zm255-105L177 64c-9.4-9.4-24.6-9.4-33.9 0L24 183c-15.1 15.1-4.4 41 17 41h238c21.4 0 32.1-25.9 17-41z"></path></svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function SortUpIcon({ className }: any) {
|
function SortUpIcon({ className }: any) {
|
||||||
return (
|
return (
|
||||||
<svg className={className} stroke="currentColor" fill="currentColor" strokeWidth="0" viewBox="0 0 320 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M279 224H41c-21.4 0-32.1-25.9-17-41L143 64c9.4-9.4 24.6-9.4 33.9 0l119 119c15.2 15.1 4.5 41-16.9 41z"></path></svg>
|
<svg className={className} stroke="currentColor" fill="currentColor" strokeWidth="0" viewBox="0 0 320 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M279 224H41c-21.4 0-32.1-25.9-17-41L143 64c9.4-9.4 24.6-9.4 33.9 0l119 119c15.2 15.1 4.5 41-16.9 41z"></path></svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function SortDownIcon({ className }: any) {
|
function SortDownIcon({ className }: any) {
|
||||||
return (
|
return (
|
||||||
<svg className={className} stroke="currentColor" fill="currentColor" strokeWidth="0" viewBox="0 0 320 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M41 288h238c21.4 0 32.1 25.9 17 41L177 448c-9.4 9.4-24.6 9.4-33.9 0L24 329c-15.1-15.1-4.4-41 17-41z"></path></svg>
|
<svg className={className} stroke="currentColor" fill="currentColor" strokeWidth="0" viewBox="0 0 320 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M41 288h238c21.4 0 32.1 25.9 17 41L177 448c-9.4 9.4-24.6 9.4-33.9 0L24 329c-15.1-15.1-4.4-41 17-41z"></path></svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* function Button({ children, className, ...rest }: any) {
|
function DataTable() {
|
||||||
return (
|
const columns = React.useMemo(() => [
|
||||||
<button
|
{
|
||||||
type="button"
|
Header: "Age",
|
||||||
className={
|
accessor: 'timestamp',
|
||||||
classNames(
|
Cell: AgeCell,
|
||||||
"relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50",
|
},
|
||||||
className
|
{
|
||||||
)}
|
Header: "Release",
|
||||||
{...rest}
|
accessor: 'torrent_name',
|
||||||
>
|
Cell: ReleaseCell,
|
||||||
{children}
|
},
|
||||||
</button>
|
{
|
||||||
)
|
Header: "Actions",
|
||||||
}
|
accessor: 'action_status',
|
||||||
|
Cell: ReleaseStatusCell,
|
||||||
function PageButton({ children, className, ...rest }: any) {
|
},
|
||||||
return (
|
{
|
||||||
<button
|
Header: "Indexer",
|
||||||
type="button"
|
accessor: 'indexer',
|
||||||
className={
|
Cell: IndexerCell,
|
||||||
classNames(
|
Filter: SelectColumnFilter, // new
|
||||||
"relative inline-flex items-center px-2 py-2 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-sm font-medium text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-600",
|
filter: 'includes',
|
||||||
className
|
},
|
||||||
)}
|
], [])
|
||||||
{...rest}
|
|
||||||
>
|
const { isLoading, data } = useQuery(
|
||||||
{children}
|
'dash_release',
|
||||||
</button>
|
() => APIClient.release.find("?limit=10"),
|
||||||
)
|
{ refetchOnWindowFocus: false }
|
||||||
} */
|
);
|
||||||
|
|
||||||
function DataTablee() {
|
if (isLoading)
|
||||||
|
return null;
|
||||||
const columns = React.useMemo(() => [
|
|
||||||
{
|
return (
|
||||||
Header: "Age",
|
<div className="flex flex-col mt-12">
|
||||||
accessor: 'timestamp',
|
<h3 className="text-lg font-medium leading-6 text-gray-900 dark:text-gray-600">
|
||||||
Cell: AgeCell,
|
Recent activity
|
||||||
},
|
</h3>
|
||||||
{
|
|
||||||
Header: "Release",
|
<Table columns={columns} data={data?.data} />
|
||||||
accessor: 'torrent_name',
|
</div>
|
||||||
Cell: ReleaseCell,
|
);
|
||||||
},
|
|
||||||
// {
|
|
||||||
// Header: "Filter Status",
|
|
||||||
// accessor: 'filter_status',
|
|
||||||
// Cell: StatusPill,
|
|
||||||
// },
|
|
||||||
{
|
|
||||||
Header: "Actions",
|
|
||||||
accessor: 'action_status',
|
|
||||||
Cell: ReleaseStatusCell,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: "Indexer",
|
|
||||||
accessor: 'indexer',
|
|
||||||
Cell: IndexerCell,
|
|
||||||
Filter: SelectColumnFilter, // new
|
|
||||||
filter: 'includes',
|
|
||||||
},
|
|
||||||
], [])
|
|
||||||
|
|
||||||
// const data = React.useMemo(() => getData(), [])
|
|
||||||
|
|
||||||
const { isLoading, data } = useQuery<ReleaseFindResponse, Error>('dash_release', () => APIClient.release.find("?limit=10"),
|
|
||||||
{
|
|
||||||
refetchOnWindowFocus: false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col mt-12">
|
|
||||||
<h3 className="text-lg font-medium leading-6 text-gray-900 dark:text-gray-600">Recent activity</h3>
|
|
||||||
|
|
||||||
<Table columns={columns} data={data?.data} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import APIClient from "../api/APIClient";
|
import { APIClient } from "../api/APIClient";
|
||||||
|
|
||||||
type LogEvent = {
|
type LogEvent = {
|
||||||
time: string;
|
time: string;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from "react-query";
|
||||||
import { formatDistanceToNowStrict } from "date-fns";
|
import { formatDistanceToNowStrict } from "date-fns";
|
||||||
import { useTable, useSortBy, usePagination } from "react-table";
|
import { useTable, useSortBy, usePagination, Column } from "react-table";
|
||||||
import {
|
import {
|
||||||
ClockIcon,
|
ClockIcon,
|
||||||
BanIcon,
|
BanIcon,
|
||||||
|
@ -15,7 +15,7 @@ import {
|
||||||
CheckIcon
|
CheckIcon
|
||||||
} from "@heroicons/react/solid";
|
} from "@heroicons/react/solid";
|
||||||
|
|
||||||
import APIClient from "../api/APIClient";
|
import { APIClient } from "../api/APIClient";
|
||||||
import { EmptyListState } from "../components/emptystates";
|
import { EmptyListState } from "../components/emptystates";
|
||||||
import { classNames, simplifyDate } from "../utils";
|
import { classNames, simplifyDate } from "../utils";
|
||||||
|
|
||||||
|
@ -222,7 +222,7 @@ function Table() {
|
||||||
Filter: SelectColumnFilter, // new
|
Filter: SelectColumnFilter, // new
|
||||||
filter: 'includes',
|
filter: 'includes',
|
||||||
},
|
},
|
||||||
], [])
|
] as Column<Release>[], [])
|
||||||
|
|
||||||
const [{ queryPageIndex, queryPageSize, totalCount }, dispatch] =
|
const [{ queryPageIndex, queryPageSize, totalCount }, dispatch] =
|
||||||
React.useReducer(reducer, initialState);
|
React.useReducer(reducer, initialState);
|
||||||
|
@ -260,7 +260,7 @@ function Table() {
|
||||||
// setGlobalFilter,
|
// setGlobalFilter,
|
||||||
} = useTable({
|
} = useTable({
|
||||||
columns,
|
columns,
|
||||||
data: isSuccess ? data.data : [],
|
data: data && isSuccess ? data.data : [],
|
||||||
initialState: {
|
initialState: {
|
||||||
pageIndex: queryPageIndex,
|
pageIndex: queryPageIndex,
|
||||||
pageSize: queryPageSize,
|
pageSize: queryPageSize,
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { useHistory } from "react-router-dom";
|
||||||
import { useMutation } from "react-query";
|
import { useMutation } from "react-query";
|
||||||
import { Form, Formik } from "formik";
|
import { Form, Formik } from "formik";
|
||||||
|
|
||||||
import APIClient from "../../api/APIClient";
|
import { APIClient } from "../../api/APIClient";
|
||||||
import { TextField, PasswordField } from "../../components/inputs";
|
import { TextField, PasswordField } from "../../components/inputs";
|
||||||
|
|
||||||
import logo from "../../logo.png";
|
import logo from "../../logo.png";
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {useEffect} from "react";
|
||||||
import {useCookies} from "react-cookie";
|
import {useCookies} from "react-cookie";
|
||||||
import {useHistory} from "react-router-dom";
|
import {useHistory} from "react-router-dom";
|
||||||
|
|
||||||
import APIClient from "../../api/APIClient";
|
import { APIClient } from "../../api/APIClient";
|
||||||
import { AuthContext } from "../../utils/Context";
|
import { AuthContext } from "../../utils/Context";
|
||||||
|
|
||||||
function Logout() {
|
function Logout() {
|
||||||
|
|
|
@ -29,7 +29,7 @@ import {
|
||||||
RELEASE_TYPE_MUSIC_OPTIONS
|
RELEASE_TYPE_MUSIC_OPTIONS
|
||||||
} from "../../domain/constants";
|
} from "../../domain/constants";
|
||||||
import { queryClient } from "../../App";
|
import { queryClient } from "../../App";
|
||||||
import APIClient from "../../api/APIClient";
|
import { APIClient } from "../../api/APIClient";
|
||||||
import { useToggle } from "../../hooks/hooks";
|
import { useToggle } from "../../hooks/hooks";
|
||||||
import { buildPath, classNames } from "../../utils";
|
import { buildPath, classNames } from "../../utils";
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@ export default function FilterDetails() {
|
||||||
const { url } = useRouteMatch();
|
const { url } = useRouteMatch();
|
||||||
const { filterId } = useParams<{ filterId: string }>();
|
const { filterId } = useParams<{ filterId: string }>();
|
||||||
|
|
||||||
const { isLoading, data: filter } = useQuery<Filter, Error>(
|
const { isLoading, data: filter } = useQuery(
|
||||||
['filter', +filterId],
|
['filter', +filterId],
|
||||||
() => APIClient.filters.getByID(parseInt(filterId)),
|
() => APIClient.filters.getByID(parseInt(filterId)),
|
||||||
{
|
{
|
||||||
|
@ -144,14 +144,15 @@ export default function FilterDetails() {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const updateMutation = useMutation((filter: Filter) => APIClient.filters.update(filter), {
|
const updateMutation = useMutation(
|
||||||
onSuccess: (filter) => {
|
(filter: Filter) => APIClient.filters.update(filter),
|
||||||
// queryClient.setQueryData(['filter', filter.id], data)
|
{
|
||||||
toast.custom((t) => <Toast type="success" body={`${filter.name} was updated successfully`} t={t} />)
|
onSuccess: () => {
|
||||||
|
toast.custom((t) => <Toast type="success" body={`${filter?.name} was updated successfully`} t={t} />)
|
||||||
queryClient.invalidateQueries(["filter", filter.id]);
|
queryClient.invalidateQueries(["filter", filter?.id]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
);
|
||||||
|
|
||||||
const deleteMutation = useMutation((id: number) => APIClient.filters.delete(id), {
|
const deleteMutation = useMutation((id: number) => APIClient.filters.delete(id), {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
@ -315,11 +316,11 @@ export default function FilterDetails() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function General() {
|
function General() {
|
||||||
const { isLoading, data: indexers } = useQuery<Indexer[], Error>(["filter", "indexer_list"], APIClient.indexers.getOptions,
|
const { isLoading, data: indexers } = useQuery(
|
||||||
{
|
["filter", "indexer_list"],
|
||||||
refetchOnWindowFocus: false
|
APIClient.indexers.getOptions,
|
||||||
}
|
{ 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,
|
||||||
|
@ -592,11 +593,11 @@ interface FilterActionsProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function FilterActions({ filter, values }: FilterActionsProps) {
|
function FilterActions({ filter, values }: FilterActionsProps) {
|
||||||
const { data } = useQuery<DownloadClient[], Error>(['filter', 'download_clients'], APIClient.download_clients.getAll,
|
const { data } = useQuery(
|
||||||
{
|
['filter', 'download_clients'],
|
||||||
refetchOnWindowFocus: false
|
APIClient.download_clients.getAll,
|
||||||
}
|
{ refetchOnWindowFocus: false }
|
||||||
)
|
);
|
||||||
|
|
||||||
const newAction = {
|
const newAction = {
|
||||||
name: "new action",
|
name: "new action",
|
||||||
|
|
|
@ -8,17 +8,17 @@ import { queryClient } from "../../App";
|
||||||
import { classNames } from "../../utils";
|
import { classNames } from "../../utils";
|
||||||
import { FilterAddForm } from "../../forms";
|
import { FilterAddForm } from "../../forms";
|
||||||
import { useToggle } from "../../hooks/hooks";
|
import { useToggle } from "../../hooks/hooks";
|
||||||
import APIClient from "../../api/APIClient";
|
import { APIClient } from "../../api/APIClient";
|
||||||
import Toast from "../../components/notifications/Toast";
|
import Toast from "../../components/notifications/Toast";
|
||||||
import { EmptyListState } from "../../components/emptystates";
|
import { EmptyListState } from "../../components/emptystates";
|
||||||
|
|
||||||
export default function Filters() {
|
export default function Filters() {
|
||||||
const [createFilterIsOpen, toggleCreateFilter] = useToggle(false)
|
const [createFilterIsOpen, toggleCreateFilter] = useToggle(false)
|
||||||
|
|
||||||
const { isLoading, error, data } = useQuery<Filter[], Error>('filter', APIClient.filters.getAll,
|
const { isLoading, error, data } = useQuery(
|
||||||
{
|
'filter',
|
||||||
refetchOnWindowFocus: false
|
APIClient.filters.getAll,
|
||||||
}
|
{ refetchOnWindowFocus: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from "react-query";
|
||||||
|
|
||||||
import APIClient from "../../api/APIClient";
|
import { APIClient } from "../../api/APIClient";
|
||||||
import { Checkbox } from "../../components/Checkbox";
|
import { Checkbox } from "../../components/Checkbox";
|
||||||
import { SettingsContext } from "../../utils/Context";
|
import { SettingsContext } from "../../utils/Context";
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import { SettingsContext } from "../../utils/Context";
|
||||||
function ApplicationSettings() {
|
function ApplicationSettings() {
|
||||||
const [settings, setSettings] = SettingsContext.use();
|
const [settings, setSettings] = SettingsContext.use();
|
||||||
|
|
||||||
const { isLoading, data } = useQuery<Config, Error>(
|
const { isLoading, data } = useQuery(
|
||||||
['config'],
|
['config'],
|
||||||
() => APIClient.config.get(),
|
() => APIClient.config.get(),
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { useQuery } from "react-query";
|
||||||
import { classNames } from "../../utils";
|
import { classNames } from "../../utils";
|
||||||
import { DownloadClientAddForm, DownloadClientUpdateForm } from "../../forms";
|
import { DownloadClientAddForm, DownloadClientUpdateForm } from "../../forms";
|
||||||
import { EmptySimple } from "../../components/emptystates";
|
import { EmptySimple } from "../../components/emptystates";
|
||||||
import APIClient from "../../api/APIClient";
|
import { APIClient } from "../../api/APIClient";
|
||||||
import { DownloadClientTypeNameMap } from "../../domain/constants";
|
import { DownloadClientTypeNameMap } from "../../domain/constants";
|
||||||
|
|
||||||
interface DLSettingsItemProps {
|
interface DLSettingsItemProps {
|
||||||
|
@ -53,10 +53,11 @@ function DownloadClientSettingsListItem({ client, idx }: DLSettingsItemProps) {
|
||||||
function DownloadClientSettings() {
|
function DownloadClientSettings() {
|
||||||
const [addClientIsOpen, toggleAddClient] = useToggle(false)
|
const [addClientIsOpen, toggleAddClient] = useToggle(false)
|
||||||
|
|
||||||
const { error, data } = useQuery<DownloadClient[], Error>('downloadClients', APIClient.download_clients.getAll,
|
const { error, data } = useQuery(
|
||||||
{
|
'downloadClients',
|
||||||
refetchOnWindowFocus: false
|
APIClient.download_clients.getAll,
|
||||||
})
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
|
||||||
if (error)
|
if (error)
|
||||||
return (<p>An error has occurred: </p>);
|
return (<p>An error has occurred: </p>);
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { IndexerAddForm, IndexerUpdateForm } from "../../forms";
|
||||||
import { Switch } from "@headlessui/react";
|
import { Switch } from "@headlessui/react";
|
||||||
import { classNames } from "../../utils";
|
import { classNames } from "../../utils";
|
||||||
import { EmptySimple } from "../../components/emptystates";
|
import { EmptySimple } from "../../components/emptystates";
|
||||||
import APIClient from "../../api/APIClient";
|
import { APIClient } from "../../api/APIClient";
|
||||||
|
|
||||||
const ListItem = ({ indexer }: any) => {
|
const ListItem = ({ indexer }: any) => {
|
||||||
const [updateIsOpen, toggleUpdate] = useToggle(false)
|
const [updateIsOpen, toggleUpdate] = useToggle(false)
|
||||||
|
@ -45,11 +45,11 @@ const ListItem = ({ indexer }: any) => {
|
||||||
function IndexerSettings() {
|
function IndexerSettings() {
|
||||||
const [addIndexerIsOpen, toggleAddIndexer] = useToggle(false)
|
const [addIndexerIsOpen, toggleAddIndexer] = useToggle(false)
|
||||||
|
|
||||||
const { error, data } = useQuery<any[], Error>('indexer', APIClient.indexers.getAll,
|
const { error, data } = useQuery(
|
||||||
{
|
'indexer',
|
||||||
refetchOnWindowFocus: false
|
APIClient.indexers.getAll,
|
||||||
}
|
{ refetchOnWindowFocus: false }
|
||||||
)
|
);
|
||||||
|
|
||||||
if (error)
|
if (error)
|
||||||
return (<p>An error has occurred</p>);
|
return (<p>An error has occurred</p>);
|
||||||
|
@ -104,7 +104,7 @@ function IndexerSettings() {
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="light:bg-white divide-y divide-gray-200 dark:divide-gray-700">
|
<tbody className="light:bg-white divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
{data && data.map((indexer: Indexer, idx: number) => (
|
{data && data.map((indexer: IndexerDefinition, idx: number) => (
|
||||||
<ListItem indexer={indexer} key={idx} />
|
<ListItem indexer={indexer} key={idx} />
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from "react-query";
|
||||||
import { formatDistanceToNowStrict, formatISO9075 } from "date-fns";
|
import { formatDistanceToNowStrict, formatISO9075 } from "date-fns";
|
||||||
|
|
||||||
import APIClient from "../../api/APIClient";
|
import { APIClient } from "../../api/APIClient";
|
||||||
import { useToggle } from "../../hooks/hooks";
|
import { useToggle } from "../../hooks/hooks";
|
||||||
import { EmptySimple } from "../../components/emptystates";
|
import { EmptySimple } from "../../components/emptystates";
|
||||||
import { IrcNetworkAddForm, IrcNetworkUpdateForm } from "../../forms";
|
import { IrcNetworkAddForm, IrcNetworkUpdateForm } from "../../forms";
|
||||||
|
@ -27,11 +27,11 @@ function simplifyDate(date: string) {
|
||||||
function IrcSettings() {
|
function IrcSettings() {
|
||||||
const [addNetworkIsOpen, toggleAddNetwork] = useToggle(false)
|
const [addNetworkIsOpen, toggleAddNetwork] = useToggle(false)
|
||||||
|
|
||||||
const { data } = useQuery<IrcNetwork[], Error>('networks', APIClient.irc.getNetworks,
|
const { data } = useQuery(
|
||||||
{
|
'networks',
|
||||||
refetchOnWindowFocus: false
|
APIClient.irc.getNetworks,
|
||||||
}
|
{ refetchOnWindowFocus: false }
|
||||||
)
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="divide-y divide-gray-200 lg:col-span-9">
|
<div className="divide-y divide-gray-200 lg:col-span-9">
|
||||||
|
@ -90,27 +90,8 @@ const LiItem = ({ idx, network }: LiItemProps) => {
|
||||||
|
|
||||||
<li key={idx} >
|
<li key={idx} >
|
||||||
<div className="grid grid-cols-12 gap-4 items-center hover:bg-gray-50 dark:hover:bg-gray-700 py-4">
|
<div className="grid grid-cols-12 gap-4 items-center hover:bg-gray-50 dark:hover:bg-gray-700 py-4">
|
||||||
|
|
||||||
<IrcNetworkUpdateForm isOpen={updateIsOpen} toggle={toggleUpdate} network={network} />
|
<IrcNetworkUpdateForm isOpen={updateIsOpen} toggle={toggleUpdate} network={network} />
|
||||||
{/* <div className="col-span-1 flex items-center sm:px-6">
|
|
||||||
<Switch
|
|
||||||
checked={network.enabled}
|
|
||||||
onChange={toggleUpdate}
|
|
||||||
className={classNames(
|
|
||||||
network.enabled ? 'bg-teal-500 dark:bg-blue-500' : 'bg-gray-200 dark:bg-gray-600',
|
|
||||||
'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'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<span className="sr-only">Enable</span>
|
|
||||||
<span
|
|
||||||
aria-hidden="true"
|
|
||||||
className={classNames(
|
|
||||||
network.enabled ? 'translate-x-5' : 'translate-x-0',
|
|
||||||
'inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200'
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Switch>
|
|
||||||
</div> */}
|
|
||||||
<div className="col-span-3 items-center sm:px-6 text-sm font-medium text-gray-900 dark:text-white cursor-pointer" onClick={toggleEdit}>
|
<div className="col-span-3 items-center sm:px-6 text-sm font-medium text-gray-900 dark:text-white cursor-pointer" onClick={toggleEdit}>
|
||||||
<span className="relative inline-flex items-center">
|
<span className="relative inline-flex items-center">
|
||||||
{
|
{
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue