├── .eslintrc.json
├── deluge
├── index.ts
├── got.ts
├── types.ts
└── deluge.ts
├── styles
└── globals.css
├── _docs
├── add.jpg
├── home.jpg
├── menu.jpg
├── sort.jpg
├── detail.jpg
├── files.jpg
├── filter.jpg
├── options.jpg
├── globaldl.jpg
├── globalup.jpg
├── add_torrent.jpg
└── pagination.jpg
├── public
├── logo.png
├── favicon.ico
├── icon-192x192.png
├── icon-256x256.png
├── icon-384x384.png
├── icon-512x512.png
├── manifest.json
└── vercel.svg
├── components
├── Loading.tsx
├── DetailGridCol.tsx
├── ListSkeleton.tsx
├── RouterTransition.tsx
├── AddTorrentModal.tsx
├── BadgeToolTip.tsx
├── MoveStorage.tsx
├── TorrentPageNav.tsx
├── Directory.tsx
├── ContentDropDown.tsx
├── Statusbar.tsx
├── Sort.tsx
├── Label.tsx
├── TorrentDetail.tsx
├── DetailLoader.tsx
├── File.tsx
├── NavBar.tsx
├── TorrentButtons.tsx
├── TorrentFiles.tsx
├── GlobalDown.tsx
├── GlobalUp.tsx
├── Options.tsx
├── GlobalConnection.tsx
├── TorrentOptionForm.tsx
├── Filter.tsx
├── Torrent.tsx
├── TorrentMenu.tsx
├── TorrentOption.tsx
├── AddTorrentButton.tsx
└── TorrentList.tsx
├── pages
├── api
│ ├── auth
│ │ └── [...nextauth].ts
│ ├── hello.ts
│ └── trpc
│ │ └── [trpc].ts
├── _document.tsx
├── _app.tsx
├── home
│ ├── index.tsx
│ └── [id].tsx
└── index.tsx
├── server
├── deluge.ts
├── routers
│ ├── _app.ts
│ ├── auth.ts
│ └── deluge.ts
├── trpc.ts
└── context.ts
├── types
└── next-auth.d.ts
├── next.config.js
├── tsconfig.json
├── utils
├── get-server-auth-session.ts
├── requiredAuth.ts
├── nextAuthOption.ts
├── trpc.ts
└── helper.ts
├── .github
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
└── workflows
│ └── docker-image.yml
├── .gitignore
├── LICENCE
├── stores
├── useTorrentStore.ts
└── useTableStore.ts
├── package.json
├── Dockerfile
├── README.md
└── methods.json
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/deluge/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./deluge";
2 | export * from "./types";
3 |
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | margin: 0;
4 | height: 100%
5 | }
--------------------------------------------------------------------------------
/_docs/add.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maulik9898/barrage/HEAD/_docs/add.jpg
--------------------------------------------------------------------------------
/_docs/home.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maulik9898/barrage/HEAD/_docs/home.jpg
--------------------------------------------------------------------------------
/_docs/menu.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maulik9898/barrage/HEAD/_docs/menu.jpg
--------------------------------------------------------------------------------
/_docs/sort.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maulik9898/barrage/HEAD/_docs/sort.jpg
--------------------------------------------------------------------------------
/_docs/detail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maulik9898/barrage/HEAD/_docs/detail.jpg
--------------------------------------------------------------------------------
/_docs/files.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maulik9898/barrage/HEAD/_docs/files.jpg
--------------------------------------------------------------------------------
/_docs/filter.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maulik9898/barrage/HEAD/_docs/filter.jpg
--------------------------------------------------------------------------------
/_docs/options.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maulik9898/barrage/HEAD/_docs/options.jpg
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maulik9898/barrage/HEAD/public/logo.png
--------------------------------------------------------------------------------
/_docs/globaldl.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maulik9898/barrage/HEAD/_docs/globaldl.jpg
--------------------------------------------------------------------------------
/_docs/globalup.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maulik9898/barrage/HEAD/_docs/globalup.jpg
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maulik9898/barrage/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/_docs/add_torrent.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maulik9898/barrage/HEAD/_docs/add_torrent.jpg
--------------------------------------------------------------------------------
/_docs/pagination.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maulik9898/barrage/HEAD/_docs/pagination.jpg
--------------------------------------------------------------------------------
/public/icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maulik9898/barrage/HEAD/public/icon-192x192.png
--------------------------------------------------------------------------------
/public/icon-256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maulik9898/barrage/HEAD/public/icon-256x256.png
--------------------------------------------------------------------------------
/public/icon-384x384.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maulik9898/barrage/HEAD/public/icon-384x384.png
--------------------------------------------------------------------------------
/public/icon-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maulik9898/barrage/HEAD/public/icon-512x512.png
--------------------------------------------------------------------------------
/components/Loading.tsx:
--------------------------------------------------------------------------------
1 | import { Loader } from '@mantine/core'
2 |
3 | const Loading = () => {
4 | return (
5 |
6 | )
7 | }
8 |
9 | export default Loading
--------------------------------------------------------------------------------
/pages/api/auth/[...nextauth].ts:
--------------------------------------------------------------------------------
1 |
2 | import NextAuth from "next-auth";
3 | import { nextAuthOptions } from "../../../utils/nextAuthOption";
4 |
5 |
6 | export default NextAuth(nextAuthOptions)
7 |
--------------------------------------------------------------------------------
/server/deluge.ts:
--------------------------------------------------------------------------------
1 | import { Deluge } from "../deluge/index";
2 |
3 | const delugeClient = new Deluge({
4 | baseUrl: process.env.DELUGE_URL,
5 | password: process.env.DELUGE_PASSWORD,
6 | });
7 |
8 | export default delugeClient;
9 |
--------------------------------------------------------------------------------
/types/next-auth.d.ts:
--------------------------------------------------------------------------------
1 | import { type DefaultSession } from "next-auth";
2 |
3 | declare module "next-auth" {
4 | /**
5 | * Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
6 | */
7 | interface Session {
8 | user?: {
9 | id: string;
10 | } & DefaultSession["user"];
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/pages/api/hello.ts:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 | import type { NextApiRequest, NextApiResponse } from 'next'
3 |
4 | type Data = {
5 | name: string
6 | }
7 |
8 | export default function handler(
9 | req: NextApiRequest,
10 | res: NextApiResponse
11 | ) {
12 | res.status(200).json({ name: 'John Doe' })
13 | }
14 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const nextConfig = {
2 | reactStrictMode: true,
3 | swcMinify: true,
4 | output: "standalone",
5 | compiler: {
6 | removeConsole: process.env.NODE_ENV !== "development",
7 | },
8 | };
9 |
10 | const withPWA = require("next-pwa")({
11 | dest: "public",
12 | disable: process.env.NODE_ENV === "development",
13 | register: true,
14 | });
15 |
16 | module.exports = withPWA(nextConfig);
--------------------------------------------------------------------------------
/pages/api/trpc/[trpc].ts:
--------------------------------------------------------------------------------
1 | import * as trpcNext from "@trpc/server/adapters/next";
2 | import { env } from "process";
3 | import { createContext } from "../../../server/context";
4 | import { appRouter } from "../../../server/routers/_app";
5 |
6 | // export API handler
7 | export default trpcNext.createNextApiHandler({
8 | router: appRouter,
9 | createContext,
10 | onError:
11 | env.NODE_ENV === "development"
12 | ? ({ path, error }) => {
13 | console.error(`❌ tRPC failed on ${path}: ${error}`);
14 | }
15 | : undefined,
16 | });
17 |
--------------------------------------------------------------------------------
/deluge/got.ts:
--------------------------------------------------------------------------------
1 | import got from "got";
2 |
3 | const logger = got.extend({
4 | handlers: [
5 | (options, next) => {
6 | console.log(`Sending ${options.method} to ${options.url} options: ${(options.json as any)?.method}`);
7 | return next(options);
8 | }
9 | ],
10 | hooks: {
11 | afterResponse: [
12 | (response, retryWithMergedOptions) => {
13 | console.log(`Total time ${response.timings.phases.total}`)
14 | return response
15 | }
16 | ]
17 | },
18 | dnsCache: true
19 | });
20 | export default logger
21 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true
17 | },
18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
19 | "exclude": ["node_modules"]
20 | }
21 |
--------------------------------------------------------------------------------
/utils/get-server-auth-session.ts:
--------------------------------------------------------------------------------
1 | import { type GetServerSidePropsContext } from "next";
2 | import { unstable_getServerSession } from "next-auth";
3 | import { nextAuthOptions } from "./nextAuthOption";
4 |
5 |
6 | /**
7 | * Wrapper for unstable_getServerSession https://next-auth.js.org/configuration/nextjs
8 | * See example usage in trpc createContext or the restricted API route
9 | */
10 | export const getServerAuthSession = async (ctx: {
11 | req: GetServerSidePropsContext["req"];
12 | res: GetServerSidePropsContext["res"];
13 | }) => {
14 | return await unstable_getServerSession(ctx.req, ctx.res, nextAuthOptions);
15 | };
16 |
--------------------------------------------------------------------------------
/pages/_document.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { createGetInitialProps } from "@mantine/next";
3 | import Document, { Head, Html, Main, NextScript } from "next/document";
4 |
5 | const getInitialProps = createGetInitialProps();
6 |
7 | export default class _Document extends Document {
8 | static getInitialProps = getInitialProps;
9 |
10 | render() {
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/server/routers/_app.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import client from "../deluge";
3 | import { publicProcedure, router } from "../trpc";
4 | import { auth } from "./auth";
5 | import { deluge } from "./deluge";
6 |
7 | export const appRouter = router({
8 | hello: publicProcedure
9 | .input(
10 | z.object({
11 | text: z.string().nullish(),
12 | })
13 | )
14 | .query(async ({ input }) => {
15 | const a = await client.getAllData();
16 | return {
17 | a,
18 | };
19 | }),
20 | auth: auth,
21 | deluge: deluge
22 | });
23 |
24 | // export type definition of API
25 | export type AppRouter = typeof appRouter;
26 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/server/routers/auth.ts:
--------------------------------------------------------------------------------
1 | import { TRPCError } from "@trpc/server";
2 | import { z } from "zod";
3 | import { publicProcedure, router } from "../trpc";
4 |
5 | export const auth = router({
6 | login: publicProcedure
7 | .input(
8 | z.object({
9 | password: z.string(),
10 | })
11 | )
12 | .mutation(({ input }) => {
13 | if (input.password == process.env.BARRAGE_PASSWORD) {
14 | return {
15 | status: "ok",
16 | token: Buffer.from(input.password).toString("base64"),
17 | };
18 | } else {
19 | throw new TRPCError({
20 | code: "UNAUTHORIZED",
21 | message: "Invalid Password"
22 | })
23 | }
24 | }),
25 | });
26 |
--------------------------------------------------------------------------------
/utils/requiredAuth.ts:
--------------------------------------------------------------------------------
1 | // @/src/common/requireAuth.ts
2 | import type { GetServerSideProps, GetServerSidePropsContext } from "next";
3 | import { unstable_getServerSession } from "next-auth";
4 | import { nextAuthOptions } from "./nextAuthOption";
5 |
6 | export const requireAuth =
7 | (func: GetServerSideProps) => async (ctx: GetServerSidePropsContext) => {
8 | const session = await unstable_getServerSession(
9 | ctx.req,
10 | ctx.res,
11 | nextAuthOptions
12 | );
13 |
14 | if (!session) {
15 | return {
16 | redirect: {
17 | destination: "/", // login path
18 | permanent: false,
19 | },
20 | };
21 | }
22 |
23 | return await func(ctx);
24 | };
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | .pnpm-debug.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
38 | docker-compose.yaml
39 |
40 | #pwa
41 | **/public/sw.js
42 | **/public/workbox-*.js
43 | **/public/worker-*.js
44 | **/public/sw.js.map
45 | **/public/workbox-*.js.map
46 | **/public/worker-*.js.map
--------------------------------------------------------------------------------
/components/DetailGridCol.tsx:
--------------------------------------------------------------------------------
1 | import { Grid, Text } from "@mantine/core";
2 | import React from "react";
3 |
4 | const DetailGridCol = ({ label, value }: { label: string; value: string }) => {
5 | return (
6 | <>
7 |
8 |
9 | {label}
10 |
11 |
12 |
13 |
21 | {value}
22 |
23 |
24 | >
25 | );
26 | };
27 |
28 | export default DetailGridCol;
29 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 |
27 | **Device (please complete the following information):**
28 | - Browser [e.g. stock browser, safari]
29 | - Deluge version Version [e.g. 1.0.3]
30 |
31 | **Additional context**
32 | Add any other context about the problem here.
33 |
--------------------------------------------------------------------------------
/server/trpc.ts:
--------------------------------------------------------------------------------
1 | import { TRPCError, initTRPC } from "@trpc/server";
2 | import { Context } from "./context";
3 |
4 | // Avoid exporting the entire t-object since it's not very
5 | // descriptive and can be confusing to newcomers used to t
6 | // meaning translation in i18n libraries.
7 | const t = initTRPC.context().create();
8 |
9 | // Base router and procedure helpers
10 | export const router = t.router;
11 | export const publicProcedure = t.procedure;
12 |
13 | const isAuthed = t.middleware(({ ctx, next }) => {
14 | if (!ctx.session || !ctx.session.user?.id) {
15 | throw new TRPCError({ code: "UNAUTHORIZED" });
16 | }
17 | return next({
18 | ctx: {
19 | // infers the `session` as non-nullable
20 | session: { ...ctx.session, user: ctx.session.user },
21 | },
22 | });
23 | });
24 | export const protectedProcedure = t.procedure.use(isAuthed);
25 |
--------------------------------------------------------------------------------
/components/ListSkeleton.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Card,
3 | Flex,
4 | Center,
5 | Title,
6 | MediaQuery,
7 | Box,
8 | Progress,
9 | useMantineTheme,
10 | Skeleton,
11 | Loader,
12 | Grid,
13 | } from "@mantine/core";
14 | import { useMediaQuery } from "@mantine/hooks";
15 | import { IconTriangleInverted, IconTriangle } from "@tabler/icons";
16 | import Link from "next/link";
17 | import React from "react";
18 | import { humanFileSize, forHumansSeconds } from "../utils/helper";
19 | import BadgeToolTip from "./BadgeToolTip";
20 | import TorrentButtons from "./TorrentButtons";
21 | import MemoizedTorrentMenu from "./TorrentMenu";
22 |
23 | const ListSkeleton = () => {
24 | return (
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | export default ListSkeleton;
32 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "theme_color": "#25262b",
3 | "background_color": "#278beb",
4 | "display": "standalone",
5 | "scope": "/",
6 | "start_url": "/",
7 | "name": "Barrage",
8 | "short_name": "Barrage",
9 | "description": "Minimal deluge WebUI",
10 | "icons": [
11 | {
12 | "src": "/icon-192x192.png",
13 | "sizes": "192x192",
14 | "type": "image/png"
15 | },
16 | {
17 | "src": "/icon-256x256.png",
18 | "sizes": "256x256",
19 | "type": "image/png"
20 | },
21 | {
22 | "src": "/icon-384x384.png",
23 | "sizes": "384x384",
24 | "type": "image/png"
25 | },
26 | {
27 | "src": "/icon-512x512.png",
28 | "sizes": "512x512",
29 | "type": "image/png"
30 | }
31 | ]
32 | }
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/components/RouterTransition.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react-hooks/exhaustive-deps */
2 | // components/RouterTransition.tsx
3 | import {
4 | completeNavigationProgress,
5 | NavigationProgress, startNavigationProgress
6 | } from '@mantine/nprogress';
7 | import { useRouter } from 'next/router';
8 | import { useEffect } from 'react';
9 |
10 | export function RouterTransition() {
11 | const router = useRouter();
12 |
13 | useEffect(() => {
14 | const handleStart = (url: string) => url !== router.asPath && startNavigationProgress();
15 | const handleComplete = () => completeNavigationProgress();
16 |
17 | router.events.on('routeChangeStart', handleStart);
18 | router.events.on('routeChangeComplete', handleComplete);
19 | router.events.on('routeChangeError', handleComplete);
20 |
21 | return () => {
22 | router.events.off('routeChangeStart', handleStart);
23 | router.events.off('routeChangeComplete', handleComplete);
24 | router.events.off('routeChangeError', handleComplete);
25 | };
26 | }, [router.asPath]);
27 |
28 | return ;
29 | }
--------------------------------------------------------------------------------
/LICENCE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 maulik9898
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/components/AddTorrentModal.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react-hooks/exhaustive-deps */
2 | import {
3 | Accordion,
4 | Box,
5 | Button,
6 | Checkbox,
7 | Grid,
8 | Group,
9 | Loader,
10 | NumberInput,
11 | Radio,
12 | Space,
13 | Text,
14 | TextInput,
15 | } from "@mantine/core";
16 | import { UseFormReturnType } from "@mantine/form";
17 | import { IconSettings } from "@tabler/icons";
18 | import { ConfigValues } from "../deluge";
19 | import TorrentOptionForm from "./TorrentOptionForm";
20 |
21 | const AddTorrentModal = ({
22 |
23 | form,
24 | }: {
25 | form: UseFormReturnType ConfigValues>;
26 | }) => {
27 | return (
28 |
29 |
30 | }>
31 | Advance options
32 |
33 |
34 |
37 |
38 |
39 |
40 | );
41 | };
42 |
43 | export default AddTorrentModal;
44 |
--------------------------------------------------------------------------------
/components/BadgeToolTip.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | ActionIcon, Badge, Tooltip, useMantineTheme
3 | } from "@mantine/core";
4 | import { useMediaQuery } from "@mantine/hooks";
5 | import { IconTag } from "@tabler/icons";
6 |
7 | const BadgeToolTip = ({
8 | tag,
9 | toolTipLabel,
10 | }: {
11 | tag?: string;
12 | toolTipLabel: string;
13 | }) => {
14 | const theme = useMantineTheme();
15 | const largeScreen = useMediaQuery("(min-width: 800px)");
16 | return (
17 |
18 |
32 |
33 |
34 | }
35 | >
36 | {tag || "No Label"}
37 |
38 |
39 | );
40 | };
41 |
42 | export default BadgeToolTip;
43 |
--------------------------------------------------------------------------------
/components/MoveStorage.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Flex, TextInput } from "@mantine/core";
2 | import { useForm } from "@mantine/form";
3 | import React from "react";
4 | import { trpc } from "../utils/trpc";
5 |
6 | const MoveStorage = ({ id, close }: { id: string; close: () => void }) => {
7 | const form = useForm({
8 | initialValues: {
9 | location: "",
10 | },
11 | validate: {
12 | location: (value) => (value ? null : "Location is required"),
13 | },
14 | });
15 |
16 | const moveStorage = trpc.deluge.moveStorage.useMutation({});
17 | return (
18 |
40 | );
41 | };
42 |
43 | export default MoveStorage;
44 |
--------------------------------------------------------------------------------
/server/context.ts:
--------------------------------------------------------------------------------
1 | import { type inferAsyncReturnType } from "@trpc/server";
2 | import { type CreateNextContextOptions } from "@trpc/server/adapters/next";
3 | import { type Session } from "next-auth";
4 |
5 | import { getServerAuthSession } from "../utils/get-server-auth-session";
6 |
7 | type CreateContextOptions = {
8 | session: Session | null;
9 | };
10 |
11 | /** Use this helper for:
12 | * - testing, so we dont have to mock Next.js' req/res
13 | * - trpc's `createSSGHelpers` where we don't have req/res
14 | * @see https://beta.create.t3.gg/en/usage/trpc#-servertrpccontextts
15 | **/
16 | export const createContextInner = async (opts: CreateContextOptions) => {
17 | return {
18 | session: opts.session,
19 | };
20 | };
21 |
22 | /**
23 | * This is the actual context you'll use in your router
24 | * @link https://trpc.io/docs/context
25 | **/
26 | export const createContext = async (opts?: CreateNextContextOptions) => {
27 | const { req, res } = opts!;
28 |
29 | // Get the session from the server using the unstable_getServerSession wrapper function
30 | const session = await getServerAuthSession({ req, res });
31 |
32 | return await createContextInner({
33 | session,
34 | });
35 | };
36 |
37 | export type Context = inferAsyncReturnType;
38 |
--------------------------------------------------------------------------------
/components/TorrentPageNav.tsx:
--------------------------------------------------------------------------------
1 | import { ActionIcon, Card, Center, Text, Tooltip } from "@mantine/core";
2 | import { IconArrowLeft } from "@tabler/icons";
3 | import Link from "next/link";
4 |
5 | const TorrentPageNav = ({ name }: { name: string }) => {
6 | return (
7 |
8 |
14 |
15 |
22 | ({
24 | borderWidth: 1,
25 | borderColor: theme.colors.cyan,
26 | })}
27 | size={"xl"}
28 | component={Link}
29 | href="/home"
30 | color={"cyan"}
31 | variant="light"
32 | >
33 |
34 |
35 |
36 |
37 | {name}
38 |
39 |
40 |
41 |
42 | );
43 | };
44 |
45 | export default TorrentPageNav;
46 |
--------------------------------------------------------------------------------
/utils/nextAuthOption.ts:
--------------------------------------------------------------------------------
1 | import { Awaitable, NextAuthOptions, RequestInternal, User } from "next-auth";
2 | import Credentials from "next-auth/providers/credentials";
3 | export const nextAuthOptions: NextAuthOptions = {
4 | providers: [
5 | Credentials({
6 | name: "credentials",
7 | credentials: {
8 | password: {
9 | label: "Password",
10 | type: "password",
11 | },
12 | },
13 | async authorize(credentials, req) {
14 | if (!credentials?.password) return null;
15 | if (credentials.password == process.env.BARRAGE_PASSWORD) {
16 | const user = {
17 | id: Buffer.from(credentials.password).toString("base64"),
18 | };
19 | console.log("login");
20 | return user;
21 | }
22 | return null;
23 | },
24 | }),
25 | ],
26 | callbacks: {
27 | jwt: async ({ token, user }) => {
28 | if (user) {
29 | token.id = user.id;
30 | token.email = user.email;
31 | }
32 |
33 | return token;
34 | ``;
35 | },
36 | session: async ({ session, token }) => {
37 | if (token && session.user) {
38 | session.user.id = token.id as string;
39 | }
40 |
41 | return session;
42 | },
43 | },
44 |
45 | pages: {
46 | signIn: "/",
47 | },
48 | session: {
49 | maxAge: 24 * 60 * 60,
50 | },
51 | };
52 |
--------------------------------------------------------------------------------
/utils/trpc.ts:
--------------------------------------------------------------------------------
1 | import { httpBatchLink } from "@trpc/client";
2 | import { createTRPCNext } from "@trpc/next";
3 | import superJSON from "superjson";
4 | import type { AppRouter } from "../server/routers/_app";
5 |
6 | function getBaseUrl() {
7 | if (typeof window !== "undefined")
8 | // browser should use relative path
9 | return "";
10 |
11 | if (process.env.VERCEL_URL)
12 | // reference for vercel.com
13 | return `https://${process.env.VERCEL_URL}`;
14 |
15 | if (process.env.RENDER_INTERNAL_HOSTNAME)
16 | // reference for render.com
17 | return `http://${process.env.RENDER_INTERNAL_HOSTNAME}:${process.env.PORT}`;
18 |
19 | // assume localhost
20 | return `http://localhost:${process.env.PORT ?? 3000}`;
21 | }
22 |
23 | export const trpc = createTRPCNext({
24 | config({ ctx }) {
25 | return {
26 | links: [
27 | httpBatchLink({
28 | /**
29 | * If you want to use SSR, you need to use the server's full URL
30 | * @link https://trpc.io/docs/ssr
31 | **/
32 | url: `${getBaseUrl()}/api/trpc`,
33 | }),
34 | ],
35 | /**
36 | * @link https://tanstack.com/query/v4/docs/reference/QueryClient
37 | **/
38 | queryClientConfig: { defaultOptions: { queries: { retry: false } } },
39 | };
40 | },
41 | /**
42 | * @link https://trpc.io/docs/ssr
43 | **/
44 | ssr: false,
45 | });
46 | // => { useQuery: ..., useMutation: ...}
47 |
--------------------------------------------------------------------------------
/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import "../styles/globals.css";
2 | import type { AppProps } from "next/app";
3 | import { MantineProvider } from "@mantine/core";
4 | import Head from "next/head";
5 | import { trpc } from "../utils/trpc";
6 | import { RouterTransition } from "../components/RouterTransition";
7 | import { SessionProvider } from "next-auth/react";
8 | import { ModalsProvider } from "@mantine/modals";
9 | import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
10 | import Label from "../components/Label";
11 |
12 | export function App({ Component, pageProps }: AppProps) {
13 | return (
14 | <>
15 |
16 | Barrage
17 |
21 |
22 |
23 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | >
39 | );
40 | }
41 |
42 | export default trpc.withTRPC(App);
43 |
--------------------------------------------------------------------------------
/stores/useTorrentStore.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ColumnFiltersState,
3 | functionalUpdate,
4 | OnChangeFn,
5 | SortingState,
6 | Updater,
7 | } from "@tanstack/react-table";
8 | import create from "zustand";
9 | import { Label, Stats, TorrentState } from "../deluge";
10 |
11 | interface TorrentStoreState {
12 | lables: Label[];
13 | states: Array<[TorrentState, number]>;
14 | isv2: boolean;
15 | stats: Stats;
16 | setStats: (stats: Stats) => void;
17 | setIsv2: (v2: boolean) => void;
18 | setState: (states: Array<[TorrentState, number]>) => void;
19 | setLables: (labels: Label[]) => void;
20 | }
21 |
22 | const useTorrentStore = create((set) => ({
23 | lables: [],
24 | stats: {
25 | dht_nodes: 0,
26 | download_protocol_rate: 0,
27 | download_rate: 0,
28 | free_space: 0,
29 | has_incoming_connections: false,
30 | max_download: 0,
31 | max_num_connections: 0,
32 | max_upload: 0,
33 | num_connections: 0,
34 | upload_protocol_rate: 0,
35 | upload_rate: 0,
36 | },
37 | isv2: true,
38 | states: [],
39 | setIsv2: (v2) =>
40 | set(() => ({
41 | isv2: v2,
42 | })),
43 | setLables: (labels: Label[]) =>
44 | set(() => ({
45 | lables: labels,
46 | })),
47 | setState: (states) =>
48 | set(() => ({
49 | states: states,
50 | })),
51 | setStats: (stats) =>
52 | set(() => ({
53 | stats: stats,
54 | })),
55 | }));
56 |
57 | export default useTorrentStore;
58 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "barrage",
3 | "version": "0.2.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@ctrl/magnet-link": "^3.1.1",
13 | "@ctrl/url-join": "^2.0.2",
14 | "@emotion/react": "^11.10.5",
15 | "@emotion/server": "^11.10.0",
16 | "@mantine/core": "^5.7.0",
17 | "@mantine/form": "^5.7.0",
18 | "@mantine/hooks": "^5.7.0",
19 | "@mantine/modals": "^5.7.0",
20 | "@mantine/next": "^5.7.0",
21 | "@mantine/nprogress": "^5.7.0",
22 | "@tabler/icons": "^1.109.0",
23 | "@tanstack/react-query": "^4.14.1",
24 | "@tanstack/react-query-devtools": "^4.14.3",
25 | "@tanstack/react-table": "^8.5.22",
26 | "@trpc/client": "^10.0.0-proxy-beta.26",
27 | "@trpc/next": "^10.0.0-proxy-beta.26",
28 | "@trpc/react-query": "^10.0.0-proxy-beta.26",
29 | "@trpc/server": "^10.0.0-proxy-beta.26",
30 | "@types/node": "18.11.9",
31 | "@types/react": "18.0.24",
32 | "@types/react-dom": "18.0.8",
33 | "@types/tough-cookie": "4.0.2",
34 | "date-fns": "^2.29.3",
35 | "eslint": "8.26.0",
36 | "eslint-config-next": "13.0.1",
37 | "formdata-node": "^5.0.0",
38 | "got": "^12.5.0",
39 | "next": "13.0.1",
40 | "next-auth": "^4.16.2",
41 | "next-pwa": "^5.6.0",
42 | "preact": "^10.11.2",
43 | "react": "18.2.0",
44 | "react-dom": "18.2.0",
45 | "superjson": "^1.11.0",
46 | "tough-cookie": "^4.1.2",
47 | "typedoc": "0.23.15",
48 | "typescript": "4.8.4",
49 | "zod": "^3.19.1",
50 | "zustand": "^4.1.4"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/stores/useTableStore.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ColumnFiltersState,
3 | SortingState,
4 | OnChangeFn,
5 | functionalUpdate,
6 | Updater,
7 | Column,
8 | PaginationState,
9 | } from "@tanstack/react-table";
10 | import create from "zustand";
11 | import { NormalizedTorrent } from "../deluge";
12 | import useTorrentStore from "./useTorrentStore";
13 |
14 | interface TableStoreState {
15 | filter: ColumnFiltersState;
16 | sorting: SortingState;
17 | setSorting: OnChangeFn;
18 | pagination: PaginationState;
19 | setFilter: OnChangeFn;
20 | columns: Column[];
21 | setColumns: (columns: Column[]) => void;
22 | setPagination: OnChangeFn;
23 | }
24 |
25 | const useTableStore = create((set) => ({
26 | filter: [],
27 | columns: [],
28 | pagination: {
29 | pageIndex: 0,
30 | pageSize: 10,
31 | },
32 | sorting: [
33 | {
34 | id: "queuePosition",
35 | desc: false,
36 | },
37 | ],
38 | setSorting: (sortingState: Updater) =>
39 | set((s) => ({
40 | sorting: functionalUpdate(sortingState, s.sorting),
41 | })),
42 | setFilter: (filterState: Updater) =>
43 | set((s) => ({
44 | pagination: { ...s.pagination, ...{ pageIndex: 0 } },
45 | filter: functionalUpdate(filterState, s.filter),
46 | })),
47 | setColumns: (columns) =>
48 | set(() => ({
49 | columns: columns,
50 | })),
51 | setPagination: (pagination) =>
52 | set((s) => ({
53 | pagination: functionalUpdate(pagination, s.pagination),
54 | })),
55 | }));
56 |
57 | export default useTableStore;
58 |
--------------------------------------------------------------------------------
/components/Directory.tsx:
--------------------------------------------------------------------------------
1 | import { Accordion, Box, Center, Select, Text } from "@mantine/core";
2 | import { Content } from "../deluge";
3 | import { getFileMap } from "../utils/helper";
4 |
5 | const Directory = ({
6 | name,
7 | content,
8 | onChange,
9 | }: {
10 | name: string;
11 | content: Content;
12 | onChange: (data: Record) => void;
13 | }) => {
14 | const setDirPriority = (priority: number) => {
15 | const fileMap = getFileMap(content.contents!, priority);
16 | onChange(fileMap);
17 | };
18 | return (
19 | ({
21 | display: "flex",
22 | alignItems: "center",
23 | "&:hover": {
24 | backgroundColor: theme.colors.dark[6],
25 | },
26 | })}
27 | >
28 |
65 | );
66 | };
67 |
68 | export default Directory;
69 |
--------------------------------------------------------------------------------
/components/ContentDropDown.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Accordion
3 | } from "@mantine/core";
4 | import { useMemo } from "react";
5 | import { Content } from "../deluge";
6 | import Directory from "./Directory";
7 | import File from "./File";
8 |
9 |
10 | const ContentDropDown = ({
11 | content,
12 | setPriority,
13 | }: {
14 | content: Content;
15 | setPriority: (data: Record) => void;
16 | }) => {
17 |
18 | const dirs = useMemo(() => {
19 | const tmp: [string, Content][] = [];
20 |
21 | Object.entries(content.contents || {}).forEach(([n, c]) => {
22 | if (c.type === "dir") {
23 | tmp.push([n, c]);
24 | }
25 | });
26 |
27 | return tmp.sort(([n, c], [n1, c1]) => n.localeCompare(n1));
28 | }, [content]);
29 |
30 | const files = useMemo(() => {
31 | const tmp: [string, Content][] = [];
32 |
33 | Object.entries(content.contents || {}).forEach(([n, c]) => {
34 | if (c.type === "file") {
35 | tmp.push([n, c]);
36 | }
37 | });
38 |
39 | return tmp.sort(([n, c], [n1, c1]) => n.localeCompare(n1));
40 | }, [content]);
41 |
42 | return (
43 | <>
44 | {files.map(([n, c]) => {
45 | return ;
46 | })}
47 |
51 | {dirs.map(([n, c]) => {
52 | return (
53 |
54 |
55 |
56 |
57 |
58 |
59 | );
60 | })}
61 |
62 | >
63 | );
64 | };
65 |
66 | export default ContentDropDown;
67 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Install dependencies only when needed
2 | FROM node:16-alpine AS deps
3 | # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
4 | RUN apk add --no-cache libc6-compat
5 | WORKDIR /app
6 |
7 | # Install dependencies based on the preferred package manager
8 | COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
9 | RUN \
10 | if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
11 | elif [ -f package-lock.json ]; then npm ci; \
12 | elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
13 | else echo "Lockfile not found." && exit 1; \
14 | fi
15 |
16 |
17 | # Rebuild the source code only when needed
18 | FROM node:16-alpine AS builder
19 | WORKDIR /app
20 | COPY --from=deps /app/node_modules ./node_modules
21 | COPY . .
22 |
23 | # Next.js collects completely anonymous telemetry data about general usage.
24 | # Learn more here: https://nextjs.org/telemetry
25 | # Uncomment the following line in case you want to disable telemetry during the build.
26 | ENV NEXT_TELEMETRY_DISABLED 1
27 |
28 | #RUN yarn build
29 |
30 | # If using npm comment out above and use below instead
31 | RUN npm run build
32 |
33 | # Production image, copy all the files and run next
34 | FROM node:16-alpine AS runner
35 | WORKDIR /app
36 |
37 | ENV NODE_ENV production
38 | # Uncomment the following line in case you want to disable telemetry during runtime.
39 | ENV NEXT_TELEMETRY_DISABLED 1
40 |
41 | RUN addgroup --system --gid 1001 nodejs
42 | RUN adduser --system --uid 1001 nextjs
43 |
44 | COPY --from=builder /app/public ./public
45 |
46 | # Automatically leverage output traces to reduce image size
47 | # https://nextjs.org/docs/advanced-features/output-file-tracing
48 | COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
49 | COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
50 |
51 | USER nextjs
52 |
53 | EXPOSE 3000
54 |
55 | ENV PORT 3000
56 |
57 | CMD ["node", "server.js"]
--------------------------------------------------------------------------------
/.github/workflows/docker-image.yml:
--------------------------------------------------------------------------------
1 | name: DOCKER-BUILD
2 | # Controls when the workflow will run
3 | on:
4 | workflow_dispatch:
5 | push:
6 | tags:
7 | - '*'
8 | pull_request:
9 | branches:
10 | - 'main'
11 | # permissions are needed if pushing to ghcr.io
12 | permissions:
13 | packages: write
14 | jobs:
15 | build:
16 | runs-on: ubuntu-latest
17 | steps:
18 | # Get the repository's code
19 | - name: Checkout
20 | uses: actions/checkout@v2
21 | # https://github.com/docker/setup-qemu-action
22 | - name: Set up QEMU
23 | uses: docker/setup-qemu-action@v2
24 | # https://github.com/docker/setup-buildx-action
25 | - name: Set up Docker Buildx
26 | id: buildx
27 | uses: docker/setup-buildx-action@v2
28 | - name: Login to Docker Hub
29 | if: github.event_name != 'pull_request'
30 | uses: docker/login-action@v1
31 | with:
32 | username: ${{ secrets.DOCKERHUB_USERNAME }}
33 | password: ${{ secrets.DOCKERHUB_TOKEN }}
34 | - name: Docker meta
35 | id: barrage-meta-id # you'll use this in the next step
36 | uses: docker/metadata-action@v4
37 | with:
38 | # list of Docker images to use as base name for tags
39 | images: |
40 | maulik9898/barrage
41 | # Docker tags based on the following events/attributes
42 | tags: |
43 | type=schedule
44 | type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
45 | type=ref,event=branch
46 | type=ref,event=pr
47 | type=semver,pattern={{version}}
48 | type=sha
49 | - name: Build and push
50 | uses: docker/build-push-action@v3
51 | with:
52 | context: .
53 | platforms: linux/amd64,linux/arm64, linux/arm/v7, linux/arm/v6
54 | push: ${{ github.event_name != 'pull_request' }}
55 | tags: ${{ steps.barrage-meta-id.outputs.tags }}
56 | labels: ${{ steps.barrage-meta-id.outputs.labels }}
57 |
--------------------------------------------------------------------------------
/components/Statusbar.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Box,
3 | Button,
4 | Card,
5 | Center,
6 | Flex,
7 | Group,
8 | MediaQuery,
9 | Paper,
10 | Text,
11 | Tooltip,
12 | UnstyledButton,
13 | } from "@mantine/core";
14 | import {
15 | IconChevronsDown,
16 | IconChevronsUp,
17 | IconNetwork,
18 | IconServer2,
19 | IconSpace,
20 | } from "@tabler/icons";
21 | import React from "react";
22 | import useTorrentStore from "../stores/useTorrentStore";
23 | import { humanFileSize } from "../utils/helper";
24 | import GlobalConnection from "./GlobalConnection";
25 | import GlobalDown from "./GlobalDown";
26 | import GlobalUp from "./GlobalUp";
27 |
28 | const Statusbar = () => {
29 | const stats = useTorrentStore((s) => s.stats);
30 | return (
31 | ({
34 | background: theme.colors.dark[8],
35 | borderBottom: "1px solid",
36 | borderColor: theme.colors.gray[7],
37 | })}
38 | radius={0}
39 | >
40 |
46 |
47 |
48 |
49 |
50 |
51 |
59 | }
64 | sx={(theme) => ({
65 | background: theme.colors.dark[8],
66 | borderRight: 0,
67 | borderColor: theme.colors.gray[7],
68 | borderBottom: 0,
69 | borderLeft: 0,
70 | borderTop: 0,
71 | })}
72 | radius={0}
73 | variant="default"
74 | >
75 | {`${humanFileSize(stats.free_space)}`}
76 |
77 |
78 |
79 |
80 |
81 | );
82 | };
83 |
84 | export default Statusbar;
85 |
--------------------------------------------------------------------------------
/pages/home/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react-hooks/exhaustive-deps */
2 | import TorrentList from "../../components/TorrentList";
3 |
4 | import {
5 | useReactTable,
6 | getCoreRowModel,
7 | getSortedRowModel,
8 | getFilteredRowModel,
9 | createColumnHelper,
10 | } from "@tanstack/react-table";
11 | import useTorrentStore from "../../stores/useTorrentStore";
12 | import { useEffect, useMemo, useState } from "react";
13 | import { NormalizedTorrent } from "../../deluge";
14 | import { trpc } from "../../utils/trpc";
15 | import MemoizedNavBar from "../../components/NavBar";
16 | import useTableStore from "../../stores/useTableStore";
17 | import NavBar from "../../components/NavBar";
18 | import { Box, Group, Stack } from "@mantine/core";
19 | import { useSession } from "next-auth/react";
20 | import { useRouter } from "next/router";
21 | import Statusbar from "../../components/Statusbar";
22 |
23 | // export const getServerSideProps = requireAuth(async (ctx) => {
24 | // const isFirstServerCall = ctx.req?.url?.indexOf("/_next/data/") === -1;
25 | // if (!isFirstServerCall) {
26 | // return {
27 | // props: {},
28 | // };
29 | // }
30 | // const session = await unstable_getServerSession(
31 | // ctx.req,
32 | // ctx.res,
33 | // nextAuthOptions
34 | // );
35 | // const ssg = createProxySSGHelpers({
36 | // router: appRouter,
37 | // ctx: {
38 | // session: session,
39 | // },
40 | // });
41 | // /*
42 | // * `prefetch` does not return the result and never throws - if you need that behavior, use `fetch` instead.
43 | // */
44 | // await ssg.deluge.allData.prefetch();
45 |
46 | // // Make sure to return { props: { trpcState: ssg.dehydrate() } }
47 | // return {
48 | // props: {
49 | // trpcState: ssg.dehydrate(),
50 | // },
51 | // };
52 | // });
53 |
54 | export default function Home() {
55 | const router = useRouter();
56 | const { data, status } = useSession({
57 | required: true,
58 | onUnauthenticated() {
59 | router.push("/");
60 | },
61 | });
62 | if (status === "loading") {
63 | return <>>;
64 | }
65 | return (
66 |
73 |
74 |
75 |
76 |
77 | );
78 | }
79 |
--------------------------------------------------------------------------------
/components/Sort.tsx:
--------------------------------------------------------------------------------
1 | import { Tooltip, ActionIcon, Menu, Box } from "@mantine/core";
2 | import {
3 | IconSortAscending2,
4 | IconSortDescending,
5 | IconSortDescending2,
6 | } from "@tabler/icons";
7 | import { Column, SortDirection, Table } from "@tanstack/react-table";
8 | import React, { useMemo } from "react";
9 | import { NormalizedTorrent } from "../deluge/types";
10 | import useTableStore from "../stores/useTableStore";
11 | import useTorrentStore from "../stores/useTorrentStore";
12 |
13 | const Sort = () => {
14 | const setSorting = useTableStore((state) => state.setSorting);
15 | const columns = useTableStore((state) => state.columns);
16 |
17 | return (
18 |
79 | );
80 | };
81 | export default Sort;
82 |
--------------------------------------------------------------------------------
/components/Label.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Menu, Select } from "@mantine/core";
2 | import { useForceUpdate } from "@mantine/hooks";
3 | import { closeAllModals, ContextModalProps, openModal } from "@mantine/modals";
4 | import { IconTag } from "@tabler/icons";
5 | import { useEffect, useState } from "react";
6 | import useTorrentStore from "../stores/useTorrentStore";
7 | import { trpc } from "../utils/trpc";
8 |
9 | const Label = ({
10 | context,
11 | id,
12 | innerProps,
13 | }: ContextModalProps<{ id: string; refetch: () => void }>) => {
14 | const forceUpdate = useForceUpdate();
15 | const labels = useTorrentStore((state) => state.lables);
16 | const [err, setErr] = useState("");
17 | const setLabel = trpc.deluge.setLabel.useMutation({
18 | onSuccess() {
19 | innerProps.refetch();
20 | },
21 | onSettled(data, error, variables, context) {
22 | if (error) {
23 | setErr(error.message);
24 | }
25 | },
26 | });
27 | const createLabel = trpc.deluge.addLabel.useMutation({
28 | onSuccess(data, variables, context) {
29 | setLabel.mutate({
30 | id: innerProps.id,
31 | label: variables.label,
32 | });
33 | },
34 | onSettled(data, error, variables, context) {
35 | if (error) {
36 | setErr(error.message);
37 | }
38 | },
39 | });
40 | const data = [
41 | ...labels
42 | .filter((s) => s.name.toLowerCase() !== "all")
43 | .filter((s) => s.name)
44 | .map((s) => ({ value: s.name, label: s.name })),
45 | { value: "", label: "No Label" },
46 | ];
47 |
48 | return (
49 | <>
50 |