mirror of
https://github.com/diced/zipline.git
synced 2026-04-28 10:43:06 -07:00
fix: perf improvements
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import { Response } from '@/lib/api/response';
|
import { Response } from '@/lib/api/response';
|
||||||
import { fetchApi } from '@/lib/fetchApi';
|
import { fetchApi } from '@/lib/fetchApi';
|
||||||
|
import useUser from '@/lib/client/hooks/useUser';
|
||||||
import { useTitle } from '@/lib/client/hooks/useTitle';
|
import { useTitle } from '@/lib/client/hooks/useTitle';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@@ -18,8 +19,8 @@ import {
|
|||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
import { notifications, showNotification } from '@mantine/notifications';
|
import { notifications, showNotification } from '@mantine/notifications';
|
||||||
import { IconLogin, IconPlus, IconUserPlus, IconX } from '@tabler/icons-react';
|
import { IconLogin, IconPlus, IconUserPlus, IconX } from '@tabler/icons-react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
import { Link, Navigate, useLocation, useNavigate } from 'react-router-dom';
|
||||||
import useSWR, { mutate } from 'swr';
|
import useSWR, { mutate } from 'swr';
|
||||||
import GenericError from '../../error/GenericError';
|
import GenericError from '../../error/GenericError';
|
||||||
import { getWebClient } from '@/lib/api/detect';
|
import { getWebClient } from '@/lib/api/detect';
|
||||||
@@ -31,8 +32,6 @@ export function Component() {
|
|||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: config,
|
data: config,
|
||||||
error: configError,
|
error: configError,
|
||||||
@@ -59,6 +58,8 @@ export function Component() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { user, loading: userLoading } = useUser();
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
username: '',
|
username: '',
|
||||||
@@ -74,17 +75,6 @@ export function Component() {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
(async () => {
|
|
||||||
const res = await fetch('/api/user');
|
|
||||||
if (res.ok) {
|
|
||||||
navigate('/dashboard');
|
|
||||||
} else {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!config) return;
|
if (!config) return;
|
||||||
|
|
||||||
@@ -138,7 +128,11 @@ export function Component() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading || configLoading) return <LoadingOverlay visible />;
|
if (userLoading || configLoading) return <LoadingOverlay visible />;
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
return <Navigate to='/dashboard' replace />;
|
||||||
|
}
|
||||||
|
|
||||||
if (!config || configError) {
|
if (!config || configError) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,10 +1,32 @@
|
|||||||
import type { File as DbFile } from '@/lib/db/models/file';
|
import type { File as DbFile } from '@/lib/db/models/file';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import useSWR from 'swr';
|
||||||
import { isDbFile } from './useFileUrls';
|
import { isDbFile } from './useFileUrls';
|
||||||
|
|
||||||
const MAX_BYTES = 1 * 1024 * 1024;
|
const MAX_BYTES = 1 * 1024 * 1024;
|
||||||
const FILE_BIG = '\n...\nThe file is too big to display click the download icon to view/download it.';
|
const FILE_BIG = '\n...\nThe file is too big to display click the download icon to view/download it.';
|
||||||
|
|
||||||
|
async function readBlobText(file: File) {
|
||||||
|
const raw = await new Promise<string>((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onerror = () => reject(new Error('Failed to read file'));
|
||||||
|
reader.onload = () => resolve((reader.result ?? '') as string);
|
||||||
|
reader.readAsText(file);
|
||||||
|
});
|
||||||
|
|
||||||
|
return raw.length > MAX_BYTES ? raw.slice(0, MAX_BYTES) + FILE_BIG : raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readText(fileUrl: string) {
|
||||||
|
const res = await fetch(fileUrl, {
|
||||||
|
headers: {
|
||||||
|
Range: `bytes=0-${MAX_BYTES}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) throw new Error('Failed to fetch file');
|
||||||
|
return await res.text();
|
||||||
|
}
|
||||||
|
|
||||||
export default function useFileContent({
|
export default function useFileContent({
|
||||||
enabled,
|
enabled,
|
||||||
file,
|
file,
|
||||||
@@ -14,41 +36,32 @@ export default function useFileContent({
|
|||||||
file: DbFile | File;
|
file: DbFile | File;
|
||||||
fileUrl: string;
|
fileUrl: string;
|
||||||
}) {
|
}) {
|
||||||
const [content, setContent] = useState('');
|
const { data, error } = useSWR<string>(
|
||||||
|
() => {
|
||||||
|
if (!enabled) return null;
|
||||||
|
|
||||||
const loadText = useCallback(async () => {
|
if (isDbFile(file)) return ['dbfile', file.id] as const;
|
||||||
try {
|
|
||||||
if (!isDbFile(file)) {
|
const f = file as File;
|
||||||
const reader = new FileReader();
|
return ['blobfile', f.name] as const;
|
||||||
reader.onload = () => {
|
},
|
||||||
const raw = reader.result as string;
|
async () => {
|
||||||
setContent(raw.length > MAX_BYTES ? raw.slice(0, MAX_BYTES) + FILE_BIG : raw);
|
if (!isDbFile(file)) return readBlobText(file as File);
|
||||||
};
|
|
||||||
reader.readAsText(file as File);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (file.size > MAX_BYTES) {
|
if (file.size > MAX_BYTES) {
|
||||||
const res = await fetch(fileUrl, { headers: { Range: `bytes=0-${MAX_BYTES}` } });
|
const text = await readText(fileUrl);
|
||||||
if (!res.ok) throw new Error('Failed to fetch file');
|
return text + FILE_BIG;
|
||||||
const text = await res.text();
|
|
||||||
setContent(text + FILE_BIG);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await fetch(fileUrl);
|
return readText(fileUrl);
|
||||||
if (!res.ok) throw new Error('Failed to fetch file');
|
},
|
||||||
const text = await res.text();
|
{
|
||||||
setContent(text);
|
revalidateOnFocus: false,
|
||||||
} catch {
|
shouldRetryOnError: false,
|
||||||
setContent('Error loading file.');
|
},
|
||||||
}
|
);
|
||||||
}, [file, fileUrl]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
if (error) return 'Error loading file.';
|
||||||
if (!enabled) return;
|
|
||||||
loadText();
|
|
||||||
}, [enabled, loadText]);
|
|
||||||
|
|
||||||
return content;
|
return data ?? '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ export default function DashboardServerSettings() {
|
|||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { data, isLoading } = useSWR<Response['/api/server/settings']>('/api/server/settings');
|
const { data } = useSWR<Response['/api/server/settings']>('/api/server/settings');
|
||||||
const [opened, { toggle }] = useDisclosure(false);
|
const [opened, { toggle }] = useDisclosure(false);
|
||||||
|
|
||||||
const toSettingSection = useCallback((settingKey: string) => {
|
const toSettingSection = useCallback((settingKey: string) => {
|
||||||
@@ -328,7 +328,7 @@ export default function DashboardServerSettings() {
|
|||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SettingsComponent swr={{ data, isLoading }} />
|
<SettingsComponent />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -1,27 +1,34 @@
|
|||||||
import { Response } from '@/lib/api/response';
|
import type { Response } from '@/lib/api/response';
|
||||||
import { Button, LoadingOverlay, Stack, Switch, TextInput } from '@mantine/core';
|
import { Button, LoadingOverlay, Stack, Switch, TextInput } from '@mantine/core';
|
||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
import { IconDeviceFloppy } from '@tabler/icons-react';
|
import { IconDeviceFloppy } from '@tabler/icons-react';
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { settingsOnSubmit } from '../settingsOnSubmit';
|
import { settingsOnSubmit } from '../settingsOnSubmit';
|
||||||
|
import useServerSettings from '../useServerSettings';
|
||||||
|
|
||||||
export default function Chunks({
|
export default function Chunks() {
|
||||||
swr: { data, isLoading },
|
const { data, isLoading } = useServerSettings();
|
||||||
}: {
|
|
||||||
swr: { data: Response['/api/server/settings'] | undefined; isLoading: boolean };
|
return (
|
||||||
}) {
|
<>
|
||||||
|
<LoadingOverlay visible={isLoading} bdrs='md' />
|
||||||
|
{data ? <Form data={data} isLoading={isLoading} /> : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Form({ data, isLoading }: { data: Response['/api/server/settings']; isLoading: boolean }) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
chunksEnabled: true,
|
chunksEnabled: data.settings.chunksEnabled,
|
||||||
chunksMax: '95mb',
|
chunksMax: data.settings.chunksMax,
|
||||||
chunksSize: '25mb',
|
chunksSize: data.settings.chunksSize,
|
||||||
},
|
},
|
||||||
enhanceGetInputProps: (payload: any): object => ({
|
enhanceGetInputProps: (payload: any): object => ({
|
||||||
disabled:
|
disabled:
|
||||||
data?.tampered?.includes(payload.field) ||
|
data.tampered.includes(payload.field) ||
|
||||||
(payload.field !== 'chunksEnabled' && !form.values.chunksEnabled) ||
|
(payload.field !== 'chunksEnabled' && !form.values.chunksEnabled) ||
|
||||||
false,
|
false,
|
||||||
}),
|
}),
|
||||||
@@ -29,20 +36,7 @@ export default function Chunks({
|
|||||||
|
|
||||||
const onSubmit = settingsOnSubmit(navigate, form);
|
const onSubmit = settingsOnSubmit(navigate, form);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!data) return;
|
|
||||||
|
|
||||||
form.setValues({
|
|
||||||
chunksEnabled: data.settings.chunksEnabled ?? true,
|
|
||||||
chunksMax: data.settings.chunksMax ?? '',
|
|
||||||
chunksSize: data.settings.chunksSize ?? '',
|
|
||||||
});
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<LoadingOverlay visible={isLoading} bdrs='md' />
|
|
||||||
|
|
||||||
<form onSubmit={form.onSubmit(onSubmit)}>
|
<form onSubmit={form.onSubmit(onSubmit)}>
|
||||||
<Stack gap='lg'>
|
<Stack gap='lg'>
|
||||||
<Switch
|
<Switch
|
||||||
@@ -72,6 +66,5 @@ export default function Chunks({
|
|||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,34 @@
|
|||||||
import { Response } from '@/lib/api/response';
|
import type { Response } from '@/lib/api/response';
|
||||||
import { Button, LoadingOverlay, Stack, Switch, TextInput } from '@mantine/core';
|
import { Button, LoadingOverlay, Stack, Switch, TextInput } from '@mantine/core';
|
||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
import { IconDeviceFloppy } from '@tabler/icons-react';
|
import { IconDeviceFloppy } from '@tabler/icons-react';
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { settingsOnSubmit } from '../settingsOnSubmit';
|
import { settingsOnSubmit } from '../settingsOnSubmit';
|
||||||
|
import useServerSettings from '../useServerSettings';
|
||||||
|
|
||||||
export default function Core({
|
export default function Core() {
|
||||||
swr: { data, isLoading },
|
const { data, isLoading } = useServerSettings();
|
||||||
}: {
|
|
||||||
swr: { data: Response['/api/server/settings'] | undefined; isLoading: boolean };
|
return (
|
||||||
}) {
|
<>
|
||||||
|
<LoadingOverlay visible={isLoading} />
|
||||||
|
{data ? <Form data={data} isLoading={isLoading} /> : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Form({ data, isLoading }: { data: Response['/api/server/settings']; isLoading: boolean }) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const form = useForm<{
|
const form = useForm({
|
||||||
coreReturnHttpsUrls: boolean;
|
|
||||||
coreDefaultDomain: string | null | undefined;
|
|
||||||
coreTempDirectory: string;
|
|
||||||
coreTrustProxy: boolean;
|
|
||||||
}>({
|
|
||||||
initialValues: {
|
initialValues: {
|
||||||
coreReturnHttpsUrls: false,
|
coreReturnHttpsUrls: data.settings.coreReturnHttpsUrls,
|
||||||
coreDefaultDomain: '',
|
coreDefaultDomain: data.settings.coreDefaultDomain,
|
||||||
coreTempDirectory: '/tmp/zipline',
|
coreTempDirectory: data.settings.coreTempDirectory,
|
||||||
coreTrustProxy: false,
|
coreTrustProxy: data.settings.coreTrustProxy,
|
||||||
},
|
},
|
||||||
enhanceGetInputProps: (payload) => ({
|
enhanceGetInputProps: (payload) => ({
|
||||||
disabled: data?.tampered?.includes(payload.field) || false,
|
disabled: data.tampered.includes(payload.field) || false,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -40,21 +42,7 @@ export default function Core({
|
|||||||
return settingsOnSubmit(navigate, form)(values);
|
return settingsOnSubmit(navigate, form)(values);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!data) return;
|
|
||||||
|
|
||||||
form.setValues({
|
|
||||||
coreReturnHttpsUrls: data.settings.coreReturnHttpsUrls ?? false,
|
|
||||||
coreDefaultDomain: data.settings.coreDefaultDomain ?? '',
|
|
||||||
coreTempDirectory: data.settings.coreTempDirectory ?? '/tmp/zipline',
|
|
||||||
coreTrustProxy: data.settings.coreTrustProxy ?? false,
|
|
||||||
});
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<LoadingOverlay visible={isLoading} />
|
|
||||||
|
|
||||||
<form onSubmit={form.onSubmit(onSubmit)}>
|
<form onSubmit={form.onSubmit(onSubmit)}>
|
||||||
<Stack gap='lg'>
|
<Stack gap='lg'>
|
||||||
<Switch
|
<Switch
|
||||||
@@ -89,6 +77,5 @@ export default function Core({
|
|||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Response } from '@/lib/api/response';
|
import type { Response } from '@/lib/api/response';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Collapse,
|
Collapse,
|
||||||
@@ -13,24 +13,31 @@ import {
|
|||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
import { IconDeviceFloppy } from '@tabler/icons-react';
|
import { IconDeviceFloppy } from '@tabler/icons-react';
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { settingsOnSubmit } from '../settingsOnSubmit';
|
import { settingsOnSubmit } from '../settingsOnSubmit';
|
||||||
|
import useServerSettings from '../useServerSettings';
|
||||||
|
|
||||||
type DiscordEmbed = Record<string, any>;
|
type DiscordEmbed = Record<string, any>;
|
||||||
|
|
||||||
export default function Discord({
|
export default function Discord() {
|
||||||
swr: { data, isLoading },
|
const { data, isLoading } = useServerSettings();
|
||||||
}: {
|
|
||||||
swr: { data: Response['/api/server/settings'] | undefined; isLoading: boolean };
|
return (
|
||||||
}) {
|
<>
|
||||||
|
<LoadingOverlay visible={isLoading} />
|
||||||
|
{data ? <Form data={data} isLoading={isLoading} /> : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Form({ data, isLoading }: { data: Response['/api/server/settings']; isLoading: boolean }) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const formMain = useForm({
|
const formMain = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
discordWebhookUrl: '',
|
discordWebhookUrl: data.settings.discordWebhookUrl,
|
||||||
discordUsername: '',
|
discordUsername: data.settings.discordUsername,
|
||||||
discordAvatarUrl: '',
|
discordAvatarUrl: data.settings.discordAvatarUrl,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -49,42 +56,46 @@ export default function Discord({
|
|||||||
|
|
||||||
const formOnUpload = useForm({
|
const formOnUpload = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
discordOnUploadWebhookUrl: '',
|
discordOnUploadWebhookUrl: data.settings.discordOnUploadWebhookUrl,
|
||||||
discordOnUploadUsername: '',
|
discordOnUploadUsername: data.settings.discordOnUploadUsername,
|
||||||
discordOnUploadAvatarUrl: '',
|
discordOnUploadAvatarUrl: data.settings.discordOnUploadAvatarUrl,
|
||||||
|
|
||||||
discordOnUploadContent: '',
|
discordOnUploadContent: data.settings.discordOnUploadContent,
|
||||||
|
|
||||||
discordOnUploadEmbed: false,
|
discordOnUploadEmbed: Boolean(data.settings.discordOnUploadEmbed),
|
||||||
discordOnUploadEmbedTitle: '',
|
discordOnUploadEmbedTitle: (data.settings.discordOnUploadEmbed as DiscordEmbed | null)?.title || '',
|
||||||
discordOnUploadEmbedDescription: '',
|
discordOnUploadEmbedDescription:
|
||||||
discordOnUploadEmbedFooter: '',
|
(data.settings.discordOnUploadEmbed as DiscordEmbed | null)?.description || '',
|
||||||
discordOnUploadEmbedColor: '',
|
discordOnUploadEmbedFooter: (data.settings.discordOnUploadEmbed as DiscordEmbed | null)?.footer || '',
|
||||||
discordOnUploadEmbedThumbnail: false,
|
discordOnUploadEmbedColor: (data.settings.discordOnUploadEmbed as DiscordEmbed | null)?.color || '',
|
||||||
discordOnUploadEmbedImageOrVideo: false,
|
discordOnUploadEmbedThumbnail: !!(data.settings.discordOnUploadEmbed as DiscordEmbed | null)?.thumbnail,
|
||||||
discordOnUploadEmbedTimestamp: false,
|
discordOnUploadEmbedImageOrVideo: !!(data.settings.discordOnUploadEmbed as DiscordEmbed | null)
|
||||||
discordOnUploadEmbedUrl: false,
|
?.imageOrVideo,
|
||||||
|
discordOnUploadEmbedTimestamp: !!(data.settings.discordOnUploadEmbed as DiscordEmbed | null)?.timestamp,
|
||||||
|
discordOnUploadEmbedUrl: !!(data.settings.discordOnUploadEmbed as DiscordEmbed | null)?.url,
|
||||||
},
|
},
|
||||||
enhanceGetInputProps: (payload) => ({
|
enhanceGetInputProps: (payload) => ({
|
||||||
disabled: data?.tampered?.includes(payload.field) || false,
|
disabled: data.tampered.includes(payload.field) || false,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const formOnShorten = useForm({
|
const formOnShorten = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
discordOnShortenWebhookUrl: '',
|
discordOnShortenWebhookUrl: data.settings.discordOnShortenWebhookUrl,
|
||||||
discordOnShortenUsername: '',
|
discordOnShortenUsername: data.settings.discordOnShortenUsername,
|
||||||
discordOnShortenAvatarUrl: '',
|
discordOnShortenAvatarUrl: data.settings.discordOnShortenAvatarUrl,
|
||||||
|
|
||||||
discordOnShortenContent: '',
|
discordOnShortenContent: data.settings.discordOnShortenContent,
|
||||||
|
|
||||||
discordOnShortenEmbed: false,
|
discordOnShortenEmbed: Boolean(data.settings.discordOnShortenEmbed),
|
||||||
discordOnShortenEmbedTitle: '',
|
discordOnShortenEmbedTitle: (data.settings.discordOnShortenEmbed as DiscordEmbed | null)?.title || '',
|
||||||
discordOnShortenEmbedDescription: '',
|
discordOnShortenEmbedDescription:
|
||||||
discordOnShortenEmbedFooter: '',
|
(data.settings.discordOnShortenEmbed as DiscordEmbed | null)?.description || '',
|
||||||
discordOnShortenEmbedColor: '',
|
discordOnShortenEmbedFooter: (data.settings.discordOnShortenEmbed as DiscordEmbed | null)?.footer || '',
|
||||||
discordOnShortenEmbedTimestamp: false,
|
discordOnShortenEmbedColor: (data.settings.discordOnShortenEmbed as DiscordEmbed | null)?.color || '',
|
||||||
discordOnShortenEmbedUrl: false,
|
discordOnShortenEmbedTimestamp: !!(data.settings.discordOnShortenEmbed as DiscordEmbed | null)
|
||||||
|
?.timestamp,
|
||||||
|
discordOnShortenEmbedUrl: !!(data.settings.discordOnShortenEmbed as DiscordEmbed | null)?.url,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -123,56 +134,8 @@ export default function Discord({
|
|||||||
return settingsOnSubmit(navigate, type === 'upload' ? formOnUpload : formOnShorten)(sendValues);
|
return settingsOnSubmit(navigate, type === 'upload' ? formOnUpload : formOnShorten)(sendValues);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!data) return;
|
|
||||||
|
|
||||||
formMain.setValues({
|
|
||||||
discordWebhookUrl: data.settings.discordWebhookUrl ?? '',
|
|
||||||
discordUsername: data.settings.discordUsername ?? '',
|
|
||||||
discordAvatarUrl: data.settings.discordAvatarUrl ?? '',
|
|
||||||
});
|
|
||||||
|
|
||||||
formOnUpload.setValues({
|
|
||||||
discordOnUploadWebhookUrl: data.settings.discordOnUploadWebhookUrl ?? '',
|
|
||||||
discordOnUploadUsername: data.settings.discordOnUploadUsername ?? '',
|
|
||||||
discordOnUploadAvatarUrl: data.settings.discordOnUploadAvatarUrl ?? '',
|
|
||||||
|
|
||||||
discordOnUploadContent: data.settings.discordOnUploadContent ?? '',
|
|
||||||
discordOnUploadEmbed: data.settings.discordOnUploadEmbed ? true : false,
|
|
||||||
discordOnUploadEmbedTitle: (data.settings.discordOnUploadEmbed as DiscordEmbed)?.title ?? '',
|
|
||||||
discordOnUploadEmbedDescription:
|
|
||||||
(data.settings.discordOnUploadEmbed as DiscordEmbed)?.description ?? '',
|
|
||||||
discordOnUploadEmbedFooter: (data.settings.discordOnUploadEmbed as DiscordEmbed)?.footer ?? '',
|
|
||||||
discordOnUploadEmbedColor: (data.settings.discordOnUploadEmbed as DiscordEmbed)?.color ?? '',
|
|
||||||
discordOnUploadEmbedThumbnail: (data.settings.discordOnUploadEmbed as DiscordEmbed)?.thumbnail ?? false,
|
|
||||||
discordOnUploadEmbedImageOrVideo:
|
|
||||||
(data.settings.discordOnUploadEmbed as DiscordEmbed)?.imageOrVideo ?? false,
|
|
||||||
discordOnUploadEmbedTimestamp: (data.settings.discordOnUploadEmbed as DiscordEmbed)?.timestamp ?? false,
|
|
||||||
discordOnUploadEmbedUrl: (data.settings.discordOnUploadEmbed as DiscordEmbed)?.url ?? false,
|
|
||||||
});
|
|
||||||
|
|
||||||
formOnShorten.setValues({
|
|
||||||
discordOnShortenWebhookUrl: data.settings.discordOnShortenWebhookUrl ?? '',
|
|
||||||
discordOnShortenUsername: data.settings.discordOnShortenUsername ?? '',
|
|
||||||
discordOnShortenAvatarUrl: data.settings.discordOnShortenAvatarUrl ?? '',
|
|
||||||
|
|
||||||
discordOnShortenContent: data.settings.discordOnShortenContent ?? '',
|
|
||||||
discordOnShortenEmbed: data.settings.discordOnShortenEmbed ? true : false,
|
|
||||||
discordOnShortenEmbedTitle: (data.settings.discordOnShortenEmbed as DiscordEmbed)?.title ?? '',
|
|
||||||
discordOnShortenEmbedDescription:
|
|
||||||
(data.settings.discordOnShortenEmbed as DiscordEmbed)?.description ?? '',
|
|
||||||
discordOnShortenEmbedFooter: (data.settings.discordOnShortenEmbed as DiscordEmbed)?.footer ?? '',
|
|
||||||
discordOnShortenEmbedColor: (data.settings.discordOnShortenEmbed as DiscordEmbed)?.color ?? '',
|
|
||||||
discordOnShortenEmbedTimestamp:
|
|
||||||
(data.settings.discordOnShortenEmbed as DiscordEmbed)?.timestamp ?? false,
|
|
||||||
discordOnShortenEmbedUrl: (data.settings.discordOnShortenEmbed as DiscordEmbed)?.url ?? false,
|
|
||||||
});
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<LoadingOverlay visible={isLoading} />
|
|
||||||
|
|
||||||
<form onSubmit={formMain.onSubmit(onSubmitMain)}>
|
<form onSubmit={formMain.onSubmit(onSubmitMain)}>
|
||||||
<TextInput
|
<TextInput
|
||||||
label='Webhook URL'
|
label='Webhook URL'
|
||||||
|
|||||||
@@ -1,19 +1,24 @@
|
|||||||
import { Response } from '@/lib/api/response';
|
import type { Response } from '@/lib/api/response';
|
||||||
import { ActionIcon, LoadingOverlay, Paper, Table, Text, TextInput } from '@mantine/core';
|
import { ActionIcon, LoadingOverlay, Paper, Table, Text, TextInput } from '@mantine/core';
|
||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
import { IconPlus, IconTrash } from '@tabler/icons-react';
|
import { IconPlus, IconTrash } from '@tabler/icons-react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { settingsOnSubmit } from '../settingsOnSubmit';
|
import { settingsOnSubmit } from '../settingsOnSubmit';
|
||||||
|
import useServerSettings from '../useServerSettings';
|
||||||
|
|
||||||
export default function Domains({
|
export default function Domains() {
|
||||||
swr: { data, isLoading },
|
const { data, isLoading } = useServerSettings();
|
||||||
}: {
|
|
||||||
swr: {
|
return (
|
||||||
data: Response['/api/server/settings'] | undefined;
|
<>
|
||||||
isLoading: boolean;
|
<LoadingOverlay visible={isLoading} />
|
||||||
};
|
{data ? <Form data={data} /> : null}
|
||||||
}) {
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Form({ data }: { data: Response['/api/server/settings'] }) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
@@ -24,7 +29,7 @@ export default function Domains({
|
|||||||
|
|
||||||
const submitSettings = settingsOnSubmit(navigate, form);
|
const submitSettings = settingsOnSubmit(navigate, form);
|
||||||
|
|
||||||
const domains = Array.isArray(data?.settings.domains) ? data!.settings.domains.map(String) : [];
|
const domains = data.settings.domains.map(String);
|
||||||
|
|
||||||
async function updateDomains(nextDomains: string[]) {
|
async function updateDomains(nextDomains: string[]) {
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
@@ -56,7 +61,7 @@ export default function Domains({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<LoadingOverlay visible={isLoading || submitting} />
|
<LoadingOverlay visible={submitting} />
|
||||||
|
|
||||||
<form onSubmit={addDomain}>
|
<form onSubmit={addDomain}>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Response } from '@/lib/api/response';
|
import type { Response } from '@/lib/api/response';
|
||||||
import {
|
import {
|
||||||
Anchor,
|
Anchor,
|
||||||
Button,
|
Button,
|
||||||
@@ -13,68 +13,53 @@ import {
|
|||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
import { IconDeviceFloppy } from '@tabler/icons-react';
|
import { IconDeviceFloppy } from '@tabler/icons-react';
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { settingsOnSubmit } from '../settingsOnSubmit';
|
import { settingsOnSubmit } from '../settingsOnSubmit';
|
||||||
|
import useServerSettings from '../useServerSettings';
|
||||||
|
|
||||||
export default function Features({
|
export default function Features() {
|
||||||
swr: { data, isLoading },
|
const { data, isLoading } = useServerSettings();
|
||||||
}: {
|
|
||||||
swr: { data: Response['/api/server/settings'] | undefined; isLoading: boolean };
|
return (
|
||||||
}) {
|
<>
|
||||||
|
<LoadingOverlay visible={isLoading} />
|
||||||
|
{data ? <Form data={data} isLoading={isLoading} /> : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Form({ data, isLoading }: { data: Response['/api/server/settings']; isLoading: boolean }) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
featuresImageCompression: true,
|
featuresImageCompression: data.settings.featuresImageCompression,
|
||||||
featuresRobotsTxt: true,
|
featuresRobotsTxt: data.settings.featuresRobotsTxt,
|
||||||
featuresHealthcheck: true,
|
featuresHealthcheck: data.settings.featuresHealthcheck,
|
||||||
featuresUserRegistration: false,
|
featuresUserRegistration: data.settings.featuresUserRegistration,
|
||||||
featuresOauthRegistration: true,
|
featuresOauthRegistration: data.settings.featuresOauthRegistration,
|
||||||
featuresDeleteOnMaxViews: true,
|
featuresDeleteOnMaxViews: data.settings.featuresDeleteOnMaxViews,
|
||||||
featuresThumbnailsEnabled: true,
|
|
||||||
featuresThumbnailsNumberThreads: 4,
|
featuresThumbnailsEnabled: data.settings.featuresThumbnailsEnabled,
|
||||||
featuresThumbnailsFormat: 'jpg',
|
featuresThumbnailsNumberThreads: data.settings.featuresThumbnailsNumberThreads,
|
||||||
featuresThumbnailsInstantaneous: false,
|
featuresThumbnailsFormat: data.settings.featuresThumbnailsFormat,
|
||||||
featuresMetricsEnabled: true,
|
featuresThumbnailsInstantaneous: data.settings.featuresThumbnailsInstantaneous,
|
||||||
featuresMetricsAdminOnly: false,
|
|
||||||
featuresMetricsShowUserSpecific: true,
|
featuresMetricsEnabled: data.settings.featuresMetricsEnabled,
|
||||||
featuresVersionChecking: true,
|
featuresMetricsAdminOnly: data.settings.featuresMetricsAdminOnly,
|
||||||
featuresVersionAPI: 'https://zipline-version.diced.sh/',
|
featuresMetricsShowUserSpecific: data.settings.featuresMetricsShowUserSpecific,
|
||||||
|
|
||||||
|
featuresVersionChecking: data.settings.featuresVersionChecking,
|
||||||
|
featuresVersionAPI: data.settings.featuresVersionAPI,
|
||||||
},
|
},
|
||||||
enhanceGetInputProps: (payload) => ({
|
enhanceGetInputProps: (payload) => ({
|
||||||
disabled: data?.tampered?.includes(payload.field) || false,
|
disabled: data.tampered.includes(payload.field) || false,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = settingsOnSubmit(navigate, form);
|
const onSubmit = settingsOnSubmit(navigate, form);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!data) return;
|
|
||||||
|
|
||||||
form.setValues({
|
|
||||||
featuresImageCompression: data.settings.featuresImageCompression ?? true,
|
|
||||||
featuresRobotsTxt: data.settings.featuresRobotsTxt ?? true,
|
|
||||||
featuresHealthcheck: data.settings.featuresHealthcheck ?? true,
|
|
||||||
featuresUserRegistration: data.settings.featuresUserRegistration ?? false,
|
|
||||||
featuresOauthRegistration: data.settings.featuresOauthRegistration ?? true,
|
|
||||||
featuresDeleteOnMaxViews: data.settings.featuresDeleteOnMaxViews ?? true,
|
|
||||||
featuresThumbnailsEnabled: data.settings.featuresThumbnailsEnabled ?? true,
|
|
||||||
featuresThumbnailsNumberThreads: data.settings.featuresThumbnailsNumberThreads ?? 4,
|
|
||||||
featuresThumbnailsFormat: data.settings.featuresThumbnailsFormat ?? 'jpg',
|
|
||||||
featuresThumbnailsInstantaneous: data.settings.featuresThumbnailsInstantaneous ?? false,
|
|
||||||
featuresMetricsEnabled: data.settings.featuresMetricsEnabled ?? true,
|
|
||||||
featuresMetricsAdminOnly: data.settings.featuresMetricsAdminOnly ?? false,
|
|
||||||
featuresMetricsShowUserSpecific: data.settings.featuresMetricsShowUserSpecific ?? true,
|
|
||||||
featuresVersionChecking: data.settings.featuresVersionChecking ?? true,
|
|
||||||
featuresVersionAPI: data.settings.featuresVersionAPI ?? 'https://zipline-version.diced.sh/',
|
|
||||||
});
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<LoadingOverlay visible={isLoading} />
|
|
||||||
|
|
||||||
<form onSubmit={form.onSubmit(onSubmit)}>
|
<form onSubmit={form.onSubmit(onSubmit)}>
|
||||||
<Stack gap='lg'>
|
<Stack gap='lg'>
|
||||||
<Switch
|
<Switch
|
||||||
@@ -198,6 +183,5 @@ export default function Features({
|
|||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,52 +1,44 @@
|
|||||||
import { Response } from '@/lib/api/response';
|
import type { Response } from '@/lib/api/response';
|
||||||
import { Button, LoadingOverlay, NumberInput, Select, Stack, Switch, TextInput } from '@mantine/core';
|
import { Button, LoadingOverlay, NumberInput, Select, Stack, Switch, TextInput } from '@mantine/core';
|
||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
import { IconDeviceFloppy } from '@tabler/icons-react';
|
import { IconDeviceFloppy } from '@tabler/icons-react';
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { settingsOnSubmit } from '../settingsOnSubmit';
|
import { settingsOnSubmit } from '../settingsOnSubmit';
|
||||||
|
import useServerSettings from '../useServerSettings';
|
||||||
|
|
||||||
export default function Files({
|
export default function Files() {
|
||||||
swr: { data, isLoading },
|
const { data, isLoading } = useServerSettings();
|
||||||
}: {
|
|
||||||
swr: { data: Response['/api/server/settings'] | undefined; isLoading: boolean };
|
return (
|
||||||
}) {
|
<>
|
||||||
|
<LoadingOverlay visible={isLoading} />
|
||||||
|
{data ? <Form data={data} isLoading={isLoading} /> : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Form({ data, isLoading }: { data: Response['/api/server/settings']; isLoading: boolean }) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const form = useForm<{
|
const form = useForm({
|
||||||
filesRoute: string;
|
|
||||||
filesLength: number;
|
|
||||||
filesDefaultFormat: string;
|
|
||||||
filesDisabledExtensions: string;
|
|
||||||
filesMaxFileSize: string;
|
|
||||||
filesDefaultExpiration: string | null;
|
|
||||||
filesMaxExpiration: string | null;
|
|
||||||
filesAssumeMimetypes: boolean;
|
|
||||||
filesDefaultDateFormat: string;
|
|
||||||
filesRemoveGpsMetadata: boolean;
|
|
||||||
filesRandomWordsNumAdjectives: number;
|
|
||||||
filesRandomWordsSeparator: string;
|
|
||||||
filesDefaultCompressionFormat: string;
|
|
||||||
filesMaxFilesPerUpload: number;
|
|
||||||
}>({
|
|
||||||
initialValues: {
|
initialValues: {
|
||||||
filesRoute: '/u',
|
filesRoute: data.settings.filesRoute,
|
||||||
filesLength: 6,
|
filesLength: data.settings.filesLength,
|
||||||
filesDefaultFormat: 'random',
|
filesDefaultFormat: data.settings.filesDefaultFormat,
|
||||||
filesDisabledExtensions: '',
|
filesDisabledExtensions: data.settings.filesDisabledExtensions.join(', '),
|
||||||
filesMaxFileSize: '100mb',
|
filesMaxFileSize: data.settings.filesMaxFileSize,
|
||||||
filesDefaultExpiration: '',
|
filesDefaultExpiration: data.settings.filesDefaultExpiration,
|
||||||
filesMaxExpiration: '',
|
filesMaxExpiration: data.settings.filesMaxExpiration,
|
||||||
filesAssumeMimetypes: false,
|
filesAssumeMimetypes: data.settings.filesAssumeMimetypes,
|
||||||
filesDefaultDateFormat: 'YYYY-MM-DD_HH:mm:ss',
|
filesDefaultDateFormat: data.settings.filesDefaultDateFormat,
|
||||||
filesRemoveGpsMetadata: false,
|
filesRemoveGpsMetadata: data.settings.filesRemoveGpsMetadata,
|
||||||
filesRandomWordsNumAdjectives: 3,
|
filesRandomWordsNumAdjectives: data.settings.filesRandomWordsNumAdjectives,
|
||||||
filesRandomWordsSeparator: '-',
|
filesRandomWordsSeparator: data.settings.filesRandomWordsSeparator,
|
||||||
filesDefaultCompressionFormat: 'jpg',
|
filesDefaultCompressionFormat: data.settings.filesDefaultCompressionFormat,
|
||||||
filesMaxFilesPerUpload: 1000,
|
filesMaxFilesPerUpload: data.settings.filesMaxFilesPerUpload,
|
||||||
},
|
},
|
||||||
enhanceGetInputProps: (payload) => ({
|
enhanceGetInputProps: (payload) => ({
|
||||||
disabled: data?.tampered?.includes(payload.field) || false,
|
disabled: data.tampered.includes(payload.field) || false,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -85,31 +77,7 @@ export default function Files({
|
|||||||
return settingsOnSubmit(navigate, form)(values);
|
return settingsOnSubmit(navigate, form)(values);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!data) return;
|
|
||||||
|
|
||||||
form.setValues({
|
|
||||||
filesRoute: data.settings.filesRoute ?? '/u',
|
|
||||||
filesLength: data.settings.filesLength ?? 6,
|
|
||||||
filesDefaultFormat: data.settings.filesDefaultFormat ?? 'random',
|
|
||||||
filesDisabledExtensions: data.settings.filesDisabledExtensions.join(', ') ?? '',
|
|
||||||
filesMaxFileSize: data.settings.filesMaxFileSize ?? '100mb',
|
|
||||||
filesDefaultExpiration: data.settings.filesDefaultExpiration ?? '',
|
|
||||||
filesMaxExpiration: data.settings.filesMaxExpiration ?? '',
|
|
||||||
filesAssumeMimetypes: data.settings.filesAssumeMimetypes ?? false,
|
|
||||||
filesDefaultDateFormat: data.settings.filesDefaultDateFormat ?? 'YYYY-MM-DD_HH:mm:ss',
|
|
||||||
filesRemoveGpsMetadata: data.settings.filesRemoveGpsMetadata ?? false,
|
|
||||||
filesRandomWordsNumAdjectives: data.settings.filesRandomWordsNumAdjectives ?? 3,
|
|
||||||
filesRandomWordsSeparator: data.settings.filesRandomWordsSeparator ?? '-',
|
|
||||||
filesDefaultCompressionFormat: data.settings.filesDefaultCompressionFormat ?? 'jpg',
|
|
||||||
filesMaxFilesPerUpload: data.settings.filesMaxFilesPerUpload ?? 1000,
|
|
||||||
});
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<LoadingOverlay visible={isLoading} />
|
|
||||||
|
|
||||||
<form onSubmit={form.onSubmit(onSubmit)}>
|
<form onSubmit={form.onSubmit(onSubmit)}>
|
||||||
<Stack gap='lg'>
|
<Stack gap='lg'>
|
||||||
<Switch
|
<Switch
|
||||||
@@ -222,6 +190,5 @@ export default function Files({
|
|||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,32 @@
|
|||||||
import { Response } from '@/lib/api/response';
|
import type { Response } from '@/lib/api/response';
|
||||||
import { Button, LoadingOverlay, Stack, TextInput } from '@mantine/core';
|
import { Button, LoadingOverlay, Stack, TextInput } from '@mantine/core';
|
||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
import { IconDeviceFloppy } from '@tabler/icons-react';
|
import { IconDeviceFloppy } from '@tabler/icons-react';
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { settingsOnSubmit } from '../settingsOnSubmit';
|
import { settingsOnSubmit } from '../settingsOnSubmit';
|
||||||
|
import useServerSettings from '../useServerSettings';
|
||||||
|
|
||||||
export default function HttpWebhook({
|
export default function HttpWebhook() {
|
||||||
swr: { data, isLoading },
|
const { data, isLoading } = useServerSettings();
|
||||||
}: {
|
|
||||||
swr: { data: Response['/api/server/settings'] | undefined; isLoading: boolean };
|
return (
|
||||||
}) {
|
<>
|
||||||
|
<LoadingOverlay visible={isLoading} />
|
||||||
|
{data ? <Form data={data} isLoading={isLoading} /> : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Form({ data, isLoading }: { data: Response['/api/server/settings']; isLoading: boolean }) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
httpWebhookOnUpload: '',
|
httpWebhookOnUpload: data.settings.httpWebhookOnUpload,
|
||||||
httpWebhookOnShorten: '',
|
httpWebhookOnShorten: data.settings.httpWebhookOnShorten,
|
||||||
},
|
},
|
||||||
enhanceGetInputProps: (payload) => ({
|
enhanceGetInputProps: (payload) => ({
|
||||||
disabled: data?.tampered?.includes(payload.field) || false,
|
disabled: data.tampered.includes(payload.field) || false,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -37,19 +44,7 @@ export default function HttpWebhook({
|
|||||||
return settingsOnSubmit(navigate, form)(values);
|
return settingsOnSubmit(navigate, form)(values);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!data) return;
|
|
||||||
|
|
||||||
form.setValues({
|
|
||||||
httpWebhookOnUpload: data.settings.httpWebhookOnUpload ?? '',
|
|
||||||
httpWebhookOnShorten: data.settings.httpWebhookOnShorten ?? '',
|
|
||||||
});
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<LoadingOverlay visible={isLoading} />
|
|
||||||
|
|
||||||
<form onSubmit={form.onSubmit(onSubmit)}>
|
<form onSubmit={form.onSubmit(onSubmit)}>
|
||||||
<Stack gap='lg'>
|
<Stack gap='lg'>
|
||||||
<TextInput
|
<TextInput
|
||||||
@@ -71,6 +66,5 @@ export default function HttpWebhook({
|
|||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,33 @@
|
|||||||
import { Response } from '@/lib/api/response';
|
import type { Response } from '@/lib/api/response';
|
||||||
import { Button, LoadingOverlay, NumberInput, Stack, Switch } from '@mantine/core';
|
import { Button, LoadingOverlay, NumberInput, Stack, Switch } from '@mantine/core';
|
||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
import { IconDeviceFloppy } from '@tabler/icons-react';
|
import { IconDeviceFloppy } from '@tabler/icons-react';
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { settingsOnSubmit } from '../settingsOnSubmit';
|
import { settingsOnSubmit } from '../settingsOnSubmit';
|
||||||
|
import useServerSettings from '../useServerSettings';
|
||||||
|
|
||||||
export default function Invites({
|
export default function Invites() {
|
||||||
swr: { data, isLoading },
|
const { data, isLoading } = useServerSettings();
|
||||||
}: {
|
|
||||||
swr: { data: Response['/api/server/settings'] | undefined; isLoading: boolean };
|
return (
|
||||||
}) {
|
<>
|
||||||
|
<LoadingOverlay visible={isLoading} />
|
||||||
|
{data ? <Form data={data} isLoading={isLoading} /> : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Form({ data, isLoading }: { data: Response['/api/server/settings']; isLoading: boolean }) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
invitesEnabled: true,
|
invitesEnabled: data.settings.invitesEnabled,
|
||||||
invitesLength: 6,
|
invitesLength: data.settings.invitesLength,
|
||||||
},
|
},
|
||||||
enhanceGetInputProps: (payload: any): object => ({
|
enhanceGetInputProps: (payload: any): object => ({
|
||||||
disabled:
|
disabled:
|
||||||
data?.tampered?.includes(payload.field) ||
|
data.tampered.includes(payload.field) ||
|
||||||
(payload.field !== 'invitesEnabled' && !form.values.invitesEnabled) ||
|
(payload.field !== 'invitesEnabled' && !form.values.invitesEnabled) ||
|
||||||
false,
|
false,
|
||||||
}),
|
}),
|
||||||
@@ -28,19 +35,7 @@ export default function Invites({
|
|||||||
|
|
||||||
const onSubmit = settingsOnSubmit(navigate, form);
|
const onSubmit = settingsOnSubmit(navigate, form);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!data) return;
|
|
||||||
|
|
||||||
form.setValues({
|
|
||||||
invitesEnabled: data.settings.invitesEnabled ?? true,
|
|
||||||
invitesLength: data.settings.invitesLength ?? 6,
|
|
||||||
});
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<LoadingOverlay visible={isLoading} />
|
|
||||||
|
|
||||||
<form onSubmit={form.onSubmit(onSubmit)}>
|
<form onSubmit={form.onSubmit(onSubmit)}>
|
||||||
<Stack gap='lg'>
|
<Stack gap='lg'>
|
||||||
<Switch
|
<Switch
|
||||||
@@ -63,6 +58,5 @@ export default function Invites({
|
|||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,49 +1,41 @@
|
|||||||
import { Response } from '@/lib/api/response';
|
import type { Response } from '@/lib/api/response';
|
||||||
import { Button, Divider, LoadingOverlay, Stack, Switch, TextInput } from '@mantine/core';
|
import { Button, Divider, LoadingOverlay, Stack, Switch, TextInput } from '@mantine/core';
|
||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
import { IconDeviceFloppy } from '@tabler/icons-react';
|
import { IconDeviceFloppy } from '@tabler/icons-react';
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { settingsOnSubmit } from '../settingsOnSubmit';
|
import { settingsOnSubmit } from '../settingsOnSubmit';
|
||||||
|
import useServerSettings from '../useServerSettings';
|
||||||
|
|
||||||
export default function Mfa({
|
export default function Mfa() {
|
||||||
swr: { data, isLoading },
|
const { data, isLoading } = useServerSettings();
|
||||||
}: {
|
|
||||||
swr: { data: Response['/api/server/settings'] | undefined; isLoading: boolean };
|
return (
|
||||||
}) {
|
<>
|
||||||
|
<LoadingOverlay visible={isLoading} />
|
||||||
|
{data ? <Form data={data} isLoading={isLoading} /> : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Form({ data, isLoading }: { data: Response['/api/server/settings']; isLoading: boolean }) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
mfaTotpEnabled: false,
|
mfaTotpEnabled: data.settings.mfaTotpEnabled,
|
||||||
mfaTotpIssuer: 'Zipline',
|
mfaTotpIssuer: data.settings.mfaTotpIssuer,
|
||||||
mfaPasskeysEnabled: false,
|
mfaPasskeysEnabled: data.settings.mfaPasskeysEnabled,
|
||||||
mfaPasskeysRpID: '',
|
mfaPasskeysRpID: data.settings.mfaPasskeysRpID,
|
||||||
mfaPasskeysOrigin: '',
|
mfaPasskeysOrigin: data.settings.mfaPasskeysOrigin,
|
||||||
},
|
},
|
||||||
enhanceGetInputProps: (payload) => ({
|
enhanceGetInputProps: (payload) => ({
|
||||||
disabled: data?.tampered?.includes(payload.field) || false,
|
disabled: data.tampered.includes(payload.field) || false,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = settingsOnSubmit(navigate, form);
|
const onSubmit = settingsOnSubmit(navigate, form);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!data) return;
|
|
||||||
|
|
||||||
form.setValues({
|
|
||||||
mfaTotpEnabled: data.settings.mfaTotpEnabled ?? false,
|
|
||||||
mfaTotpIssuer: data.settings.mfaTotpIssuer ?? 'Zipline',
|
|
||||||
mfaPasskeysEnabled: data.settings.mfaPasskeysEnabled ?? false,
|
|
||||||
mfaPasskeysRpID: data.settings.mfaPasskeysRpID ?? '',
|
|
||||||
mfaPasskeysOrigin: data.settings.mfaPasskeysOrigin ?? '',
|
|
||||||
});
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<LoadingOverlay visible={isLoading} />
|
|
||||||
|
|
||||||
<form onSubmit={form.onSubmit(onSubmit)}>
|
<form onSubmit={form.onSubmit(onSubmit)}>
|
||||||
<Stack gap='lg'>
|
<Stack gap='lg'>
|
||||||
<Switch
|
<Switch
|
||||||
@@ -85,6 +77,5 @@ export default function Mfa({
|
|||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Response } from '@/lib/api/response';
|
import type { Response } from '@/lib/api/response';
|
||||||
import {
|
import {
|
||||||
Anchor,
|
Anchor,
|
||||||
Button,
|
Button,
|
||||||
@@ -13,45 +13,52 @@ import {
|
|||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
import { IconDeviceFloppy } from '@tabler/icons-react';
|
import { IconDeviceFloppy } from '@tabler/icons-react';
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
import { settingsOnSubmit } from '../settingsOnSubmit';
|
import { settingsOnSubmit } from '../settingsOnSubmit';
|
||||||
|
import useServerSettings from '../useServerSettings';
|
||||||
|
|
||||||
export default function Oauth({
|
export default function Oauth() {
|
||||||
swr: { data, isLoading },
|
const { data, isLoading } = useServerSettings();
|
||||||
}: {
|
|
||||||
swr: { data: Response['/api/server/settings'] | undefined; isLoading: boolean };
|
return (
|
||||||
}) {
|
<>
|
||||||
|
<LoadingOverlay visible={isLoading} />
|
||||||
|
{data ? <Form data={data} isLoading={isLoading} /> : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Form({ data, isLoading }: { data: Response['/api/server/settings']; isLoading: boolean }) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
oauthBypassLocalLogin: false,
|
oauthBypassLocalLogin: data.settings.oauthBypassLocalLogin,
|
||||||
oauthLoginOnly: false,
|
oauthLoginOnly: data.settings.oauthLoginOnly,
|
||||||
|
|
||||||
oauthDiscordClientId: '',
|
oauthDiscordClientId: data.settings.oauthDiscordClientId,
|
||||||
oauthDiscordClientSecret: '',
|
oauthDiscordClientSecret: data.settings.oauthDiscordClientSecret,
|
||||||
oauthDiscordRedirectUri: '',
|
oauthDiscordRedirectUri: data.settings.oauthDiscordRedirectUri,
|
||||||
oauthDiscordAllowedIds: '',
|
oauthDiscordAllowedIds: data.settings.oauthDiscordAllowedIds.join(', '),
|
||||||
oauthDiscordDeniedIds: '',
|
oauthDiscordDeniedIds: data.settings.oauthDiscordDeniedIds.join(', '),
|
||||||
|
|
||||||
oauthGoogleClientId: '',
|
oauthGoogleClientId: data.settings.oauthGoogleClientId,
|
||||||
oauthGoogleClientSecret: '',
|
oauthGoogleClientSecret: data.settings.oauthGoogleClientSecret,
|
||||||
oauthGoogleRedirectUri: '',
|
oauthGoogleRedirectUri: data.settings.oauthGoogleRedirectUri,
|
||||||
|
|
||||||
oauthGithubClientId: '',
|
oauthGithubClientId: data.settings.oauthGithubClientId,
|
||||||
oauthGithubClientSecret: '',
|
oauthGithubClientSecret: data.settings.oauthGithubClientSecret,
|
||||||
oauthGithubRedirectUri: '',
|
oauthGithubRedirectUri: data.settings.oauthGithubRedirectUri,
|
||||||
|
|
||||||
oauthOidcClientId: '',
|
oauthOidcClientId: data.settings.oauthOidcClientId,
|
||||||
oauthOidcClientSecret: '',
|
oauthOidcClientSecret: data.settings.oauthOidcClientSecret,
|
||||||
oauthOidcAuthorizeUrl: '',
|
oauthOidcAuthorizeUrl: data.settings.oauthOidcAuthorizeUrl,
|
||||||
oauthOidcTokenUrl: '',
|
oauthOidcTokenUrl: data.settings.oauthOidcTokenUrl,
|
||||||
oauthOidcUserinfoUrl: '',
|
oauthOidcUserinfoUrl: data.settings.oauthOidcUserinfoUrl,
|
||||||
oauthOidcRedirectUri: '',
|
oauthOidcRedirectUri: data.settings.oauthOidcRedirectUri,
|
||||||
},
|
},
|
||||||
enhanceGetInputProps: (payload) => ({
|
enhanceGetInputProps: (payload) => ({
|
||||||
disabled: data?.tampered?.includes(payload.field) || false,
|
disabled: data.tampered.includes(payload.field) || false,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -90,44 +97,8 @@ export default function Oauth({
|
|||||||
return settingsOnSubmit(navigate, form)(values);
|
return settingsOnSubmit(navigate, form)(values);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!data) return;
|
|
||||||
|
|
||||||
form.setValues({
|
|
||||||
oauthBypassLocalLogin: data.settings.oauthBypassLocalLogin ?? false,
|
|
||||||
oauthLoginOnly: data.settings.oauthLoginOnly ?? false,
|
|
||||||
|
|
||||||
oauthDiscordClientId: data.settings.oauthDiscordClientId ?? '',
|
|
||||||
oauthDiscordClientSecret: data.settings.oauthDiscordClientSecret ?? '',
|
|
||||||
oauthDiscordRedirectUri: data.settings.oauthDiscordRedirectUri ?? '',
|
|
||||||
oauthDiscordAllowedIds: data.settings.oauthDiscordAllowedIds
|
|
||||||
? data.settings.oauthDiscordAllowedIds.join(', ')
|
|
||||||
: '',
|
|
||||||
oauthDiscordDeniedIds: data.settings.oauthDiscordDeniedIds
|
|
||||||
? data.settings.oauthDiscordDeniedIds.join(', ')
|
|
||||||
: '',
|
|
||||||
|
|
||||||
oauthGoogleClientId: data.settings.oauthGoogleClientId ?? '',
|
|
||||||
oauthGoogleClientSecret: data.settings.oauthGoogleClientSecret ?? '',
|
|
||||||
oauthGoogleRedirectUri: data.settings.oauthGoogleRedirectUri ?? '',
|
|
||||||
|
|
||||||
oauthGithubClientId: data.settings.oauthGithubClientId ?? '',
|
|
||||||
oauthGithubClientSecret: data.settings.oauthGithubClientSecret ?? '',
|
|
||||||
oauthGithubRedirectUri: data.settings.oauthGithubRedirectUri ?? '',
|
|
||||||
|
|
||||||
oauthOidcClientId: data.settings.oauthOidcClientId ?? '',
|
|
||||||
oauthOidcClientSecret: data.settings.oauthOidcClientSecret ?? '',
|
|
||||||
oauthOidcAuthorizeUrl: data.settings.oauthOidcAuthorizeUrl ?? '',
|
|
||||||
oauthOidcTokenUrl: data.settings.oauthOidcTokenUrl ?? '',
|
|
||||||
oauthOidcUserinfoUrl: data.settings.oauthOidcUserinfoUrl ?? '',
|
|
||||||
oauthOidcRedirectUri: data.settings.oauthOidcRedirectUri ?? '',
|
|
||||||
});
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<LoadingOverlay visible={isLoading} />
|
|
||||||
|
|
||||||
<Text size='sm' c='dimmed' mb='md'>
|
<Text size='sm' c='dimmed' mb='md'>
|
||||||
For OAuth to work, the "OAuth Registration" setting must be enabled in the{' '}
|
For OAuth to work, the "OAuth Registration" setting must be enabled in the{' '}
|
||||||
<Anchor component={Link} to='/dashboard/admin/settings/features'>
|
<Anchor component={Link} to='/dashboard/admin/settings/features'>
|
||||||
|
|||||||
@@ -1,30 +1,37 @@
|
|||||||
import { Response } from '@/lib/api/response';
|
import type { Response } from '@/lib/api/response';
|
||||||
import { Button, ColorInput, Group, LoadingOverlay, Stack, Switch, Text, TextInput } from '@mantine/core';
|
import { Button, ColorInput, Group, LoadingOverlay, Stack, Switch, Text, TextInput } from '@mantine/core';
|
||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
import { IconDeviceFloppy, IconRefresh } from '@tabler/icons-react';
|
import { IconDeviceFloppy, IconRefresh } from '@tabler/icons-react';
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { settingsOnSubmit } from '../settingsOnSubmit';
|
import { settingsOnSubmit } from '../settingsOnSubmit';
|
||||||
|
import useServerSettings from '../useServerSettings';
|
||||||
|
|
||||||
export default function PWA({
|
export default function PWA() {
|
||||||
swr: { data, isLoading },
|
const { data, isLoading } = useServerSettings();
|
||||||
}: {
|
|
||||||
swr: { data: Response['/api/server/settings'] | undefined; isLoading: boolean };
|
return (
|
||||||
}) {
|
<>
|
||||||
|
<LoadingOverlay visible={isLoading} />
|
||||||
|
{data ? <Form data={data} isLoading={isLoading} /> : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Form({ data, isLoading }: { data: Response['/api/server/settings']; isLoading: boolean }) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
pwaEnabled: false,
|
pwaEnabled: data.settings.pwaEnabled,
|
||||||
pwaTitle: '',
|
pwaTitle: data.settings.pwaTitle,
|
||||||
pwaShortName: '',
|
pwaShortName: data.settings.pwaShortName,
|
||||||
pwaDescription: '',
|
pwaDescription: data.settings.pwaDescription,
|
||||||
pwaThemeColor: '',
|
pwaThemeColor: data.settings.pwaThemeColor,
|
||||||
pwaBackgroundColor: '',
|
pwaBackgroundColor: data.settings.pwaBackgroundColor,
|
||||||
},
|
},
|
||||||
enhanceGetInputProps: (payload: any): object => ({
|
enhanceGetInputProps: (payload: any): object => ({
|
||||||
disabled:
|
disabled:
|
||||||
data?.tampered?.includes(payload.field) ||
|
data.tampered.includes(payload.field) ||
|
||||||
(payload.field !== 'pwaEnabled' && !form.values.pwaEnabled) ||
|
(payload.field !== 'pwaEnabled' && !form.values.pwaEnabled) ||
|
||||||
false,
|
false,
|
||||||
}),
|
}),
|
||||||
@@ -48,23 +55,8 @@ export default function PWA({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!data) return;
|
|
||||||
|
|
||||||
form.setValues({
|
|
||||||
pwaEnabled: data.settings.pwaEnabled ?? false,
|
|
||||||
pwaTitle: data.settings.pwaTitle ?? '',
|
|
||||||
pwaShortName: data.settings.pwaShortName ?? '',
|
|
||||||
pwaDescription: data.settings.pwaDescription ?? '',
|
|
||||||
pwaThemeColor: data.settings.pwaThemeColor ?? '',
|
|
||||||
pwaBackgroundColor: data.settings.pwaBackgroundColor ?? '',
|
|
||||||
});
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<LoadingOverlay visible={isLoading} />
|
|
||||||
|
|
||||||
<Text size='sm' c='dimmed' mb='md'>
|
<Text size='sm' c='dimmed' mb='md'>
|
||||||
Refresh the page after enabling PWA to see any changes.
|
Refresh the page after enabling PWA to see any changes.
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -1,35 +1,42 @@
|
|||||||
import { Response } from '@/lib/api/response';
|
import type { Response } from '@/lib/api/response';
|
||||||
import { Button, LoadingOverlay, NumberInput, Stack, Switch, Text, TextInput } from '@mantine/core';
|
import { Button, LoadingOverlay, NumberInput, Stack, Switch, Text, TextInput } from '@mantine/core';
|
||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
import { IconDeviceFloppy } from '@tabler/icons-react';
|
import { IconDeviceFloppy } from '@tabler/icons-react';
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { settingsOnSubmit } from '../settingsOnSubmit';
|
import { settingsOnSubmit } from '../settingsOnSubmit';
|
||||||
|
import useServerSettings from '../useServerSettings';
|
||||||
|
|
||||||
export default function Ratelimit({
|
export default function Ratelimit() {
|
||||||
swr: { data, isLoading },
|
const { data, isLoading } = useServerSettings();
|
||||||
}: {
|
|
||||||
swr: { data: Response['/api/server/settings'] | undefined; isLoading: boolean };
|
return (
|
||||||
}) {
|
<>
|
||||||
|
<LoadingOverlay visible={isLoading} />
|
||||||
|
{data ? <Form data={data} isLoading={isLoading} /> : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Form({ data, isLoading }: { data: Response['/api/server/settings']; isLoading: boolean }) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const form = useForm<{
|
const form = useForm<{
|
||||||
ratelimitEnabled: boolean;
|
ratelimitEnabled: boolean;
|
||||||
ratelimitMax: number;
|
ratelimitMax: number;
|
||||||
ratelimitWindow: number | '';
|
ratelimitWindow: number | '' | null;
|
||||||
ratelimitAdminBypass: boolean;
|
ratelimitAdminBypass: boolean;
|
||||||
ratelimitAllowList: string;
|
ratelimitAllowList: string;
|
||||||
}>({
|
}>({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
ratelimitEnabled: true,
|
ratelimitEnabled: data.settings.ratelimitEnabled,
|
||||||
ratelimitMax: 10,
|
ratelimitMax: data.settings.ratelimitMax,
|
||||||
ratelimitWindow: '',
|
ratelimitWindow: data.settings.ratelimitWindow,
|
||||||
ratelimitAdminBypass: false,
|
ratelimitAdminBypass: data.settings.ratelimitAdminBypass,
|
||||||
ratelimitAllowList: '',
|
ratelimitAllowList: data.settings.ratelimitAllowList.join(', '),
|
||||||
},
|
},
|
||||||
enhanceGetInputProps: (payload: any): object => ({
|
enhanceGetInputProps: (payload: any): object => ({
|
||||||
disabled:
|
disabled:
|
||||||
data?.tampered?.includes(payload.field) ||
|
data.tampered.includes(payload.field) ||
|
||||||
(payload.field !== 'ratelimitEnabled' && !form.values.ratelimitEnabled) ||
|
(payload.field !== 'ratelimitEnabled' && !form.values.ratelimitEnabled) ||
|
||||||
false,
|
false,
|
||||||
}),
|
}),
|
||||||
@@ -55,22 +62,8 @@ export default function Ratelimit({
|
|||||||
return settingsOnSubmit(navigate, form)(values);
|
return settingsOnSubmit(navigate, form)(values);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!data) return;
|
|
||||||
|
|
||||||
form.setValues({
|
|
||||||
ratelimitEnabled: data.settings.ratelimitEnabled ?? true,
|
|
||||||
ratelimitMax: data.settings.ratelimitMax ?? 10,
|
|
||||||
ratelimitWindow: data.settings.ratelimitWindow ?? '',
|
|
||||||
ratelimitAdminBypass: data.settings.ratelimitAdminBypass ?? false,
|
|
||||||
ratelimitAllowList: data.settings.ratelimitAllowList.join(', ') ?? '',
|
|
||||||
});
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<LoadingOverlay visible={isLoading} />
|
|
||||||
|
|
||||||
<Text size='sm' c='dimmed' mb='md'>
|
<Text size='sm' c='dimmed' mb='md'>
|
||||||
All options require a restart to take effect.
|
All options require a restart to take effect.
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -1,51 +1,43 @@
|
|||||||
import { Response } from '@/lib/api/response';
|
import type { Response } from '@/lib/api/response';
|
||||||
import { Button, Code, LoadingOverlay, Stack, Text, TextInput } from '@mantine/core';
|
import { Button, Code, LoadingOverlay, Stack, Text, TextInput } from '@mantine/core';
|
||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
import { IconDeviceFloppy } from '@tabler/icons-react';
|
import { IconDeviceFloppy } from '@tabler/icons-react';
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { settingsOnSubmit } from '../settingsOnSubmit';
|
import { settingsOnSubmit } from '../settingsOnSubmit';
|
||||||
|
import useServerSettings from '../useServerSettings';
|
||||||
|
|
||||||
export default function Tasks({
|
export default function Tasks() {
|
||||||
swr: { data, isLoading },
|
const { data, isLoading } = useServerSettings();
|
||||||
}: {
|
|
||||||
swr: { data: Response['/api/server/settings'] | undefined; isLoading: boolean };
|
return (
|
||||||
}) {
|
<>
|
||||||
|
<LoadingOverlay visible={isLoading} />
|
||||||
|
{data ? <Form data={data} isLoading={isLoading} /> : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Form({ data, isLoading }: { data: Response['/api/server/settings']; isLoading: boolean }) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
tasksDeleteInterval: '30m',
|
tasksDeleteInterval: data.settings.tasksDeleteInterval,
|
||||||
tasksClearInvitesInterval: '30m',
|
tasksClearInvitesInterval: data.settings.tasksClearInvitesInterval,
|
||||||
tasksMaxViewsInterval: '30m',
|
tasksMaxViewsInterval: data.settings.tasksMaxViewsInterval,
|
||||||
tasksThumbnailsInterval: '30m',
|
tasksThumbnailsInterval: data.settings.tasksThumbnailsInterval,
|
||||||
tasksMetricsInterval: '30m',
|
tasksMetricsInterval: data.settings.tasksMetricsInterval,
|
||||||
tasksCleanThumbnailsInterval: '1d',
|
tasksCleanThumbnailsInterval: data.settings.tasksCleanThumbnailsInterval,
|
||||||
},
|
},
|
||||||
enhanceGetInputProps: (payload) => ({
|
enhanceGetInputProps: (payload) => ({
|
||||||
disabled: data?.tampered?.includes(payload.field) || false,
|
disabled: data.tampered.includes(payload.field) || false,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = settingsOnSubmit(navigate, form);
|
const onSubmit = settingsOnSubmit(navigate, form);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!data) return;
|
|
||||||
|
|
||||||
form.setValues({
|
|
||||||
tasksDeleteInterval: data.settings.tasksDeleteInterval ?? '30m',
|
|
||||||
tasksClearInvitesInterval: data.settings.tasksClearInvitesInterval ?? '30m',
|
|
||||||
tasksMaxViewsInterval: data.settings.tasksMaxViewsInterval ?? '30m',
|
|
||||||
tasksThumbnailsInterval: data.settings.tasksThumbnailsInterval ?? '30m',
|
|
||||||
tasksMetricsInterval: data.settings.tasksMetricsInterval ?? '30m',
|
|
||||||
tasksCleanThumbnailsInterval: data.settings.tasksCleanThumbnailsInterval ?? '1d',
|
|
||||||
});
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<LoadingOverlay visible={isLoading} />
|
|
||||||
|
|
||||||
<Text size='sm' c='dimmed' mb='md'>
|
<Text size='sm' c='dimmed' mb='md'>
|
||||||
All options require a restart to take effect. Setting a value of <Code>0</Code> will disable the task.
|
All options require a restart to take effect. Setting a value of <Code>0</Code> will disable the task.
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -1,43 +1,38 @@
|
|||||||
import { Response } from '@/lib/api/response';
|
import type { Response } from '@/lib/api/response';
|
||||||
import { Button, LoadingOverlay, NumberInput, Stack, TextInput } from '@mantine/core';
|
import { Button, LoadingOverlay, NumberInput, Stack, TextInput } from '@mantine/core';
|
||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
import { IconDeviceFloppy } from '@tabler/icons-react';
|
import { IconDeviceFloppy } from '@tabler/icons-react';
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { settingsOnSubmit } from '../settingsOnSubmit';
|
import { settingsOnSubmit } from '../settingsOnSubmit';
|
||||||
|
import useServerSettings from '../useServerSettings';
|
||||||
|
|
||||||
export default function Urls({
|
export default function Urls() {
|
||||||
swr: { data, isLoading },
|
const { data, isLoading } = useServerSettings();
|
||||||
}: {
|
|
||||||
swr: { data: Response['/api/server/settings'] | undefined; isLoading: boolean };
|
return (
|
||||||
}) {
|
<>
|
||||||
|
<LoadingOverlay visible={isLoading} />
|
||||||
|
{data ? <Form data={data} isLoading={isLoading} /> : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Form({ data, isLoading }: { data: Response['/api/server/settings']; isLoading: boolean }) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
urlsRoute: '/go',
|
urlsRoute: data.settings.urlsRoute,
|
||||||
urlsLength: 6,
|
urlsLength: data.settings.urlsLength,
|
||||||
},
|
},
|
||||||
enhanceGetInputProps: (payload) => ({
|
enhanceGetInputProps: (payload) => ({
|
||||||
disabled: data?.tampered?.includes(payload.field) || false,
|
disabled: data.tampered.includes(payload.field) || false,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = settingsOnSubmit(navigate, form);
|
const onSubmit = settingsOnSubmit(navigate, form);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!data) return;
|
|
||||||
|
|
||||||
form.setValues({
|
|
||||||
urlsRoute: data.settings.urlsRoute ?? '/go',
|
|
||||||
urlsLength: data.settings.urlsLength ?? 6,
|
|
||||||
});
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<LoadingOverlay visible={isLoading} />
|
|
||||||
|
|
||||||
<form onSubmit={form.onSubmit(onSubmit)}>
|
<form onSubmit={form.onSubmit(onSubmit)}>
|
||||||
<Stack gap='lg'>
|
<Stack gap='lg'>
|
||||||
<TextInput
|
<TextInput
|
||||||
@@ -61,6 +56,5 @@ export default function Urls({
|
|||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +1,41 @@
|
|||||||
import { Response } from '@/lib/api/response';
|
import type { Response } from '@/lib/api/response';
|
||||||
import { Button, JsonInput, LoadingOverlay, Stack, Switch, TextInput } from '@mantine/core';
|
import { Button, JsonInput, LoadingOverlay, Stack, Switch, TextInput } from '@mantine/core';
|
||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
import { IconDeviceFloppy } from '@tabler/icons-react';
|
import { IconDeviceFloppy } from '@tabler/icons-react';
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { settingsOnSubmit } from '../settingsOnSubmit';
|
import { settingsOnSubmit } from '../settingsOnSubmit';
|
||||||
|
import useServerSettings from '../useServerSettings';
|
||||||
|
|
||||||
const defaultExternalLinks = [
|
export default function Website() {
|
||||||
{
|
const { data, isLoading } = useServerSettings();
|
||||||
name: 'GitHub',
|
|
||||||
url: 'https://github.com/diced/zipline',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Documentation',
|
|
||||||
url: 'https://zipline.diced.sh',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function Website({
|
return (
|
||||||
swr: { data, isLoading },
|
<>
|
||||||
}: {
|
<LoadingOverlay visible={isLoading} />
|
||||||
swr: { data: Response['/api/server/settings'] | undefined; isLoading: boolean };
|
{data ? <Form data={data} isLoading={isLoading} /> : null}
|
||||||
}) {
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Form({ data, isLoading }: { data: Response['/api/server/settings']; isLoading: boolean }) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
websiteTitle: 'Zipline',
|
websiteTitle: data.settings.websiteTitle,
|
||||||
websiteTitleLogo: '',
|
websiteTitleLogo: data.settings.websiteTitleLogo,
|
||||||
websiteExternalLinks: JSON.stringify(defaultExternalLinks),
|
websiteExternalLinks: JSON.stringify(data.settings.websiteExternalLinks, null, 2),
|
||||||
websiteLoginBackground: '',
|
websiteLoginBackground: data.settings.websiteLoginBackground,
|
||||||
websiteLoginBackgroundBlur: true,
|
websiteLoginBackgroundBlur: data.settings.websiteLoginBackgroundBlur,
|
||||||
websiteDefaultAvatar: '',
|
websiteDefaultAvatar: data.settings.websiteDefaultAvatar,
|
||||||
websiteTos: '',
|
websiteTos: data.settings.websiteTos,
|
||||||
|
|
||||||
websiteThemeDefault: 'system',
|
websiteThemeDefault: data.settings.websiteThemeDefault,
|
||||||
websiteThemeDark: 'builtin:dark_gray',
|
websiteThemeDark: data.settings.websiteThemeDark,
|
||||||
websiteThemeLight: 'builtin:light_gray',
|
websiteThemeLight: data.settings.websiteThemeLight,
|
||||||
},
|
},
|
||||||
enhanceGetInputProps: (payload) => ({
|
enhanceGetInputProps: (payload) => ({
|
||||||
disabled: data?.tampered?.includes(payload.field) || false,
|
disabled: data.tampered.includes(payload.field) || false,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -59,12 +55,19 @@ export default function Website({
|
|||||||
}
|
}
|
||||||
|
|
||||||
sendValues.websiteTitleLogo =
|
sendValues.websiteTitleLogo =
|
||||||
values.websiteTitleLogo.trim() === '' ? null : values.websiteTitleLogo.trim();
|
values.websiteTitleLogo?.trim() === '' || !values.websiteTitleLogo?.trim()
|
||||||
|
? null
|
||||||
|
: values.websiteTitleLogo.trim();
|
||||||
sendValues.websiteLoginBackground =
|
sendValues.websiteLoginBackground =
|
||||||
values.websiteLoginBackground.trim() === '' ? null : values.websiteLoginBackground.trim();
|
values.websiteLoginBackground?.trim() === '' || !values.websiteLoginBackground?.trim()
|
||||||
|
? null
|
||||||
|
: values.websiteLoginBackground.trim();
|
||||||
sendValues.websiteDefaultAvatar =
|
sendValues.websiteDefaultAvatar =
|
||||||
values.websiteDefaultAvatar.trim() === '' ? null : values.websiteDefaultAvatar.trim();
|
values.websiteDefaultAvatar?.trim() === '' || !values.websiteDefaultAvatar?.trim()
|
||||||
sendValues.websiteTos = values.websiteTos.trim() === '' ? null : values.websiteTos.trim();
|
? null
|
||||||
|
: values.websiteDefaultAvatar.trim();
|
||||||
|
sendValues.websiteTos =
|
||||||
|
values.websiteTos?.trim() === '' || !values.websiteTos?.trim() ? null : values.websiteTos.trim();
|
||||||
|
|
||||||
sendValues.websiteThemeDefault = values.websiteThemeDefault.trim();
|
sendValues.websiteThemeDefault = values.websiteThemeDefault.trim();
|
||||||
sendValues.websiteThemeDark = values.websiteThemeDark.trim();
|
sendValues.websiteThemeDark = values.websiteThemeDark.trim();
|
||||||
@@ -76,31 +79,7 @@ export default function Website({
|
|||||||
return settingsOnSubmit(navigate, form)(sendValues);
|
return settingsOnSubmit(navigate, form)(sendValues);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!data) return;
|
|
||||||
|
|
||||||
form.setValues({
|
|
||||||
websiteTitle: data.settings.websiteTitle ?? 'Zipline',
|
|
||||||
websiteTitleLogo: data.settings.websiteTitleLogo ?? '',
|
|
||||||
websiteExternalLinks: JSON.stringify(
|
|
||||||
data.settings.websiteExternalLinks ?? defaultExternalLinks,
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
),
|
|
||||||
websiteLoginBackground: data.settings.websiteLoginBackground ?? '',
|
|
||||||
websiteLoginBackgroundBlur: data.settings.websiteLoginBackgroundBlur ?? true,
|
|
||||||
websiteDefaultAvatar: data.settings.websiteDefaultAvatar ?? '',
|
|
||||||
websiteTos: data.settings.websiteTos ?? '',
|
|
||||||
websiteThemeDefault: data.settings.websiteThemeDefault ?? 'system',
|
|
||||||
websiteThemeDark: data.settings.websiteThemeDark ?? 'builtin:dark_gray',
|
|
||||||
websiteThemeLight: data.settings.websiteThemeLight ?? 'builtin:light_gray',
|
|
||||||
});
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<LoadingOverlay visible={isLoading} />
|
|
||||||
|
|
||||||
<form onSubmit={form.onSubmit(onSubmit)}>
|
<form onSubmit={form.onSubmit(onSubmit)}>
|
||||||
<Stack gap='lg'>
|
<Stack gap='lg'>
|
||||||
<TextInput
|
<TextInput
|
||||||
@@ -124,7 +103,14 @@ export default function Website({
|
|||||||
minRows={1}
|
minRows={1}
|
||||||
maxRows={7}
|
maxRows={7}
|
||||||
autosize
|
autosize
|
||||||
placeholder={JSON.stringify(defaultExternalLinks, null, 2)}
|
placeholder={JSON.stringify(
|
||||||
|
[
|
||||||
|
{ name: 'GitHub', url: 'https://github.com/diced/zipline' },
|
||||||
|
{ name: 'Documentation', url: 'https://zipline.diced.sh' },
|
||||||
|
],
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
)}
|
||||||
{...form.getInputProps('websiteExternalLinks')}
|
{...form.getInputProps('websiteExternalLinks')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -180,6 +166,5 @@ export default function Website({
|
|||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
6
src/components/pages/serverSettings/useServerSettings.ts
Normal file
6
src/components/pages/serverSettings/useServerSettings.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import type { Response } from '@/lib/api/response';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
|
||||||
|
export default function useServerSettings() {
|
||||||
|
return useSWR<Response['/api/server/settings']>('/api/server/settings');
|
||||||
|
}
|
||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
IconSettingsFilled,
|
IconSettingsFilled,
|
||||||
IconX,
|
IconX,
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
export default function SettingsAvatar() {
|
export default function SettingsAvatar() {
|
||||||
const user = useUserStore((state) => state.user);
|
const user = useUserStore((state) => state.user);
|
||||||
@@ -36,14 +36,16 @@ export default function SettingsAvatar() {
|
|||||||
const [avatar, setAvatar] = useState<File | null>(null);
|
const [avatar, setAvatar] = useState<File | null>(null);
|
||||||
const [avatarSrc, setAvatarSrc] = useState<string | null>(null);
|
const [avatarSrc, setAvatarSrc] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
const onAvatarChange = async (file: File | null) => {
|
||||||
(async () => {
|
setAvatar(file);
|
||||||
if (!avatar) return;
|
|
||||||
|
|
||||||
const base64url = await readToDataURL(avatar);
|
if (!file) {
|
||||||
setAvatarSrc(base64url);
|
setAvatarSrc(null);
|
||||||
})();
|
return;
|
||||||
}, [avatar]);
|
}
|
||||||
|
|
||||||
|
setAvatarSrc(await readToDataURL(file));
|
||||||
|
};
|
||||||
|
|
||||||
const saveAvatar = async () => {
|
const saveAvatar = async () => {
|
||||||
if (!avatar) return;
|
if (!avatar) return;
|
||||||
@@ -111,7 +113,7 @@ export default function SettingsAvatar() {
|
|||||||
accept='image/*'
|
accept='image/*'
|
||||||
placeholder='Upload new avatar...'
|
placeholder='Upload new avatar...'
|
||||||
value={avatar}
|
value={avatar}
|
||||||
onChange={(file) => setAvatar(file)}
|
onChange={onAvatarChange}
|
||||||
leftSection={<IconPhotoUp size='1rem' />}
|
leftSection={<IconPhotoUp size='1rem' />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { User } from '@/lib/db/models/user';
|
||||||
import { Response } from '@/lib/api/response';
|
import { Response } from '@/lib/api/response';
|
||||||
import { fetchApi } from '@/lib/fetchApi';
|
import { fetchApi } from '@/lib/fetchApi';
|
||||||
import { useUserStore } from '@/lib/client/store/user';
|
import { useUserStore } from '@/lib/client/store/user';
|
||||||
@@ -27,7 +28,6 @@ import {
|
|||||||
IconDeviceFloppy,
|
IconDeviceFloppy,
|
||||||
IconFileX,
|
IconFileX,
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { mutate } from 'swr';
|
import { mutate } from 'swr';
|
||||||
import { useShallow } from 'zustand/shallow';
|
import { useShallow } from 'zustand/shallow';
|
||||||
|
|
||||||
@@ -40,19 +40,34 @@ const alignIcons: Record<string, React.ReactNode> = {
|
|||||||
export default function SettingsFileView() {
|
export default function SettingsFileView() {
|
||||||
const [user, setUser] = useUserStore(useShallow((state) => [state.user, state.setUser]));
|
const [user, setUser] = useUserStore(useShallow((state) => [state.user, state.setUser]));
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return (
|
||||||
|
<Paper withBorder p='sm'>
|
||||||
|
<Title order={2}>Viewing Files</Title>
|
||||||
|
<Text c='dimmed' mt='xs'>
|
||||||
|
Loading…
|
||||||
|
</Text>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Form user={user} setUser={setUser} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Form({ user, setUser }: { user: User; setUser: (u: User) => void }) {
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
enabled: user?.view.enabled ?? false,
|
enabled: user.view.enabled || false,
|
||||||
content: user?.view.content ?? '',
|
content: user.view.content || '',
|
||||||
embed: user?.view.embed ?? false,
|
embed: user.view.embed || false,
|
||||||
embedTitle: user?.view.embedTitle ?? '',
|
embedTitle: user.view.embedTitle || '',
|
||||||
embedDescription: user?.view.embedDescription ?? '',
|
embedDescription: user.view.embedDescription || '',
|
||||||
embedSiteName: user?.view.embedSiteName ?? '',
|
embedSiteName: user.view.embedSiteName || '',
|
||||||
embedColor: user?.view.embedColor ?? '',
|
embedColor: user.view.embedColor || '',
|
||||||
align: user?.view.align ?? 'left',
|
align: user.view.align || 'left',
|
||||||
showMimetype: user?.view.showMimetype ?? false,
|
showMimetype: user.view.showMimetype || false,
|
||||||
showTags: user?.view.showTags ?? false,
|
showTags: user.view.showTags || false,
|
||||||
showFolder: user?.view.showFolder ?? false,
|
showFolder: user.view.showFolder || false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -95,24 +110,6 @@ export default function SettingsFileView() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (user) {
|
|
||||||
form.setValues({
|
|
||||||
enabled: user.view.enabled || false,
|
|
||||||
content: user.view.content || '',
|
|
||||||
embed: user.view.embed || false,
|
|
||||||
embedTitle: user.view.embedTitle || '',
|
|
||||||
embedDescription: user.view.embedDescription || '',
|
|
||||||
embedSiteName: user.view.embedSiteName || '',
|
|
||||||
embedColor: user.view.embedColor || '',
|
|
||||||
align: user.view.align || 'left',
|
|
||||||
showMimetype: user.view.showMimetype || false,
|
|
||||||
showTags: user.view.showTags || false,
|
|
||||||
showFolder: user.view.showFolder || false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [user]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper withBorder p='sm'>
|
<Paper withBorder p='sm'>
|
||||||
<Title order={2}>Viewing Files</Title>
|
<Title order={2}>Viewing Files</Title>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { User } from '@/lib/db/models/user';
|
||||||
import { ApiError } from '@/lib/api/errors';
|
import { ApiError } from '@/lib/api/errors';
|
||||||
import { Response } from '@/lib/api/response';
|
import { Response } from '@/lib/api/response';
|
||||||
import { fetchApi } from '@/lib/fetchApi';
|
import { fetchApi } from '@/lib/fetchApi';
|
||||||
@@ -25,29 +26,36 @@ import {
|
|||||||
IconUser,
|
IconUser,
|
||||||
IconUserCancel,
|
IconUserCancel,
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { mutate } from 'swr';
|
import { mutate } from 'swr';
|
||||||
|
import useSWR from 'swr';
|
||||||
import { useShallow } from 'zustand/shallow';
|
import { useShallow } from 'zustand/shallow';
|
||||||
|
|
||||||
export default function SettingsUser() {
|
export default function SettingsUser() {
|
||||||
const [user, setUser] = useUserStore(useShallow((state) => [state.user, state.setUser]));
|
const [user, setUser] = useUserStore(useShallow((state) => [state.user, state.setUser]));
|
||||||
|
|
||||||
const [tokenShown, setTokenShown] = useState(false);
|
const { data: tokenPayload } = useSWR<Response['/api/user/token']>('/api/user/token');
|
||||||
const [token, setToken] = useState('');
|
|
||||||
|
|
||||||
useEffect(() => {
|
if (!user) {
|
||||||
(async () => {
|
return (
|
||||||
const { data } = await fetchApi<Response['/api/user/token']>('/api/user/token');
|
<Paper withBorder p='sm'>
|
||||||
|
<Title order={2}>User</Title>
|
||||||
if (data) {
|
<Text c='dimmed' size='sm' mt='sm'>
|
||||||
setToken(data.token || '');
|
Loading…
|
||||||
|
</Text>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
})();
|
|
||||||
}, []);
|
return <Form user={user} setUser={setUser} token={tokenPayload?.token ?? ''} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Form({ user, setUser, token }: { user: User; setUser: (u: User) => void; token: string }) {
|
||||||
|
const [tokenShown, setTokenShown] = useState(false);
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
username: user?.username ?? '',
|
username: user.username,
|
||||||
password: '',
|
password: '',
|
||||||
},
|
},
|
||||||
validate: {
|
validate: {
|
||||||
@@ -61,7 +69,7 @@ export default function SettingsUser() {
|
|||||||
password?: string;
|
password?: string;
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
if (values.username !== user?.username) send['username'] = values.username.trim();
|
if (values.username !== user.username) send['username'] = values.username.trim();
|
||||||
if (values.password) send['password'] = values.password.trim();
|
if (values.password) send['password'] = values.password.trim();
|
||||||
|
|
||||||
const { data, error } = await fetchApi<Response['/api/user']>('/api/user', 'PATCH', send);
|
const { data, error } = await fetchApi<Response['/api/user']>('/api/user', 'PATCH', send);
|
||||||
@@ -84,6 +92,7 @@ export default function SettingsUser() {
|
|||||||
if (!data?.user) return;
|
if (!data?.user) return;
|
||||||
|
|
||||||
mutate('/api/user');
|
mutate('/api/user');
|
||||||
|
mutate('/api/user/token');
|
||||||
setUser(data.user);
|
setUser(data.user);
|
||||||
notifications.show({
|
notifications.show({
|
||||||
message: 'User updated',
|
message: 'User updated',
|
||||||
@@ -96,7 +105,7 @@ export default function SettingsUser() {
|
|||||||
<Paper withBorder p='sm'>
|
<Paper withBorder p='sm'>
|
||||||
<Title order={2}>User</Title>
|
<Title order={2}>User</Title>
|
||||||
<Text c='dimmed' size='sm' mb='sm'>
|
<Text c='dimmed' size='sm' mb='sm'>
|
||||||
{user?.id}
|
{user.id}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<form onSubmit={form.onSubmit(onSubmit)}>
|
<form onSubmit={form.onSubmit(onSubmit)}>
|
||||||
@@ -134,7 +143,7 @@ export default function SettingsUser() {
|
|||||||
leftSection={<IconAsteriskSimple size='1rem' />}
|
leftSection={<IconAsteriskSimple size='1rem' />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button type='submit' mt='md' loading={!user} leftSection={<IconDeviceFloppy size='1rem' />}>
|
<Button type='submit' mt='md' leftSection={<IconDeviceFloppy size='1rem' />}>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
24
src/lib/client/hooks/useUser.ts
Normal file
24
src/lib/client/hooks/useUser.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import type { Response } from '@/lib/api/response';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
|
||||||
|
async function fetcher(url: string): Promise<Response['/api/user'] | null> {
|
||||||
|
const res = await fetch(url);
|
||||||
|
if (!res.ok) return null;
|
||||||
|
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useUser(): {
|
||||||
|
user: Response['/api/user']['user'] | undefined;
|
||||||
|
loading: boolean;
|
||||||
|
} {
|
||||||
|
const { data, isLoading } = useSWR<Response['/api/user'] | null>('/api/user', fetcher, {
|
||||||
|
revalidateOnFocus: false,
|
||||||
|
revalidateOnReconnect: false,
|
||||||
|
refreshWhenHidden: false,
|
||||||
|
revalidateIfStale: false,
|
||||||
|
shouldRetryOnError: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { user: data?.user, loading: isLoading };
|
||||||
|
}
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import { Response } from '../../api/response';
|
import { Response } from '../../api/response';
|
||||||
const f = async () => {
|
|
||||||
|
async function fetcher() {
|
||||||
const res = await fetch('/api/version');
|
const res = await fetch('/api/version');
|
||||||
if (!res.ok) throw new Error('Failed to fetch version');
|
if (!res.ok) throw new Error('Failed to fetch version');
|
||||||
|
|
||||||
const r = await res.json();
|
const r = await res.json();
|
||||||
return r;
|
return r;
|
||||||
};
|
}
|
||||||
|
|
||||||
export default function useVersion() {
|
export default function useVersion() {
|
||||||
const { isLoading, data } = useSWR<Response['/api/version'], Error>('/api/version', f, {
|
const { isLoading, data } = useSWR<Response['/api/version'], Error>('/api/version', fetcher, {
|
||||||
refreshInterval: undefined,
|
refreshInterval: undefined,
|
||||||
revalidateOnFocus: false,
|
revalidateOnFocus: false,
|
||||||
revalidateIfStale: false,
|
revalidateIfStale: false,
|
||||||
|
|||||||
Reference in New Issue
Block a user