diff --git a/src/App.tsx b/src/App.tsx
index 62b198d2..6f011f1c 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -33,6 +33,7 @@ import '@/i18n';
import { LibrarySettings } from '@/screens/settings/LibrarySettings';
import { DefaultNavBar } from '@/components/navbar/DefaultNavBar';
import { DownloadSettings } from '@/screens/settings/DownloadSettings.tsx';
+import { ServerSettings } from '@/screens/settings/ServerSettings.tsx';
if (__DEV__) {
// Adds messages only in a dev environment
@@ -66,6 +67,7 @@ export const App: React.FC = () => (
} />
} />
} />
+ } />
{/* Manga Routes */}
diff --git a/src/components/settings/ServerDirSetting.tsx b/src/components/settings/ServerDirSetting.tsx
deleted file mode 100644
index 00afa44b..00000000
--- a/src/components/settings/ServerDirSetting.tsx
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) Contributors to the Suwayomi project
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-import { Button, Dialog, DialogTitle, ListItemText } from '@mui/material';
-import DialogContent from '@mui/material/DialogContent';
-import TextField from '@mui/material/TextField';
-import DialogActions from '@mui/material/DialogActions';
-import ListItemButton from '@mui/material/ListItemButton';
-import { useEffect, useState } from 'react';
-import { useTranslation } from 'react-i18next';
-import DialogContentText from '@mui/material/DialogContentText';
-
-export const ServerDirSetting = ({
- settingName,
- dialogDescription,
- dirPath,
- handlePathChange,
-}: {
- settingName: string;
- dialogDescription: string;
- dirPath?: string;
- handlePathChange: (path: string) => void;
-}) => {
- const { t } = useTranslation();
-
- const [isDialogOpen, setIsDialogOpen] = useState(false);
- const [dialogDirPath, setDialogDirPath] = useState(dirPath ?? '');
-
- useEffect(() => {
- if (!dirPath) {
- return;
- }
-
- setDialogDirPath(dirPath);
- }, [dirPath]);
-
- const closeDialog = () => {
- setIsDialogOpen(false);
- };
-
- const updateSetting = () => {
- closeDialog();
- handlePathChange(dialogDirPath);
- };
-
- return (
- <>
- setIsDialogOpen(true)}>
-
-
-
-
- >
- );
-};
diff --git a/src/components/settings/TextSetting.tsx b/src/components/settings/TextSetting.tsx
new file mode 100644
index 00000000..dc3e9a3f
--- /dev/null
+++ b/src/components/settings/TextSetting.tsx
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) Contributors to the Suwayomi project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+import { Button, Dialog, DialogTitle, InputAdornment, ListItemText } from '@mui/material';
+import DialogContent from '@mui/material/DialogContent';
+import TextField from '@mui/material/TextField';
+import DialogActions from '@mui/material/DialogActions';
+import ListItemButton from '@mui/material/ListItemButton';
+import { useEffect, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import DialogContentText from '@mui/material/DialogContentText';
+import IconButton from '@mui/material/IconButton';
+import { Visibility, VisibilityOff } from '@mui/icons-material';
+
+export const TextSetting = ({
+ settingName,
+ dialogDescription,
+ value,
+ handleChange,
+ isPassword = false,
+ placeholder = '',
+}: {
+ settingName: string;
+ dialogDescription?: string;
+ value?: string;
+ handleChange: (value: string) => void;
+ isPassword?: boolean;
+ placeholder?: string;
+}) => {
+ const { t } = useTranslation();
+
+ const [isDialogOpen, setIsDialogOpen] = useState(false);
+ const [dialogValue, setDialogValue] = useState(value ?? '');
+ const [showPassword, setShowPassword] = useState(false);
+
+ const handleClickShowPassword = () => setShowPassword((show) => !show);
+
+ useEffect(() => {
+ if (!value) {
+ return;
+ }
+
+ setDialogValue(value);
+ }, [value]);
+
+ const closeDialog = (resetValue: boolean = true) => {
+ if (resetValue) {
+ setDialogValue(value ?? '');
+ }
+
+ setShowPassword(false);
+ setIsDialogOpen(false);
+ };
+
+ const updateSetting = () => {
+ closeDialog(false);
+ handleChange(dialogValue);
+ };
+
+ return (
+ <>
+ setIsDialogOpen(true)}>
+
+
+
+
+ >
+ );
+};
diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json
index 1d72108d..4ad1808a 100644
--- a/src/i18n/locale/en.json
+++ b/src/i18n/locale/en.json
@@ -283,13 +283,16 @@
},
"label": {
"browse": "Browse",
+ "client": "Client",
"display": "Display",
"filter": "Filter",
"loading": "Loading…",
"never": "Never",
"none": "None",
+ "password": "Password",
"sort": "Sort",
- "unknown": "Unknown"
+ "unknown": "Unknown",
+ "username": "Username"
},
"language": {
"label": {
@@ -488,7 +491,6 @@
"build_time": "Build time",
"discord": "Discord",
"github": "GitHub",
- "server": "Server",
"server_address": "Server address",
"server_version": "Server version"
},
@@ -534,11 +536,86 @@
"show_nsfw": "Show NSFW",
"show_nsfw_description": "Hide NSFW extensions and sources"
},
- "server_address": {
- "dialog": {
- "label": {
- "enter_address": "Enter server address"
+ "server": {
+ "address": {
+ "client": {
+ "dialog": {
+ "label": {
+ "enter_address": "Enter server address"
+ }
+ },
+ "label": {
+ "description": "The address the client will use to connect to the server"
+ }
+ },
+ "server": {
+ "label": {
+ "ip": "IP",
+ "port": "Port"
+ },
+ "title": "Server bindings"
}
+ },
+ "auth": {
+ "basic": {
+ "label": {
+ "enable": "Use basic authentication",
+ "password": "Basic authentication $t(global.label.password, lowercase)",
+ "username": "Basic authentication $t(global.label.username, lowercase)"
+ }
+ },
+ "title": "Authentication"
+ },
+ "local_source": {
+ "path": {
+ "label": {
+ "description": "The path to the directory on the server where local source files are saved in",
+ "title": "Local source location"
+ }
+ }
+ },
+ "misc": {
+ "log_level": {
+ "graphql": {
+ "label": {
+ "description": "This includes logs with non privacy safe information",
+ "title": "Enable graphql debug logs"
+ }
+ },
+ "label": {
+ "server": "Enable debug logs"
+ }
+ },
+ "title": "Misc",
+ "tray_icon": {
+ "label": {
+ "description": "This icon will be shown on the system that is running the server",
+ "title": "Show icon in system tray"
+ }
+ }
+ },
+ "requests": {
+ "sources": {
+ "parallel": {
+ "label": {
+ "title": "Parallel source requests",
+ "value": "{{value}} $t(source.title)"
+ }
+ }
+ },
+ "title": "Requests"
+ },
+ "socks_proxy": {
+ "label": {
+ "enable": "Use SOCKS5 proxy",
+ "host": "SOCKS5 host",
+ "port": "SOCKS5 port"
+ },
+ "title": "SOCKS proxy"
+ },
+ "title": {
+ "server": "Server",
+ "settings": "Server Settings"
}
},
"title": "Settings"
diff --git a/src/lib/graphql/Fragments.ts b/src/lib/graphql/Fragments.ts
index 03589f48..48a54814 100644
--- a/src/lib/graphql/Fragments.ts
+++ b/src/lib/graphql/Fragments.ts
@@ -455,5 +455,8 @@ export const SERVER_SETTINGS = gql`
backupTime
backupInterval
backupTTL
+
+ # local source
+ localSourcePath
}
`;
diff --git a/src/lib/graphql/generated/graphql.ts b/src/lib/graphql/generated/graphql.ts
index e1ac25dc..7c88c79a 100644
--- a/src/lib/graphql/generated/graphql.ts
+++ b/src/lib/graphql/generated/graphql.ts
@@ -2108,7 +2108,7 @@ export type WebuiUpdateInfoFragment = { __typename?: 'WebUIUpdateInfo', channel:
export type WebuiUpdateStatusFragment = { __typename?: 'WebUIUpdateStatus', progress: number, state: UpdateState, info: { __typename?: 'WebUIUpdateInfo', channel: string, tag: string, updateAvailable: boolean } };
-export type ServerSettingsFragment = { __typename?: 'SettingsType', ip: string, port: number, socksProxyEnabled: boolean, socksProxyHost: string, socksProxyPort: string, webUIFlavor: WebUiFlavor, initialOpenInBrowserEnabled: boolean, webUIInterface: WebUiInterface, electronPath: string, webUIChannel: WebUiChannel, webUIUpdateCheckInterval: number, downloadAsCbz: boolean, downloadsPath: string, autoDownloadNewChapters: boolean, excludeEntryWithUnreadChapters: boolean, autoDownloadAheadLimit: number, maxSourcesInParallel: number, excludeUnreadChapters: boolean, excludeNotStarted: boolean, excludeCompleted: boolean, globalUpdateInterval: number, updateMangas: boolean, basicAuthEnabled: boolean, basicAuthUsername: string, basicAuthPassword: string, debugLogsEnabled: boolean, gqlDebugLogsEnabled: boolean, systemTrayEnabled: boolean, backupPath: string, backupTime: string, backupInterval: number, backupTTL: number };
+export type ServerSettingsFragment = { __typename?: 'SettingsType', ip: string, port: number, socksProxyEnabled: boolean, socksProxyHost: string, socksProxyPort: string, webUIFlavor: WebUiFlavor, initialOpenInBrowserEnabled: boolean, webUIInterface: WebUiInterface, electronPath: string, webUIChannel: WebUiChannel, webUIUpdateCheckInterval: number, downloadAsCbz: boolean, downloadsPath: string, autoDownloadNewChapters: boolean, excludeEntryWithUnreadChapters: boolean, autoDownloadAheadLimit: number, maxSourcesInParallel: number, excludeUnreadChapters: boolean, excludeNotStarted: boolean, excludeCompleted: boolean, globalUpdateInterval: number, updateMangas: boolean, basicAuthEnabled: boolean, basicAuthUsername: string, basicAuthPassword: string, debugLogsEnabled: boolean, gqlDebugLogsEnabled: boolean, systemTrayEnabled: boolean, backupPath: string, backupTime: string, backupInterval: number, backupTTL: number, localSourcePath: string };
export type CreateBackupMutationVariables = Exact<{
input: CreateBackupInput;
@@ -2388,14 +2388,14 @@ export type ResetServerSettingsMutationVariables = Exact<{
}>;
-export type ResetServerSettingsMutation = { __typename?: 'Mutation', resetSettings: { __typename?: 'ResetSettingsPayload', clientMutationId?: string | null, settings: { __typename?: 'SettingsType', ip: string, port: number, socksProxyEnabled: boolean, socksProxyHost: string, socksProxyPort: string, webUIFlavor: WebUiFlavor, initialOpenInBrowserEnabled: boolean, webUIInterface: WebUiInterface, electronPath: string, webUIChannel: WebUiChannel, webUIUpdateCheckInterval: number, downloadAsCbz: boolean, downloadsPath: string, autoDownloadNewChapters: boolean, excludeEntryWithUnreadChapters: boolean, autoDownloadAheadLimit: number, maxSourcesInParallel: number, excludeUnreadChapters: boolean, excludeNotStarted: boolean, excludeCompleted: boolean, globalUpdateInterval: number, updateMangas: boolean, basicAuthEnabled: boolean, basicAuthUsername: string, basicAuthPassword: string, debugLogsEnabled: boolean, gqlDebugLogsEnabled: boolean, systemTrayEnabled: boolean, backupPath: string, backupTime: string, backupInterval: number, backupTTL: number } } };
+export type ResetServerSettingsMutation = { __typename?: 'Mutation', resetSettings: { __typename?: 'ResetSettingsPayload', clientMutationId?: string | null, settings: { __typename?: 'SettingsType', ip: string, port: number, socksProxyEnabled: boolean, socksProxyHost: string, socksProxyPort: string, webUIFlavor: WebUiFlavor, initialOpenInBrowserEnabled: boolean, webUIInterface: WebUiInterface, electronPath: string, webUIChannel: WebUiChannel, webUIUpdateCheckInterval: number, downloadAsCbz: boolean, downloadsPath: string, autoDownloadNewChapters: boolean, excludeEntryWithUnreadChapters: boolean, autoDownloadAheadLimit: number, maxSourcesInParallel: number, excludeUnreadChapters: boolean, excludeNotStarted: boolean, excludeCompleted: boolean, globalUpdateInterval: number, updateMangas: boolean, basicAuthEnabled: boolean, basicAuthUsername: string, basicAuthPassword: string, debugLogsEnabled: boolean, gqlDebugLogsEnabled: boolean, systemTrayEnabled: boolean, backupPath: string, backupTime: string, backupInterval: number, backupTTL: number, localSourcePath: string } } };
export type UpdateServerSettingsMutationVariables = Exact<{
input: SetSettingsInput;
}>;
-export type UpdateServerSettingsMutation = { __typename?: 'Mutation', setSettings: { __typename?: 'SetSettingsPayload', clientMutationId?: string | null, settings: { __typename?: 'SettingsType', ip: string, port: number, socksProxyEnabled: boolean, socksProxyHost: string, socksProxyPort: string, webUIFlavor: WebUiFlavor, initialOpenInBrowserEnabled: boolean, webUIInterface: WebUiInterface, electronPath: string, webUIChannel: WebUiChannel, webUIUpdateCheckInterval: number, downloadAsCbz: boolean, downloadsPath: string, autoDownloadNewChapters: boolean, excludeEntryWithUnreadChapters: boolean, autoDownloadAheadLimit: number, maxSourcesInParallel: number, excludeUnreadChapters: boolean, excludeNotStarted: boolean, excludeCompleted: boolean, globalUpdateInterval: number, updateMangas: boolean, basicAuthEnabled: boolean, basicAuthUsername: string, basicAuthPassword: string, debugLogsEnabled: boolean, gqlDebugLogsEnabled: boolean, systemTrayEnabled: boolean, backupPath: string, backupTime: string, backupInterval: number, backupTTL: number } } };
+export type UpdateServerSettingsMutation = { __typename?: 'Mutation', setSettings: { __typename?: 'SetSettingsPayload', clientMutationId?: string | null, settings: { __typename?: 'SettingsType', ip: string, port: number, socksProxyEnabled: boolean, socksProxyHost: string, socksProxyPort: string, webUIFlavor: WebUiFlavor, initialOpenInBrowserEnabled: boolean, webUIInterface: WebUiInterface, electronPath: string, webUIChannel: WebUiChannel, webUIUpdateCheckInterval: number, downloadAsCbz: boolean, downloadsPath: string, autoDownloadNewChapters: boolean, excludeEntryWithUnreadChapters: boolean, autoDownloadAheadLimit: number, maxSourcesInParallel: number, excludeUnreadChapters: boolean, excludeNotStarted: boolean, excludeCompleted: boolean, globalUpdateInterval: number, updateMangas: boolean, basicAuthEnabled: boolean, basicAuthUsername: string, basicAuthPassword: string, debugLogsEnabled: boolean, gqlDebugLogsEnabled: boolean, systemTrayEnabled: boolean, backupPath: string, backupTime: string, backupInterval: number, backupTTL: number, localSourcePath: string } } };
export type GetSourceMangasFetchMutationVariables = Exact<{
input: FetchSourceMangaInput;
@@ -2591,7 +2591,7 @@ export type GetWebuiUpdateStatusQuery = { __typename?: 'Query', getWebUIUpdateSt
export type GetServerSettingsQueryVariables = Exact<{ [key: string]: never; }>;
-export type GetServerSettingsQuery = { __typename?: 'Query', settings: { __typename?: 'SettingsType', ip: string, port: number, socksProxyEnabled: boolean, socksProxyHost: string, socksProxyPort: string, webUIFlavor: WebUiFlavor, initialOpenInBrowserEnabled: boolean, webUIInterface: WebUiInterface, electronPath: string, webUIChannel: WebUiChannel, webUIUpdateCheckInterval: number, downloadAsCbz: boolean, downloadsPath: string, autoDownloadNewChapters: boolean, excludeEntryWithUnreadChapters: boolean, autoDownloadAheadLimit: number, maxSourcesInParallel: number, excludeUnreadChapters: boolean, excludeNotStarted: boolean, excludeCompleted: boolean, globalUpdateInterval: number, updateMangas: boolean, basicAuthEnabled: boolean, basicAuthUsername: string, basicAuthPassword: string, debugLogsEnabled: boolean, gqlDebugLogsEnabled: boolean, systemTrayEnabled: boolean, backupPath: string, backupTime: string, backupInterval: number, backupTTL: number } };
+export type GetServerSettingsQuery = { __typename?: 'Query', settings: { __typename?: 'SettingsType', ip: string, port: number, socksProxyEnabled: boolean, socksProxyHost: string, socksProxyPort: string, webUIFlavor: WebUiFlavor, initialOpenInBrowserEnabled: boolean, webUIInterface: WebUiInterface, electronPath: string, webUIChannel: WebUiChannel, webUIUpdateCheckInterval: number, downloadAsCbz: boolean, downloadsPath: string, autoDownloadNewChapters: boolean, excludeEntryWithUnreadChapters: boolean, autoDownloadAheadLimit: number, maxSourcesInParallel: number, excludeUnreadChapters: boolean, excludeNotStarted: boolean, excludeCompleted: boolean, globalUpdateInterval: number, updateMangas: boolean, basicAuthEnabled: boolean, basicAuthUsername: string, basicAuthPassword: string, debugLogsEnabled: boolean, gqlDebugLogsEnabled: boolean, systemTrayEnabled: boolean, backupPath: string, backupTime: string, backupInterval: number, backupTTL: number, localSourcePath: string } };
export type GetSourceQueryVariables = Exact<{
id: Scalars['LongString']['input'];
diff --git a/src/screens/Settings.tsx b/src/screens/Settings.tsx
index dbe240e8..783363da 100644
--- a/src/screens/Settings.tsx
+++ b/src/screens/Settings.tsx
@@ -6,14 +6,12 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
-import { useContext, useEffect, useMemo, useState } from 'react';
+import { useContext, useEffect, useMemo } from 'react';
import AutoStoriesIcon from '@mui/icons-material/AutoStories';
import List from '@mui/material/List';
import ListAltIcon from '@mui/icons-material/ListAlt';
import BackupIcon from '@mui/icons-material/Backup';
import Brightness6Icon from '@mui/icons-material/Brightness6';
-import DnsIcon from '@mui/icons-material/Dns';
-import EditIcon from '@mui/icons-material/Edit';
import InfoIcon from '@mui/icons-material/Info';
import CachedIcon from '@mui/icons-material/Cached';
import ListItem from '@mui/material/ListItem';
@@ -21,21 +19,14 @@ import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction';
import Switch from '@mui/material/Switch';
-import Button from '@mui/material/Button';
-import IconButton from '@mui/material/IconButton';
-import Dialog from '@mui/material/Dialog';
-import DialogActions from '@mui/material/DialogActions';
-import DialogContent from '@mui/material/DialogContent';
-import DialogContentText from '@mui/material/DialogContentText';
-import TextField from '@mui/material/TextField';
import FavoriteIcon from '@mui/icons-material/Favorite';
-import { Link, MenuItem, Select, Tooltip } from '@mui/material';
+import { Link, MenuItem, Select } from '@mui/material';
import { useTranslation } from 'react-i18next';
import LanguageIcon from '@mui/icons-material/Language';
import CollectionsOutlinedBookmarkIcon from '@mui/icons-material/CollectionsBookmarkOutlined';
import GetAppOutlinedIcon from '@mui/icons-material/GetAppOutlined';
import ViewModuleIcon from '@mui/icons-material/ViewModule';
-import { requestManager } from '@/lib/requests/RequestManager.ts';
+import DnsIcon from '@mui/icons-material/Dns';
import { langCodeToName } from '@/util/language';
import { useLocalStorage } from '@/util/useLocalStorage';
import { ListItemLink } from '@/components/util/ListItemLink';
@@ -53,191 +44,133 @@ export function Settings() {
}, [t]);
const { darkTheme, setDarkTheme } = useContext(DarkTheme);
- const [serverAddress, setServerAddress] = useLocalStorage('serverBaseURL', '');
const [showNsfw, setShowNsfw] = useLocalStorage('showNsfw', true);
const [useCache, setUseCache] = useLocalStorage('useCache', true);
- const [dialogOpen, setDialogOpen] = useState(false);
- const [dialogValue, setDialogValue] = useState(serverAddress);
const DEFAULT_ITEM_WIDTH = 300;
const itemWidthIcon = useMemo(() => , []);
const [itemWidth, setItemWidth] = useLocalStorage('ItemWidth', DEFAULT_ITEM_WIDTH);
- const handleDialogOpen = () => {
- setDialogValue(serverAddress);
- setDialogOpen(true);
- };
-
- const handleDialogCancel = () => {
- setDialogOpen(false);
- };
-
- const handleDialogSubmit = () => {
- setDialogOpen(false);
- const serverBaseUrl = dialogValue.replaceAll(/(\/)+$/g, '');
- setServerAddress(serverBaseUrl);
- requestManager.updateClient({ baseURL: serverBaseUrl });
- };
-
return (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- setDarkTheme(!darkTheme)} />
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ setDarkTheme(!darkTheme)} />
+
+
+
+
+
+
+
+
-
-
-
-
-
-
- setShowNsfw(!showNsfw)} />
-
-
-
-
-
-
-
-
- setUseCache(!useCache)} />
-
-
-
-
-
-
-
- {t('settings.label.language_description')}
-
- {t('global.language.title.weblate')}
-
- >
- }
- />
-
-
-
-
-
-
-
-
-
-
-
- {
- handleDialogOpen();
- }}
- size="large"
- >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
+
+ setShowNsfw(!showNsfw)} />
+
+
+
+
+
+
+
+
+ setUseCache(!useCache)} />
+
+
+
+
+
+
+
+ {t('settings.label.language_description')}
+
+ {t('global.language.title.weblate')}
+
+ >
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/src/screens/settings/About.tsx b/src/screens/settings/About.tsx
index 85cad6e1..3d0f5fcc 100644
--- a/src/screens/settings/About.tsx
+++ b/src/screens/settings/About.tsx
@@ -45,7 +45,7 @@ export function About() {
diff --git a/src/screens/settings/Backup.tsx b/src/screens/settings/Backup.tsx
index c0b93392..47524625 100644
--- a/src/screens/settings/Backup.tsx
+++ b/src/screens/settings/Backup.tsx
@@ -21,7 +21,7 @@ import { ListItemLink } from '@/components/util/ListItemLink';
import { NavBarContext, useSetDefaultBackTo } from '@/components/context/NavbarContext';
import { BackupRestoreState } from '@/lib/graphql/generated/graphql.ts';
import { Progress } from '@/components/util/Progress.tsx';
-import { ServerDirSetting } from '@/components/settings/ServerDirSetting.tsx';
+import { TextSetting } from '@/components/settings/TextSetting.tsx';
import { NumberSetting } from '@/components/settings/NumberSetting.tsx';
import { TimeSetting } from '@/components/settings/TimeSetting.tsx';
import { ServerSettings } from '@/typings.ts';
@@ -188,11 +188,11 @@ export function Backup() {
}
>
- updateSetting('backupPath', path)}
+ value={backupSettings?.backupPath}
+ handleChange={(path) => updateSetting('backupPath', path)}
/>
{
return (
- updateSetting('downloadsPath', path)}
+ value={downloadSettings?.downloadsPath}
+ handleChange={(path) => updateSetting('downloadsPath', path)}
/>
diff --git a/src/screens/settings/ServerSettings.tsx b/src/screens/settings/ServerSettings.tsx
new file mode 100644
index 00000000..58feb5a7
--- /dev/null
+++ b/src/screens/settings/ServerSettings.tsx
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) Contributors to the Suwayomi project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+import { useTranslation } from 'react-i18next';
+import { useContext, useEffect } from 'react';
+import { List, ListItem, ListItemText, Switch } from '@mui/material';
+import ListSubheader from '@mui/material/ListSubheader';
+import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction';
+import { NavBarContext, useSetDefaultBackTo } from '@/components/context/NavbarContext.tsx';
+import { requestManager } from '@/lib/requests/RequestManager.ts';
+import { useLocalStorage } from '@/util/useLocalStorage.tsx';
+import { TextSetting } from '@/components/settings/TextSetting.tsx';
+import { ServerSettings as GqlServerSettings } from '@/typings.ts';
+import { NumberSetting } from '@/components/settings/NumberSetting.tsx';
+
+type ServerSettingsType = Pick<
+ GqlServerSettings,
+ | 'ip'
+ | 'port'
+ | 'socksProxyEnabled'
+ | 'socksProxyHost'
+ | 'socksProxyPort'
+ | 'debugLogsEnabled'
+ | 'gqlDebugLogsEnabled'
+ | 'systemTrayEnabled'
+ | 'basicAuthEnabled'
+ | 'basicAuthUsername'
+ | 'basicAuthPassword'
+ | 'maxSourcesInParallel'
+ | 'localSourcePath'
+>;
+
+const extractDownloadSettings = (settings: GqlServerSettings): ServerSettingsType => ({
+ ip: settings.ip,
+ port: settings.port,
+ socksProxyEnabled: settings.socksProxyEnabled,
+ socksProxyHost: settings.socksProxyHost,
+ socksProxyPort: settings.socksProxyPort,
+ debugLogsEnabled: settings.debugLogsEnabled,
+ gqlDebugLogsEnabled: settings.gqlDebugLogsEnabled,
+ systemTrayEnabled: settings.systemTrayEnabled,
+ basicAuthEnabled: settings.basicAuthEnabled,
+ basicAuthUsername: settings.basicAuthUsername,
+ basicAuthPassword: settings.basicAuthPassword,
+ maxSourcesInParallel: settings.maxSourcesInParallel,
+ localSourcePath: settings.localSourcePath,
+});
+
+export const ServerSettings = () => {
+ const { t } = useTranslation();
+ const { setTitle, setAction } = useContext(NavBarContext);
+
+ useSetDefaultBackTo('settings');
+
+ useEffect(() => {
+ setTitle(t('settings.server.title.settings'));
+ setAction(null);
+ }, [t]);
+
+ const { data } = requestManager.useGetServerSettings();
+ const serverSettings = data ? extractDownloadSettings(data.settings) : undefined;
+ const [mutateSettings] = requestManager.useUpdateServerSettings();
+
+ const [serverAddress, setServerAddress] = useLocalStorage('serverBaseURL', '');
+
+ const handleServerAddressChange = (address: string) => {
+ const serverBaseUrl = address.replaceAll(/(\/)+$/g, '');
+ setServerAddress(serverBaseUrl);
+ requestManager.updateClient({ baseURL: serverBaseUrl });
+ };
+
+ const updateSetting = (
+ setting: Setting,
+ value: ServerSettingsType[Setting],
+ ) => {
+ mutateSettings({ variables: { input: { settings: { [setting]: value } } } });
+ };
+
+ return (
+
+
+ {t('global.label.client')}
+
+ }
+ >
+
+
+
+ {t('settings.server.requests.title')}
+
+ }
+ >
+ updateSetting('maxSourcesInParallel', parallelSources)}
+ />
+
+
+ {t('settings.server.requests.title')}
+
+ }
+ >
+ updateSetting('localSourcePath', path)}
+ />
+
+
+ {t('settings.server.address.server.title')}
+
+ }
+ >
+ updateSetting('ip', ip)}
+ value={serverSettings?.ip}
+ placeholder="0.0.0.0"
+ />
+ updateSetting('port', port)}
+ value={serverSettings?.port ?? 4567}
+ defaultValue={4567}
+ valueUnit={t('settings.server.address.server.label.port')}
+ />
+
+
+ {t('settings.server.socks_proxy.title')}
+
+ }
+ >
+
+
+
+ updateSetting('socksProxyEnabled', e.target.checked)}
+ />
+
+
+ {!!serverSettings?.socksProxyEnabled && (
+ <>
+ updateSetting('socksProxyHost', proxyHost)}
+ />
+ updateSetting('socksProxyPort', proxyPort)}
+ />
+ >
+ )}
+
+
+ {t('settings.server.auth.title')}
+
+ }
+ >
+
+
+
+ updateSetting('basicAuthEnabled', e.target.checked)}
+ />
+
+
+ {!!serverSettings?.basicAuthEnabled && (
+ <>
+ updateSetting('basicAuthUsername', authUsername)}
+ />
+ updateSetting('basicAuthPassword', authPassword)}
+ />
+ >
+ )}
+
+
+ {t('settings.server.misc.title')}
+
+ }
+ >
+
+
+
+ updateSetting('debugLogsEnabled', e.target.checked)}
+ />
+
+
+
+
+
+ updateSetting('gqlDebugLogsEnabled', e.target.checked)}
+ />
+
+
+
+
+
+ updateSetting('systemTrayEnabled', e.target.checked)}
+ />
+
+
+
+
+ );
+};