diff --git a/src/components/utils/Divider.tsx b/src/components/utils/Divider.tsx new file mode 100644 index 00000000..470bf17d --- /dev/null +++ b/src/components/utils/Divider.tsx @@ -0,0 +1,12 @@ +import classNames from "classnames"; + +export function Divider(props: { marginClass?: string }) { + return ( +
+ ); +} diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx new file mode 100644 index 00000000..845d5d4f --- /dev/null +++ b/src/pages/Settings.tsx @@ -0,0 +1,79 @@ +import { Icon, Icons } from "@/components/Icon"; +import { WideContainer } from "@/components/layout/WideContainer"; +import { Divider } from "@/components/utils/Divider"; +import { Heading1 } from "@/components/utils/Text"; +import { conf } from "@/setup/config"; + +import { SubPageLayout } from "./layouts/SubPageLayout"; + +// TODO Put all of this not here (when I'm done writing them) + +function SidebarSection(props: { title: string; children: React.ReactNode }) { + return ( +
+

+ {props.title} +

+ {props.children} +
+ ); +} + +function SidebarLink(props: { children: React.ReactNode; icon: Icons }) { + return ( +
+ + {props.children} +
+ ); +} + +function SettingsSidebar() { + // eslint-disable-next-line no-restricted-globals + const hostname = location.hostname; + + return ( +
+
+ + Account + + + +
+ Version + {conf().APP_VERSION} +
+
+ Domain + {hostname} +
+
+
+
+ ); +} + +function SettingsLayout(props: { children: React.ReactNode }) { + return ( + +
+ + {props.children} +
+
+ ); +} + +export function SettingsPage() { + return ( + + + Setting + + + ); +} diff --git a/src/pages/admin/AdminPage.tsx b/src/pages/admin/AdminPage.tsx index 5572912f..d98660d2 100644 --- a/src/pages/admin/AdminPage.tsx +++ b/src/pages/admin/AdminPage.tsx @@ -1,6 +1,8 @@ import { ThinContainer } from "@/components/layout/ThinContainer"; import { Heading1, Paragraph } from "@/components/utils/Text"; import { SubPageLayout } from "@/pages/layouts/SubPageLayout"; +import { ConfigValuesPart } from "@/pages/parts/admin/ConfigValuesPart"; +import { TMDBTestPart } from "@/pages/parts/admin/TMDBTestPart"; import { WorkerTestPart } from "@/pages/parts/admin/WorkerTestPart"; export function AdminPage() { @@ -10,7 +12,9 @@ export function AdminPage() { Admin tools Useful tools to test out your current deployment + + ); diff --git a/src/pages/parts/admin/ConfigValuesPart.tsx b/src/pages/parts/admin/ConfigValuesPart.tsx new file mode 100644 index 00000000..8947a5ab --- /dev/null +++ b/src/pages/parts/admin/ConfigValuesPart.tsx @@ -0,0 +1,32 @@ +import { ReactNode } from "react"; + +import { Divider } from "@/components/utils/Divider"; +import { Heading2 } from "@/components/utils/Text"; +import { conf } from "@/setup/config"; + +function ConfigValue(props: { name: string; children?: ReactNode }) { + return ( + <> +
+

{props.name}

+

{props.children}

+
+ + + ); +} + +export function ConfigValuesPart() { + const normalRouter = conf().NORMAL_ROUTER; + const appVersion = conf().APP_VERSION; + + return ( + <> + Configured values + + {normalRouter ? "Normal routing" : "Hash based routing"} + + v{appVersion} + + ); +} diff --git a/src/pages/parts/admin/TMDBTestPart.tsx b/src/pages/parts/admin/TMDBTestPart.tsx new file mode 100644 index 00000000..33d35144 --- /dev/null +++ b/src/pages/parts/admin/TMDBTestPart.tsx @@ -0,0 +1,93 @@ +import { useState } from "react"; +import { useAsyncFn } from "react-use"; + +import { getMediaDetails } from "@/backend/metadata/tmdb"; +import { TMDBContentTypes } from "@/backend/metadata/types/tmdb"; +import { Button } from "@/components/Button"; +import { Icon, Icons } from "@/components/Icon"; +import { Box } from "@/components/layout/Box"; +import { Spinner } from "@/components/layout/Spinner"; +import { Heading2 } from "@/components/utils/Text"; +import { conf } from "@/setup/config"; + +export function TMDBTestPart() { + const tmdbApiKey = conf().TMDB_READ_API_KEY; + const [status, setStatus] = useState({ + hasTested: false, + success: false, + errorText: "", + }); + + const [testState, runTests] = useAsyncFn(async () => { + setStatus({ + hasTested: false, + success: false, + errorText: "", + }); + + if (tmdbApiKey.length === 0) { + return setStatus({ + hasTested: true, + success: false, + errorText: "TMDB api key is not set", + }); + } + const isJWT = tmdbApiKey.split(".").length > 2; + if (!isJWT) { + return setStatus({ + hasTested: true, + success: false, + errorText: "TMDB api key is not a read only key", + }); + } + + try { + await getMediaDetails("556574", TMDBContentTypes.MOVIE); + } catch (err) { + return setStatus({ + hasTested: true, + success: false, + errorText: + "Failed to call tmdb, double check api key and your internet connection", + }); + } + + return setStatus({ + hasTested: true, + success: true, + errorText: "", + }); + }, [tmdbApiKey, setStatus]); + + return ( + <> + TMDB tests + +
+
+ {!status.hasTested ? ( +

Run the test to validate TMDB

+ ) : status.success ? ( +

+ + TMDB is working as expected +

+ ) : ( + <> +

TMDB is not working

+

{status.errorText}

+ + )} +
+ +
+
+ + ); +} diff --git a/src/pages/parts/admin/WorkerTestPart.tsx b/src/pages/parts/admin/WorkerTestPart.tsx index f687e31f..2b33d61a 100644 --- a/src/pages/parts/admin/WorkerTestPart.tsx +++ b/src/pages/parts/admin/WorkerTestPart.tsx @@ -1,13 +1,13 @@ import classNames from "classnames"; -import { f } from "ofetch/dist/shared/ofetch.441891d5"; -import { useCallback, useMemo, useState } from "react"; +import { useMemo, useState } from "react"; import { useAsyncFn } from "react-use"; import { mwFetch } from "@/backend/helpers/fetch"; import { Button } from "@/components/Button"; import { Icon, Icons } from "@/components/Icon"; import { Box } from "@/components/layout/Box"; -import { Divider } from "@/components/player/internals/ContextMenu/Misc"; +import { Spinner } from "@/components/layout/Spinner"; +import { Divider } from "@/components/utils/Divider"; import { Heading2 } from "@/components/utils/Text"; import { conf } from "@/setup/config"; @@ -53,7 +53,7 @@ export function WorkerTestPart() { { id: string; status: "error" | "success"; error?: Error }[] >([]); - const runTests = useAsyncFn(async () => { + const [testState, runTests] = useAsyncFn(async () => { function updateWorker(id: string, data: (typeof workerState)[number]) { setWorkerState((s) => { return [...s.filter((v) => v.id !== id), data]; @@ -62,6 +62,14 @@ export function WorkerTestPart() { setWorkerState([]); for (const worker of workerList) { try { + if (worker.url.endsWith("/")) { + updateWorker(worker.id, { + id: worker.id, + status: "error", + error: new Error("URL ends with slash"), + }); + continue; + } await mwFetch(worker.url, { query: { destination: "https://postman-echo.com/get", @@ -83,8 +91,8 @@ export function WorkerTestPart() { return ( <> - Worker tests -

15 workers registered

+ Worker tests +

{workerList.length} worker(s) registered

{workerList.map((v, i) => { const s = workerState.find((segment) => segment.id); @@ -105,7 +113,10 @@ export function WorkerTestPart() { })}
- +
diff --git a/src/setup/App.tsx b/src/setup/App.tsx index f571b65a..c09c816e 100644 --- a/src/setup/App.tsx +++ b/src/setup/App.tsx @@ -17,6 +17,7 @@ import { DmcaPage } from "@/pages/Dmca"; import { NotFoundPage } from "@/pages/errors/NotFoundPage"; import { HomePage } from "@/pages/HomePage"; import { PlayerView } from "@/pages/PlayerView"; +import { SettingsPage } from "@/pages/Settings"; import { Layout } from "@/setup/Layout"; import { BookmarkContextProvider } from "@/state/bookmark"; import { SettingsProvider } from "@/state/settings"; @@ -103,6 +104,9 @@ function App() { + {/* Settings page */} + + {/* admin routes */} diff --git a/tailwind.config.js b/tailwind.config.js index 3783226d..dbefe882 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -109,6 +109,21 @@ module.exports = { badgeText: "#5F5F7A" }, + settings: { + sidebar: { + type: { + secondary: "#4B395F", + inactive: "#8D68A9", + icon: "#926CAD", + activated: "#CBA1E8" + } + } + }, + + utils: { + divider: "#353549" + }, + // Error page errors: { card: "#12121B",