Compare commits
8 Commits
dep-hell
...
qbittorren
| Author | SHA1 | Date | |
|---|---|---|---|
| c6f719e78b | |||
| b4d1b678b1 | |||
| aa3192bc1a | |||
| 56ddfbd16e | |||
| 173735da45 | |||
| f4408cd493 | |||
| 41a9428729 | |||
| e1da6ddcef |
51
src/client/components/ComicDetail/AirDCPPBundles.tsx
Normal file
51
src/client/components/ComicDetail/AirDCPPBundles.tsx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import React from "react";
|
||||||
|
import prettyBytes from "pretty-bytes";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import ellipsize from "ellipsize";
|
||||||
|
import { map } from "lodash";
|
||||||
|
|
||||||
|
export const AirDCPPBundles = (props) => {
|
||||||
|
return (
|
||||||
|
<div className="overflow-x-auto w-fit mt-4 rounded-lg border border-gray-200">
|
||||||
|
<table className="min-w-full divide-y-2 divide-gray-200 dark:divide-gray-200 text-md">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th className="whitespace-nowrap px-4 py-2 font-medium text-gray-900 dark:text-slate-200">
|
||||||
|
Filename
|
||||||
|
</th>
|
||||||
|
<th className="whitespace-nowrap px-4 py-2 font-medium text-gray-900 dark:text-slate-200">
|
||||||
|
Size
|
||||||
|
</th>
|
||||||
|
<th className="whitespace-nowrap px-4 py-2 font-medium text-gray-900 dark:text-slate-200">
|
||||||
|
Download Time
|
||||||
|
</th>
|
||||||
|
<th className="whitespace-nowrap px-4 py-2 font-medium text-gray-900 dark:text-slate-200">
|
||||||
|
Bundle ID
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-gray-200">
|
||||||
|
{map(props.data, (bundle) => (
|
||||||
|
<tr key={bundle.id} className="text-sm">
|
||||||
|
<td className="whitespace-nowrap px-2 py-2 text-gray-700 dark:text-slate-300">
|
||||||
|
<h5>{ellipsize(bundle.name, 58)}</h5>
|
||||||
|
<span className="text-xs">{ellipsize(bundle.target, 88)}</span>
|
||||||
|
</td>
|
||||||
|
<td className="whitespace-nowrap px-2 py-2 text-gray-700 dark:text-slate-300">
|
||||||
|
{prettyBytes(bundle.size)}
|
||||||
|
</td>
|
||||||
|
<td className="whitespace-nowrap px-2 py-2 text-gray-700 dark:text-slate-300">
|
||||||
|
{dayjs
|
||||||
|
.unix(bundle.time_finished)
|
||||||
|
.format("h:mm on ddd, D MMM, YYYY")}
|
||||||
|
</td>
|
||||||
|
<td className="whitespace-nowrap px-2 py-2 text-gray-700 dark:text-slate-300">
|
||||||
|
<span className="tag is-warning">{bundle.id}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -351,13 +351,18 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
|
|||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
name: "Torrent Search",
|
name: "Torrent Search",
|
||||||
content: <TorrentSearchPanel />,
|
content: <TorrentSearchPanel comicObjectId={_id} issueName={issueName} />,
|
||||||
shouldShow: true,
|
shouldShow: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 6,
|
id: 6,
|
||||||
name: "Downloads",
|
name: "Downloads",
|
||||||
icon: <>{acquisition?.directconnect?.downloads?.length}</>,
|
icon: (
|
||||||
|
<>
|
||||||
|
{acquisition?.directconnect?.downloads?.length +
|
||||||
|
acquisition?.torrent.length}
|
||||||
|
</>
|
||||||
|
),
|
||||||
content:
|
content:
|
||||||
!isNil(data.data) && !isEmpty(data.data) ? (
|
!isNil(data.data) && !isEmpty(data.data) ? (
|
||||||
<DownloadsPanel key={5} />
|
<DownloadsPanel key={5} />
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { isEmpty, isNil, isUndefined } from "lodash";
|
import React, { ReactElement } from "react";
|
||||||
import React, { ReactElement, useContext, useEffect, useState } from "react";
|
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { getComicBookDetailById } from "../../actions/comicinfo.actions";
|
|
||||||
import { ComicDetail } from "../ComicDetail/ComicDetail";
|
import { ComicDetail } from "../ComicDetail/ComicDetail";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { LIBRARY_SERVICE_BASE_URI } from "../../constants/endpoints";
|
import { LIBRARY_SERVICE_BASE_URI } from "../../constants/endpoints";
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import React, { useEffect, useContext, ReactElement, useState } from "react";
|
import React, { useEffect, useContext, ReactElement, useState } from "react";
|
||||||
import { RootState } from "threetwo-ui-typings";
|
import { RootState } from "threetwo-ui-typings";
|
||||||
import { isEmpty, map } from "lodash";
|
import { isEmpty, map } from "lodash";
|
||||||
import prettyBytes from "pretty-bytes";
|
import { AirDCPPBundles } from "./AirDCPPBundles";
|
||||||
import dayjs from "dayjs";
|
import { TorrentDownloads } from "./TorrentDownloads";
|
||||||
import ellipsize from "ellipsize";
|
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { LIBRARY_SERVICE_BASE_URI } from "../../constants/endpoints";
|
import {
|
||||||
|
LIBRARY_SERVICE_BASE_URI,
|
||||||
|
QBITTORRENT_SERVICE_BASE_URI,
|
||||||
|
TORRENT_JOB_SERVICE_BASE_URI,
|
||||||
|
} from "../../constants/endpoints";
|
||||||
import { useStore } from "../../store";
|
import { useStore } from "../../store";
|
||||||
import { useShallow } from "zustand/react/shallow";
|
import { useShallow } from "zustand/react/shallow";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
@@ -20,12 +23,27 @@ export const DownloadsPanel = (
|
|||||||
): ReactElement | null => {
|
): ReactElement | null => {
|
||||||
const { comicObjectId } = useParams<{ comicObjectId: string }>();
|
const { comicObjectId } = useParams<{ comicObjectId: string }>();
|
||||||
const [bundles, setBundles] = useState([]);
|
const [bundles, setBundles] = useState([]);
|
||||||
const { airDCPPSocketInstance } = useStore(
|
const [infoHashes, setInfoHashes] = useState<string[]>([]);
|
||||||
useShallow((state) => ({
|
const [torrentDetails, setTorrentDetails] = useState([]);
|
||||||
|
const [activeTab, setActiveTab] = useState("torrents");
|
||||||
|
const { airDCPPSocketInstance, socketIOInstance } = useStore(
|
||||||
|
useShallow((state: any) => ({
|
||||||
airDCPPSocketInstance: state.airDCPPSocketInstance,
|
airDCPPSocketInstance: state.airDCPPSocketInstance,
|
||||||
|
socketIOInstance: state.socketIOInstance,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// React to torrent progress data sent over websockets
|
||||||
|
socketIOInstance.on("AS_TORRENT_DATA", (data) => {
|
||||||
|
const torrents = data.torrents
|
||||||
|
.flatMap(({ _id, details }) => {
|
||||||
|
if (_id === comicObjectId) {
|
||||||
|
return details;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter((item) => item !== undefined);
|
||||||
|
setTorrentDetails(torrents);
|
||||||
|
});
|
||||||
// Fetch the downloaded files and currently-downloading file(s) from AirDC++
|
// Fetch the downloaded files and currently-downloading file(s) from AirDC++
|
||||||
const { data: comicObject, isSuccess } = useQuery({
|
const { data: comicObject, isSuccess } = useQuery({
|
||||||
queryKey: ["bundles"],
|
queryKey: ["bundles"],
|
||||||
@@ -54,65 +72,75 @@ export const DownloadsPanel = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Call the scheduled job for fetching torrent data
|
||||||
|
// triggered by the active tab been set to "torrents"
|
||||||
|
const { data: torrentData } = useQuery({
|
||||||
|
queryFn: () =>
|
||||||
|
axios({
|
||||||
|
url: `${TORRENT_JOB_SERVICE_BASE_URI}/getTorrentData`,
|
||||||
|
method: "GET",
|
||||||
|
params: {
|
||||||
|
trigger: activeTab,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
queryKey: [activeTab],
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getBundles(comicObject).then((result) => {
|
getBundles(comicObject).then((result) => {
|
||||||
setBundles(result);
|
setBundles(result);
|
||||||
});
|
});
|
||||||
}, [comicObject]);
|
}, [comicObject]);
|
||||||
|
|
||||||
const Bundles = (props) => {
|
|
||||||
return (
|
|
||||||
<div className="overflow-x-auto w-fit mt-4 rounded-lg border border-gray-200">
|
|
||||||
<table className="min-w-full divide-y-2 divide-gray-200 dark:divide-gray-200 text-md">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th className="whitespace-nowrap px-4 py-2 font-medium text-gray-900 dark:text-slate-200">
|
|
||||||
Filename
|
|
||||||
</th>
|
|
||||||
<th className="whitespace-nowrap px-4 py-2 font-medium text-gray-900 dark:text-slate-200">
|
|
||||||
Size
|
|
||||||
</th>
|
|
||||||
<th className="whitespace-nowrap px-4 py-2 font-medium text-gray-900 dark:text-slate-200">
|
|
||||||
Download Time
|
|
||||||
</th>
|
|
||||||
<th className="whitespace-nowrap px-4 py-2 font-medium text-gray-900 dark:text-slate-200">
|
|
||||||
Bundle ID
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="divide-y divide-gray-200">
|
|
||||||
{map(props.data, (bundle) => (
|
|
||||||
<tr key={bundle.id} className="text-sm">
|
|
||||||
<td className="whitespace-nowrap px-2 py-2 text-gray-700 dark:text-slate-300">
|
|
||||||
<h5>{ellipsize(bundle.name, 58)}</h5>
|
|
||||||
<span className="text-xs">
|
|
||||||
{ellipsize(bundle.target, 88)}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td className="whitespace-nowrap px-2 py-2 text-gray-700 dark:text-slate-300">
|
|
||||||
{prettyBytes(bundle.size)}
|
|
||||||
</td>
|
|
||||||
<td className="whitespace-nowrap px-2 py-2 text-gray-700 dark:text-slate-300">
|
|
||||||
{dayjs
|
|
||||||
.unix(bundle.time_finished)
|
|
||||||
.format("h:mm on ddd, D MMM, YYYY")}
|
|
||||||
</td>
|
|
||||||
<td className="whitespace-nowrap px-2 py-2 text-gray-700 dark:text-slate-300">
|
|
||||||
<span className="tag is-warning">{bundle.id}</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="columns is-multiline">
|
<div className="columns is-multiline">
|
||||||
{!isEmpty(airDCPPSocketInstance) && !isEmpty(bundles) && (
|
{!isEmpty(airDCPPSocketInstance) &&
|
||||||
<Bundles data={bundles} />
|
!isEmpty(bundles) &&
|
||||||
)}
|
activeTab === "directconnect" && <AirDCPPBundles data={bundles} />}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="sm:hidden">
|
||||||
|
<label htmlFor="Download Type" className="sr-only">
|
||||||
|
Download Type
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<select id="Tab" className="w-full rounded-md border-gray-200">
|
||||||
|
<option>DC++ Downloads</option>
|
||||||
|
<option>Torrents</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="hidden sm:block">
|
||||||
|
<nav className="flex gap-6" aria-label="Tabs">
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className={`shrink-0 rounded-lg p-2 text-sm font-medium hover:bg-gray-50 hover:text-gray-700 ${
|
||||||
|
activeTab === "directconnect"
|
||||||
|
? "bg-slate-200 dark:text-slate-200 dark:bg-slate-400 text-slate-800"
|
||||||
|
: "dark:text-slate-400 text-slate-800"
|
||||||
|
}`}
|
||||||
|
aria-current="page"
|
||||||
|
onClick={() => setActiveTab("directconnect")}
|
||||||
|
>
|
||||||
|
DC++ Downloads
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className={`shrink-0 rounded-lg p-2 text-sm font-medium hover:bg-gray-50 hover:text-gray-700 ${
|
||||||
|
activeTab === "torrents"
|
||||||
|
? "bg-slate-200 text-slate-800"
|
||||||
|
: "dark:text-slate-400 text-slate-800"
|
||||||
|
}`}
|
||||||
|
onClick={() => setActiveTab("torrents")}
|
||||||
|
>
|
||||||
|
Torrents
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{activeTab === "torrents" && <TorrentDownloads data={torrentDetails} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export const RawFileDetails = (props): ReactElement => {
|
|||||||
<dd className="mt-1 text-sm text-gray-500 dark:text-slate-900">
|
<dd className="mt-1 text-sm text-gray-500 dark:text-slate-900">
|
||||||
{/* File extension */}
|
{/* File extension */}
|
||||||
<span className="inline-flex items-center bg-slate-50 text-slate-800 text-xs font-medium px-2 rounded-md dark:text-slate-900 dark:bg-slate-400">
|
<span className="inline-flex items-center bg-slate-50 text-slate-800 text-xs font-medium px-2 rounded-md dark:text-slate-900 dark:bg-slate-400">
|
||||||
<span className="pr-1 pt-1">
|
<span className="pt-1">
|
||||||
<i className="icon-[solar--zip-file-bold-duotone] w-5 h-5"></i>
|
<i className="icon-[solar--zip-file-bold-duotone] w-5 h-5"></i>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,9 @@
|
|||||||
import React, { ReactElement, useEffect, useState } from "react";
|
import React, { ReactElement, useState } from "react";
|
||||||
import { isNil } from "lodash";
|
import { isNil } from "lodash";
|
||||||
|
|
||||||
export const TabControls = (props): ReactElement => {
|
export const TabControls = (props): ReactElement => {
|
||||||
// const comicBookDetailData = useSelector(
|
|
||||||
// (state: RootState) => state.comicInfo.comicBookDetail,
|
|
||||||
// );
|
|
||||||
const { filteredTabs, downloadCount } = props;
|
const { filteredTabs, downloadCount } = props;
|
||||||
const [active, setActive] = useState(filteredTabs[0].id);
|
const [active, setActive] = useState(filteredTabs[0].id);
|
||||||
// useEffect(() => {
|
|
||||||
// setActive(filteredTabs[0].id);
|
|
||||||
// }, [filteredTabs]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -19,7 +13,11 @@ export const TabControls = (props): ReactElement => {
|
|||||||
{filteredTabs.map(({ id, name, icon }) => (
|
{filteredTabs.map(({ id, name, icon }) => (
|
||||||
<a
|
<a
|
||||||
key={id}
|
key={id}
|
||||||
className="inline-flex shrink-0 items-center gap-2 border-b border-transparent px-1 py-1 text-md font-medium text-gray-500 dark:text-gray-400 hover:border-gray-300 hover:text-gray-700"
|
className={`inline-flex shrink-0 items-center gap-2 px-1 py-1 text-md font-medium text-gray-500 dark:text-gray-400 hover:border-gray-300 hover:border-b hover:dark:text-slate-200 ${
|
||||||
|
active === id
|
||||||
|
? "border-b border-cyan-50 dark:text-slate-200"
|
||||||
|
: "border-b border-transparent"
|
||||||
|
}`}
|
||||||
aria-current="page"
|
aria-current="page"
|
||||||
onClick={() => setActive(id)}
|
onClick={() => setActive(id)}
|
||||||
>
|
>
|
||||||
@@ -28,7 +26,7 @@ export const TabControls = (props): ReactElement => {
|
|||||||
{id === 6 && !isNil(downloadCount) ? (
|
{id === 6 && !isNil(downloadCount) ? (
|
||||||
<span className="inline-flex flex-row">
|
<span className="inline-flex flex-row">
|
||||||
{/* download count */}
|
{/* download count */}
|
||||||
<span className="inline-flex mx-2 items-center bg-slate-200 text-slate-800 text-xs font-medium px-2 rounded-md dark:text-slate-900 dark:bg-slate-400">
|
<span className="inline-flex mx-2 items-center bg-slate-200 text-slate-800 text-xs font-medium px-2 rounded-md dark:text-slate-900 dark:bg-orange-400">
|
||||||
<span className="text-md text-slate-500 dark:text-slate-900">
|
<span className="text-md text-slate-500 dark:text-slate-900">
|
||||||
{icon}
|
{icon}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ export const ArchiveOperations = (props): ReactElement => {
|
|||||||
const [currentImage, setCurrentImage] = useState([]);
|
const [currentImage, setCurrentImage] = useState([]);
|
||||||
const [uncompressedArchive, setUncompressedArchive] = useState([]);
|
const [uncompressedArchive, setUncompressedArchive] = useState([]);
|
||||||
const [imageAnalysisResult, setImageAnalysisResult] = useState({});
|
const [imageAnalysisResult, setImageAnalysisResult] = useState({});
|
||||||
|
const [shouldRefetchComicBookData, setShouldRefetchComicBookData] =
|
||||||
|
useState(false);
|
||||||
const constructImagePaths = (data): Array<string> => {
|
const constructImagePaths = (data): Array<string> => {
|
||||||
return data?.map((path: string) =>
|
return data?.map((path: string) =>
|
||||||
escapePoundSymbol(encodeURI(`${LIBRARY_SERVICE_HOST}/${path}`)),
|
escapePoundSymbol(encodeURI(`${LIBRARY_SERVICE_HOST}/${path}`)),
|
||||||
@@ -63,6 +65,7 @@ export const ArchiveOperations = (props): ReactElement => {
|
|||||||
|
|
||||||
if (isMounted) {
|
if (isMounted) {
|
||||||
setUncompressedArchive(uncompressedArchive);
|
setUncompressedArchive(uncompressedArchive);
|
||||||
|
setShouldRefetchComicBookData(true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -122,8 +125,9 @@ export const ArchiveOperations = (props): ReactElement => {
|
|||||||
enabled: false,
|
enabled: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isSuccess) {
|
if (isSuccess && shouldRefetchComicBookData) {
|
||||||
queryClient.invalidateQueries({ queryKey: ["comicBookMetadata"] });
|
queryClient.invalidateQueries({ queryKey: ["comicBookMetadata"] });
|
||||||
|
setShouldRefetchComicBookData(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// sliding panel init
|
// sliding panel init
|
||||||
@@ -171,7 +175,8 @@ export const ArchiveOperations = (props): ReactElement => {
|
|||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
<div className="mt-5">
|
<div className="mt-5">
|
||||||
{data.rawFileDetails.archive?.uncompressed ? (
|
{data.rawFileDetails.archive?.uncompressed &&
|
||||||
|
!isEmpty(uncompressedArchive) ? (
|
||||||
<article
|
<article
|
||||||
role="alert"
|
role="alert"
|
||||||
className="mt-4 text-md rounded-lg max-w-screen-md border-s-4 border-yellow-500 bg-yellow-50 p-4 dark:border-s-4 dark:border-yellow-600 dark:bg-yellow-300 dark:text-slate-600"
|
className="mt-4 text-md rounded-lg max-w-screen-md border-s-4 border-yellow-500 bg-yellow-50 p-4 dark:border-s-4 dark:border-yellow-600 dark:bg-yellow-300 dark:text-slate-600"
|
||||||
@@ -187,7 +192,7 @@ export const ArchiveOperations = (props): ReactElement => {
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<div className="flex flex-row gap-2 mt-4">
|
<div className="flex flex-row gap-2 mt-4">
|
||||||
{!data.rawFileDetails?.archive?.uncompressed ? (
|
{isEmpty(uncompressedArchive) ? (
|
||||||
<button
|
<button
|
||||||
className="flex space-x-1 sm:mt-0 sm:flex-row sm:items-center rounded-lg border border-green-400 dark:border-green-200 bg-green-200 px-3 py-2 text-gray-500 hover:bg-transparent hover:text-green-600 focus:outline-none focus:ring active:text-indigo-500"
|
className="flex space-x-1 sm:mt-0 sm:flex-row sm:items-center rounded-lg border border-green-400 dark:border-green-200 bg-green-200 px-3 py-2 text-gray-500 hover:bg-transparent hover:text-green-600 focus:outline-none focus:ring active:text-indigo-500"
|
||||||
onClick={() => refetch()}
|
onClick={() => refetch()}
|
||||||
|
|||||||
77
src/client/components/ComicDetail/TorrentDownloads.tsx
Normal file
77
src/client/components/ComicDetail/TorrentDownloads.tsx
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import React from "react";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import prettyBytes from "pretty-bytes";
|
||||||
|
|
||||||
|
export const TorrentDownloads = (props) => {
|
||||||
|
const { data } = props;
|
||||||
|
console.log(Object.values(data));
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{data.map(({ torrent }) => {
|
||||||
|
return (
|
||||||
|
<dl className="mt-5 dark:text-slate-200 text-slate-600">
|
||||||
|
<dt className="text-lg">{torrent.name}</dt>
|
||||||
|
<p className="text-sm">{torrent.hash}</p>
|
||||||
|
<p className="text-sm">
|
||||||
|
Added on {dayjs.unix(torrent.added_on).format("ddd, D MMM, YYYY")}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p className="flex gap-2 mt-1">
|
||||||
|
{torrent.progress > 0 ? (
|
||||||
|
<>
|
||||||
|
<progress
|
||||||
|
className="w-80 mt-2 [&::-webkit-progress-bar]:rounded-lg [&::-webkit-progress-value]:rounded-lg [&::-webkit-progress-bar]:bg-slate-300 [&::-webkit-progress-value]:bg-green-400 [&::-moz-progress-bar]:bg-green-400 h-2"
|
||||||
|
value={Math.floor(torrent.progress * 100).toString()}
|
||||||
|
max="100"
|
||||||
|
></progress>
|
||||||
|
|
||||||
|
<span>{Math.floor(torrent.progress * 100)}%</span>
|
||||||
|
|
||||||
|
{/* downloaded/left */}
|
||||||
|
<p className="inline-flex items-center bg-slate-200 text-green-800 dark:text-green-900 text-xs font-medium px-2.5 py-1 rounded-md dark:bg-slate-400">
|
||||||
|
<span className="pr-1">
|
||||||
|
<i className="icon-[solar--arrow-to-down-left-outline] h-4 w-4"></i>
|
||||||
|
</span>
|
||||||
|
<span className="text-md">
|
||||||
|
{prettyBytes(torrent.downloaded)}
|
||||||
|
</span>
|
||||||
|
{/* uploaded */}
|
||||||
|
<span className="pr-1 text-orange-800 dark:text-orange-900 ml-2">
|
||||||
|
<i className="icon-[solar--arrow-to-top-left-outline] h-4 w-4"></i>
|
||||||
|
</span>
|
||||||
|
<span className="text-md text-orange-800 dark:text-orange-900">
|
||||||
|
{prettyBytes(torrent.uploaded)}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</p>
|
||||||
|
<div className="flex gap-4 mt-2">
|
||||||
|
{/* Peers */}
|
||||||
|
<span className="inline-flex items-center bg-slate-200 text-slate-800 text-xs font-medium px-2.5 py-0.5 rounded-md dark:text-slate-900 dark:bg-slate-400">
|
||||||
|
<span className="pr-1">
|
||||||
|
<i className="icon-[solar--station-minimalistic-line-duotone] h-5 w-5"></i>
|
||||||
|
</span>
|
||||||
|
<span className="text-md text-slate-900">
|
||||||
|
{torrent.trackers_count}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Size */}
|
||||||
|
<span className="inline-flex items-center bg-slate-200 text-slate-800 text-xs font-medium px-2.5 py-0.5 rounded-md dark:text-slate-900 dark:bg-slate-400">
|
||||||
|
<span className="pr-1 pt-1">
|
||||||
|
<i className="icon-[solar--mirror-right-bold-duotone] h-4 w-4"></i>
|
||||||
|
</span>
|
||||||
|
<span className="text-md text-slate-900">
|
||||||
|
{prettyBytes(torrent.total_size)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TorrentDownloads;
|
||||||
@@ -1,16 +1,25 @@
|
|||||||
import React, { useCallback, ReactElement, useEffect, useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { Form, Field } from "react-final-form";
|
import { Form, Field } from "react-final-form";
|
||||||
import { PROWLARR_SERVICE_BASE_URI } from "../../constants/endpoints";
|
import {
|
||||||
|
PROWLARR_SERVICE_BASE_URI,
|
||||||
|
QBITTORRENT_SERVICE_BASE_URI,
|
||||||
|
} from "../../constants/endpoints";
|
||||||
|
import { isEmpty, isNil } from "lodash";
|
||||||
|
import ellipsize from "ellipsize";
|
||||||
|
import prettyBytes from "pretty-bytes";
|
||||||
|
|
||||||
export const TorrentSearchPanel = (props): ReactElement => {
|
export const TorrentSearchPanel = (props) => {
|
||||||
const [prowlarrSettingsData, setProwlarrSettingsData] = useState({});
|
const { issueName, comicObjectId } = props;
|
||||||
|
// Initialize searchTerm with issueName from props
|
||||||
|
const [searchTerm, setSearchTerm] = useState({ issueName });
|
||||||
|
const [torrentToDownload, setTorrentToDownload] = useState("");
|
||||||
|
|
||||||
const { data } = useQuery({
|
const { data, isSuccess, isLoading } = useQuery({
|
||||||
queryFn: async () =>
|
queryKey: ["searchResults", searchTerm.issueName],
|
||||||
axios({
|
queryFn: async () => {
|
||||||
|
return await axios({
|
||||||
url: `${PROWLARR_SERVICE_BASE_URI}/search`,
|
url: `${PROWLARR_SERVICE_BASE_URI}/search`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
data: {
|
data: {
|
||||||
@@ -18,58 +27,172 @@ export const TorrentSearchPanel = (props): ReactElement => {
|
|||||||
apiKey: "c4f42e265fb044dc81f7e88bd41c3367",
|
apiKey: "c4f42e265fb044dc81f7e88bd41c3367",
|
||||||
offset: 0,
|
offset: 0,
|
||||||
categories: [7030],
|
categories: [7030],
|
||||||
query: "the darkness",
|
query: searchTerm.issueName,
|
||||||
host: "localhost",
|
host: "localhost",
|
||||||
limit: 100,
|
limit: 100,
|
||||||
type: "search",
|
type: "search",
|
||||||
indexerIds: [2],
|
indexerIds: [2],
|
||||||
},
|
},
|
||||||
}),
|
});
|
||||||
queryKey: ["prowlarrSettingsData"],
|
},
|
||||||
|
enabled: !isNil(searchTerm.issueName) && searchTerm.issueName.trim() !== "", // Make sure searchTerm is not empty
|
||||||
});
|
});
|
||||||
console.log(data?.data);
|
const mutation = useMutation({
|
||||||
|
mutationFn: async (newTorrent) =>
|
||||||
|
axios.post(`${QBITTORRENT_SERVICE_BASE_URI}/addTorrent`, newTorrent),
|
||||||
|
onSuccess: async (data) => {
|
||||||
|
console.log(data);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const searchIndexer = (values) => {
|
||||||
|
setSearchTerm({ issueName: values.issueName }); // Update searchTerm based on the form submission
|
||||||
|
};
|
||||||
|
const downloadTorrent = (evt) => {
|
||||||
|
const newTorrent = {
|
||||||
|
comicObjectId,
|
||||||
|
torrentToDownload: evt,
|
||||||
|
};
|
||||||
|
mutation.mutate(newTorrent);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="mt-5">
|
<div className="mt-5">
|
||||||
<Form
|
<Form
|
||||||
onSubmit={() => {}}
|
onSubmit={searchIndexer}
|
||||||
initialValues={{}}
|
initialValues={searchTerm}
|
||||||
render={({ handleSubmit, form, submitting, pristine, values }) => (
|
render={({ handleSubmit }) => (
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<Field name="issueName">
|
<Field name="issueName">
|
||||||
{({ input, meta }) => {
|
{({ input, meta }) => (
|
||||||
return (
|
<div className="max-w-fit">
|
||||||
<div className="max-w-fit">
|
<div className="flex flex-row bg-slate-300 dark:bg-slate-400 rounded-l-lg">
|
||||||
<div className="flex flex-row bg-slate-300 dark:bg-slate-400 rounded-l-lg">
|
<div className="w-10 pl-2 pt-1 text-gray-400 dark:text-gray-200">
|
||||||
<div className="w-10 pl-2 pt-1 text-gray-400 dark:text-gray-200">
|
{/* Icon placeholder */}
|
||||||
<i className="icon-[solar--magnifer-bold-duotone] h-7 w-7" />
|
<i className="icon-[solar--magnifer-bold-duotone] h-7 w-7" />
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
{...input}
|
|
||||||
className="dark:bg-slate-400 bg-slate-300 py-2 px-2 rounded-l-md border-gray-300 h-10 min-w-full dark:text-slate-800 sm:text-md sm:leading-5 focus:outline-none focus:shadow-outline-blue focus:border-blue-300"
|
|
||||||
placeholder="Enter a search term"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<button
|
|
||||||
className="sm:mt-0 min-w-fit rounded-r-lg border border-green-400 dark:border-green-200 bg-green-200 px-3 py-1 text-gray-500 hover:bg-transparent hover:text-green-600 focus:outline-none focus:ring active:text-indigo-500"
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
<div className="flex flex-row">
|
|
||||||
Search Indexer
|
|
||||||
<div className="h-5 w-5 ml-1">
|
|
||||||
<i className="h-6 w-6 icon-[solar--magnet-bold-duotone]" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<input
|
||||||
|
{...input}
|
||||||
|
type="text"
|
||||||
|
className="dark:bg-slate-400 bg-slate-300 py-2 px-2 rounded-l-md border-gray-300 h-10 min-w-full dark:text-slate-800 sm:text-md sm:leading-5 focus:outline-none focus:shadow-outline-blue focus:border-blue-300"
|
||||||
|
placeholder="Enter a search term"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className="sm:mt-0 min-w-fit rounded-r-lg border border-green-400 dark:border-green-200 bg-green-200 px-3 py-1 text-gray-500 hover:bg-transparent hover:text-green-600 focus:outline-none focus:ring active:text-indigo-500"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
<div className="flex flex-row">
|
||||||
|
Search Indexer
|
||||||
|
<div className="h-5 w-5 ml-1">
|
||||||
|
<i className="h-6 w-6 icon-[solar--magnet-bold-duotone]" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
}}
|
)}
|
||||||
</Field>
|
</Field>
|
||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<article
|
||||||
|
role="alert"
|
||||||
|
className="mt-4 rounded-lg text-sm max-w-screen-md border-s-4 border-blue-500 bg-blue-50 p-4 dark:border-s-4 dark:border-blue-600 dark:bg-blue-300 dark:text-slate-600"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
The default search term is an auto-detected title; you may need to
|
||||||
|
change it to get better matches if the auto-detected one doesn't work.
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{!isEmpty(data?.data) ? (
|
||||||
|
<div className="overflow-x-auto w-fit mt-4 rounded-lg border border-gray-200 dark:border-gray-500">
|
||||||
|
<table className="min-w-full divide-y-2 divide-gray-200 dark:divide-gray-500 text-md">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th className="whitespace-nowrap px-2 py-2 font-medium text-gray-900 dark:text-slate-200">
|
||||||
|
Name
|
||||||
|
</th>
|
||||||
|
<th className="whitespace-nowrap py-2 font-medium text-gray-900 dark:text-slate-200">
|
||||||
|
Indexer
|
||||||
|
</th>
|
||||||
|
|
||||||
|
<th className="whitespace-nowrap py-2 font-medium text-gray-900 dark:text-slate-200">
|
||||||
|
Action
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-slate-100 dark:divide-gray-500">
|
||||||
|
{data?.data.map((result, idx) => (
|
||||||
|
<tr key={idx}>
|
||||||
|
<td className="px-3 py-3 text-gray-700 dark:text-slate-300 text-md">
|
||||||
|
<p>{ellipsize(result.fileName, 90)}</p>
|
||||||
|
{/* Seeders/Leechers */}
|
||||||
|
<div className="flex gap-3 mt-2">
|
||||||
|
<span className="inline-flex items-center bg-slate-50 text-slate-800 text-xs font-medium px-2 rounded-md dark:text-slate-900 dark:bg-slate-400">
|
||||||
|
<span className="pr-1 pt-1">
|
||||||
|
<i className="icon-[solar--archive-up-minimlistic-bold-duotone] w-5 h-5"></i>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="text-md text-slate-500 dark:text-slate-900">
|
||||||
|
{result.seeders} seeders
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="inline-flex items-center bg-slate-50 text-slate-800 text-xs font-medium px-2 rounded-md dark:text-slate-900 dark:bg-slate-400">
|
||||||
|
<span className="pr-1 pt-1">
|
||||||
|
<i className="icon-[solar--archive-down-minimlistic-bold-duotone] w-5 h-5"></i>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="text-md text-slate-500 dark:text-slate-900">
|
||||||
|
{result.leechers} leechers
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
{/* Size */}
|
||||||
|
<span className="inline-flex items-center bg-slate-50 text-slate-800 text-xs font-medium px-2 rounded-md dark:text-slate-900 dark:bg-slate-400">
|
||||||
|
<span className="pr-1 pt-1">
|
||||||
|
<i className="icon-[solar--mirror-right-bold-duotone] w-5 h-5"></i>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="text-md text-slate-500 dark:text-slate-900">
|
||||||
|
{prettyBytes(result.size)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Files */}
|
||||||
|
<span className="inline-flex items-center bg-slate-50 text-slate-800 text-xs font-medium px-2 rounded-md dark:text-slate-900 dark:bg-slate-400">
|
||||||
|
<span className="pr-1 pt-1">
|
||||||
|
<i className="icon-[solar--documents-bold-duotone] w-5 h-5"></i>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="text-md text-slate-500 dark:text-slate-900">
|
||||||
|
{result.files} files
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td className="px-3 py-3 text-gray-700 dark:text-slate-300 text-sm">
|
||||||
|
{result.indexer}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td className="px-3 py-3 text-gray-700 dark:text-slate-300 text-sm">
|
||||||
|
<button
|
||||||
|
className="flex space-x-1 sm:mt-0 sm:flex-row sm:items-center rounded-lg border border-green-400 dark:border-green-200 bg-green-200 px-3 py-1 text-gray-500 hover:bg-transparent hover:text-green-600 focus:outline-none focus:ring active:text-indigo-500"
|
||||||
|
onClick={() => downloadTorrent(result.downloadUrl)}
|
||||||
|
>
|
||||||
|
<span className="text-xs">Download</span>
|
||||||
|
<span className="w-5 h-5">
|
||||||
|
<i className="h-5 w-5 icon-[solar--download-bold-duotone]"></i>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ export const Library = (): ReactElement => {
|
|||||||
<i className="icon-[solar--magnet-bold-duotone] w-5 h-5"></i>
|
<i className="icon-[solar--magnet-bold-duotone] w-5 h-5"></i>
|
||||||
</span>
|
</span>
|
||||||
<span className="text-md text-slate-900 dark:text-slate-900">
|
<span className="text-md text-slate-900 dark:text-slate-900">
|
||||||
Torrent: {info.getValue().torrent.downloads.length}
|
Torrent: {info.getValue().torrent.length}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import React, { useCallback, ReactElement, useState } from "react";
|
import React, { useCallback, ReactElement, useState } from "react";
|
||||||
import { isNil, isEmpty } from "lodash";
|
import { isNil, isEmpty } from "lodash";
|
||||||
import { IExtractedComicBookCoverFile, RootState } from "threetwo-ui-typings";
|
import { IExtractedComicBookCoverFile, RootState } from "threetwo-ui-typings";
|
||||||
import { importToDB } from "../../actions/fileops.actions";
|
|
||||||
import { comicinfoAPICall } from "../../actions/comicinfo.actions";
|
|
||||||
import { search } from "../../services/api/SearchApi";
|
|
||||||
import { Form, Field } from "react-final-form";
|
import { Form, Field } from "react-final-form";
|
||||||
import Card from "../shared/Carda";
|
import Card from "../shared/Carda";
|
||||||
import ellipsize from "ellipsize";
|
import ellipsize from "ellipsize";
|
||||||
@@ -27,7 +25,6 @@ export const Search = ({}: ISearchProps): ReactElement => {
|
|||||||
const [comicVineMetadata, setComicVineMetadata] = useState({});
|
const [comicVineMetadata, setComicVineMetadata] = useState({});
|
||||||
const getCVSearchResults = (searchQuery) => {
|
const getCVSearchResults = (searchQuery) => {
|
||||||
setSearchQuery(searchQuery.search);
|
setSearchQuery(searchQuery.search);
|
||||||
// queryClient.invalidateQueries({ queryKey: ["comicvineSearchResults"] });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -146,6 +143,7 @@ export const Search = ({}: ISearchProps): ReactElement => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{isLoading && <>Loading kaka...</>}
|
||||||
{!isNil(comicVineSearchResults?.data.results) &&
|
{!isNil(comicVineSearchResults?.data.results) &&
|
||||||
!isEmpty(comicVineSearchResults?.data.results) ? (
|
!isEmpty(comicVineSearchResults?.data.results) ? (
|
||||||
<div className="mx-auto max-w-screen-xl px-4 py-4 sm:px-6 sm:py-8 lg:px-8">
|
<div className="mx-auto max-w-screen-xl px-4 py-4 sm:px-6 sm:py-8 lg:px-8">
|
||||||
|
|||||||
@@ -16,16 +16,7 @@ export const QbittorrentConnectionForm = (): ReactElement => {
|
|||||||
});
|
});
|
||||||
const hostDetails = data?.data?.bittorrent?.client?.host;
|
const hostDetails = data?.data?.bittorrent?.client?.host;
|
||||||
// connect to qbittorrent client
|
// connect to qbittorrent client
|
||||||
const { data: connectionDetails } = useQuery({
|
|
||||||
queryKey: [],
|
|
||||||
queryFn: async () =>
|
|
||||||
await axios({
|
|
||||||
url: "http://localhost:3060/api/qbittorrent/connect",
|
|
||||||
method: "POST",
|
|
||||||
data: hostDetails,
|
|
||||||
}),
|
|
||||||
enabled: !!hostDetails,
|
|
||||||
});
|
|
||||||
// get qbittorrent client info
|
// get qbittorrent client info
|
||||||
const { data: qbittorrentClientInfo } = useQuery({
|
const { data: qbittorrentClientInfo } = useQuery({
|
||||||
queryKey: ["qbittorrentClientInfo"],
|
queryKey: ["qbittorrentClientInfo"],
|
||||||
@@ -34,7 +25,6 @@ export const QbittorrentConnectionForm = (): ReactElement => {
|
|||||||
url: "http://localhost:3060/api/qbittorrent/getClientInfo",
|
url: "http://localhost:3060/api/qbittorrent/getClientInfo",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
}),
|
}),
|
||||||
enabled: !!connectionDetails,
|
|
||||||
});
|
});
|
||||||
// Update action using a mutation
|
// Update action using a mutation
|
||||||
const { mutate } = useMutation({
|
const { mutate } = useMutation({
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export const WantedComics = (props): ReactElement => {
|
|||||||
const {
|
const {
|
||||||
data: wantedComics,
|
data: wantedComics,
|
||||||
isSuccess,
|
isSuccess,
|
||||||
|
isFetched,
|
||||||
isError,
|
isError,
|
||||||
isLoading,
|
isLoading,
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
@@ -41,6 +42,7 @@ export const WantedComics = (props): ReactElement => {
|
|||||||
minWidth: 350,
|
minWidth: 350,
|
||||||
accessorFn: (data) => data,
|
accessorFn: (data) => data,
|
||||||
cell: (value) => {
|
cell: (value) => {
|
||||||
|
console.log("ASDASd", value);
|
||||||
const row = value.getValue()._source;
|
const row = value.getValue()._source;
|
||||||
return row && <MetadataPanel data={row} />;
|
return row && <MetadataPanel data={row} />;
|
||||||
},
|
},
|
||||||
@@ -172,7 +174,7 @@ export const WantedComics = (props): ReactElement => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
{isSuccess ? (
|
{isSuccess && wantedComics?.data.hits?.hits ? (
|
||||||
<div>
|
<div>
|
||||||
<div className="library">
|
<div className="library">
|
||||||
<T2Table
|
<T2Table
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { DayPicker, SelectSingleEventHandler } from "react-day-picker";
|
|||||||
import { usePopper } from "react-popper";
|
import { usePopper } from "react-popper";
|
||||||
|
|
||||||
export const DatePickerDialog = (props) => {
|
export const DatePickerDialog = (props) => {
|
||||||
console.log(props);
|
|
||||||
const { setter, apiAction } = props;
|
const { setter, apiAction } = props;
|
||||||
const [selected, setSelected] = useState<Date>();
|
const [selected, setSelected] = useState<Date>();
|
||||||
const [isPopperOpen, setIsPopperOpen] = useState(false);
|
const [isPopperOpen, setIsPopperOpen] = useState(false);
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ interface IMetadatPanelProps {
|
|||||||
containerStyle: any;
|
containerStyle: any;
|
||||||
}
|
}
|
||||||
export const MetadataPanel = (props: IMetadatPanelProps): ReactElement => {
|
export const MetadataPanel = (props: IMetadatPanelProps): ReactElement => {
|
||||||
console.log(props);
|
|
||||||
const {
|
const {
|
||||||
rawFileDetails,
|
rawFileDetails,
|
||||||
inferredMetadata,
|
inferredMetadata,
|
||||||
|
|||||||
@@ -97,3 +97,10 @@ export const PROWLARR_SERVICE_BASE_URI = hostURIBuilder({
|
|||||||
port: "3060",
|
port: "3060",
|
||||||
apiPath: `/api/prowlarr`,
|
apiPath: `/api/prowlarr`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const TORRENT_JOB_SERVICE_BASE_URI = hostURIBuilder({
|
||||||
|
protocol: "http",
|
||||||
|
host: import.meta.env.UNDERLYING_HOSTNAME || "localhost",
|
||||||
|
port: "3000",
|
||||||
|
apiPath: `/api/torrentjobs`,
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user