mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-20 14:37:43 +01:00
admin page + beginning of settings page
Co-authored-by: Jip Frijlink <JipFr@users.noreply.github.com>
This commit is contained in:
parent
e267482d33
commit
de68438793
8 changed files with 257 additions and 7 deletions
12
src/components/utils/Divider.tsx
Normal file
12
src/components/utils/Divider.tsx
Normal file
|
@ -0,0 +1,12 @@
|
|||
import classNames from "classnames";
|
||||
|
||||
export function Divider(props: { marginClass?: string }) {
|
||||
return (
|
||||
<hr
|
||||
className={classNames(
|
||||
"w-full h-px border-0 bg-utils-divider bg-opacity-50",
|
||||
props.marginClass ?? "my-8"
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
79
src/pages/Settings.tsx
Normal file
79
src/pages/Settings.tsx
Normal file
|
@ -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 (
|
||||
<section>
|
||||
<p className="text-sm font-bold uppercase text-settings-sidebar-type-secondary mb-2">
|
||||
{props.title}
|
||||
</p>
|
||||
{props.children}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function SidebarLink(props: { children: React.ReactNode; icon: Icons }) {
|
||||
return (
|
||||
<div className="w-full px-2 py-1 flex items-center space-x-3">
|
||||
<Icon
|
||||
className="text-2xl text-settings-sidebar-type-icon"
|
||||
icon={props.icon}
|
||||
/>
|
||||
<span>{props.children}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SettingsSidebar() {
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
const hostname = location.hostname;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="sticky top-24 text-settings-sidebar-type-inactive">
|
||||
<SidebarSection title="Settings">
|
||||
<SidebarLink icon={Icons.WAND}>Account</SidebarLink>
|
||||
</SidebarSection>
|
||||
<Divider />
|
||||
<SidebarSection title="App information">
|
||||
<div className="flex justify-between items-center space-x-3">
|
||||
<span>Version</span>
|
||||
<span>{conf().APP_VERSION}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center space-x-3">
|
||||
<span>Domain</span>
|
||||
<span className="text-right">{hostname}</span>
|
||||
</div>
|
||||
</SidebarSection>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SettingsLayout(props: { children: React.ReactNode }) {
|
||||
return (
|
||||
<WideContainer ultraWide>
|
||||
<div className="grid grid-cols-[260px,1fr] gap-12">
|
||||
<SettingsSidebar />
|
||||
{props.children}
|
||||
</div>
|
||||
</WideContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export function SettingsPage() {
|
||||
return (
|
||||
<SubPageLayout>
|
||||
<SettingsLayout>
|
||||
<Heading1>Setting</Heading1>
|
||||
</SettingsLayout>
|
||||
</SubPageLayout>
|
||||
);
|
||||
}
|
|
@ -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() {
|
|||
<Heading1>Admin tools</Heading1>
|
||||
<Paragraph>Useful tools to test out your current deployment</Paragraph>
|
||||
|
||||
<ConfigValuesPart />
|
||||
<WorkerTestPart />
|
||||
<TMDBTestPart />
|
||||
</ThinContainer>
|
||||
</SubPageLayout>
|
||||
);
|
||||
|
|
32
src/pages/parts/admin/ConfigValuesPart.tsx
Normal file
32
src/pages/parts/admin/ConfigValuesPart.tsx
Normal file
|
@ -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 (
|
||||
<>
|
||||
<div className="flex">
|
||||
<p className="flex-1 font-bold text-white">{props.name}</p>
|
||||
<p>{props.children}</p>
|
||||
</div>
|
||||
<Divider marginClass="my-3" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function ConfigValuesPart() {
|
||||
const normalRouter = conf().NORMAL_ROUTER;
|
||||
const appVersion = conf().APP_VERSION;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Heading2 className="mb-8 mt-12">Configured values</Heading2>
|
||||
<ConfigValue name="Routing mode">
|
||||
{normalRouter ? "Normal routing" : "Hash based routing"}
|
||||
</ConfigValue>
|
||||
<ConfigValue name="Application version">v{appVersion}</ConfigValue>
|
||||
</>
|
||||
);
|
||||
}
|
93
src/pages/parts/admin/TMDBTestPart.tsx
Normal file
93
src/pages/parts/admin/TMDBTestPart.tsx
Normal file
|
@ -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 (
|
||||
<>
|
||||
<Heading2 className="mb-8 mt-12">TMDB tests</Heading2>
|
||||
<Box>
|
||||
<div className="flex items-center">
|
||||
<div className="flex-1">
|
||||
{!status.hasTested ? (
|
||||
<p>Run the test to validate TMDB</p>
|
||||
) : status.success ? (
|
||||
<p className="flex items-center">
|
||||
<Icon
|
||||
icon={Icons.CIRCLE_CHECK}
|
||||
className="text-video-scraping-success mr-2"
|
||||
/>
|
||||
TMDB is working as expected
|
||||
</p>
|
||||
) : (
|
||||
<>
|
||||
<p className="text-white font-bold">TMDB is not working</p>
|
||||
<p>{status.errorText}</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<Button theme="purple" onClick={runTests}>
|
||||
{testState.loading ? <Spinner className="text-base mr-2" /> : null}
|
||||
Test TMDB
|
||||
</Button>
|
||||
</div>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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 (
|
||||
<>
|
||||
<Heading2 className="mb-0 mt-12">Worker tests</Heading2>
|
||||
<p className="mb-8 mt-2">15 workers registered</p>
|
||||
<Heading2 className="!mb-0 mt-12">Worker tests</Heading2>
|
||||
<p className="mb-8 mt-2">{workerList.length} worker(s) registered</p>
|
||||
<Box>
|
||||
{workerList.map((v, i) => {
|
||||
const s = workerState.find((segment) => segment.id);
|
||||
|
@ -105,7 +113,10 @@ export function WorkerTestPart() {
|
|||
})}
|
||||
<Divider />
|
||||
<div className="flex justify-end">
|
||||
<Button theme="purple">Run tests</Button>
|
||||
<Button theme="purple" onClick={runTests}>
|
||||
{testState.loading ? <Spinner className="text-base mr-2" /> : null}
|
||||
Test workers
|
||||
</Button>
|
||||
</div>
|
||||
</Box>
|
||||
</>
|
||||
|
|
|
@ -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() {
|
|||
<Route exact path="/faq" component={AboutPage} />
|
||||
<Route exact path="/dmca" component={DmcaPage} />
|
||||
|
||||
{/* Settings page */}
|
||||
<Route exact path="/settings" component={SettingsPage} />
|
||||
|
||||
{/* admin routes */}
|
||||
<Route exact path="/admin" component={AdminPage} />
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in a new issue