Backend Settings

This commit is contained in:
glax 2025-03-20 00:07:20 +01:00
parent 2092db2ba3
commit 187dd22027
8 changed files with 229 additions and 29 deletions

View File

@ -58,7 +58,7 @@ export function getData(uri: string) : Promise<object> {
return makeRequest("GET", uri, null) as Promise<object>;
}
export function postData(uri: string, content: object | string | number) : Promise<object> {
export function postData(uri: string, content: object | string | number | boolean) : Promise<object> {
return makeRequest("POST", uri, content) as Promise<object>;
}
@ -66,15 +66,15 @@ export function deleteData(uri: string) : Promise<void> {
return makeRequest("DELETE", uri, null) as Promise<void>;
}
export function patchData(uri: string, content: object | string | number) : Promise<object> {
export function patchData(uri: string, content: object | string | number | boolean) : Promise<object> {
return makeRequest("patch", uri, content) as Promise<object>;
}
export function putData(uri: string, content: object | string | number) : Promise<object> {
export function putData(uri: string, content: object | string | number | boolean) : Promise<object> {
return makeRequest("PUT", uri, content) as Promise<object>;
}
function makeRequest(method: string, uri: string, content: object | string | number | null) : Promise<object | void> {
function makeRequest(method: string, uri: string, content: object | string | number | null | boolean) : Promise<object | void> {
return fetch(uri,
{
method: method,
@ -139,8 +139,8 @@ export const checkConnection = async (apiUri: string): Promise<boolean> =>{
{
method: 'GET',
})
.then((response) =>{
return response.type != "error";
.then((response) => {
return response.ok;
})
.catch(() => {
return Promise.reject();

View File

@ -0,0 +1,61 @@
import {deleteData, getData, patchData} from "../App";
import IRequestLimits, {RequestType} from "./interfaces/IRequestLimits";
import IBackendSettings from "./interfaces/IBackendSettings";
export default class BackendSettings {
static async GetSettings(apiUri: string) : Promise<IBackendSettings> {
return getData(`${apiUri}/v2/Settings`).then((s) => s as IBackendSettings);
}
static async GetUserAgent(apiUri: string) : Promise<string> {
return getData(`${apiUri}/v2/Settings/UserAgent`).then((text) => text as unknown as string);
}
static async UpdateUserAgent(apiUri: string, userAgent: string) {
return patchData(`${apiUri}/v2/Settings/UserAgent`, userAgent);
}
static async ResetUserAgent(apiUri: string) {
return deleteData(`${apiUri}/v2/Settings/UserAgent`);
}
static async GetRequestLimits(apiUri: string) : Promise<IRequestLimits> {
return getData(`${apiUri}/v2/Settings/RequestLimits`).then((limits) => limits as IRequestLimits);
}
static async ResetRequestLimits(apiUri: string) {
return deleteData(`${apiUri}/v2/Settings/RequestLimits`);
}
static async UpdateRequestLimit(apiUri: string, requestType: RequestType, value: number) {
return patchData(`${apiUri}/v2/Settings/RequestLimits/${requestType}`, value);
}
static async ResetRequestLimit(apiUri: string, requestType: RequestType) {
return deleteData(`${apiUri}/v2/Settings/RequestLimits/${requestType}`);
}
static async GetImageCompressionValue(apiUri: string) : Promise<number> {
return getData(`${apiUri}/v2/Settings/ImageCompression`).then((n) => n as unknown as number);
}
static async UpdateImageCompressionValue(apiUri: string, value: number) {
return patchData(`${apiUri}/v2/Settings/ImageCompression`, value);
}
static async GetBWImageToggle(apiUri: string) : Promise<boolean> {
return getData(`${apiUri}/v2/Settings/BWImages`).then((state) => state as unknown as boolean);
}
static async UpdateBWImageToggle(apiUri: string, value: boolean) {
return patchData(`${apiUri}/v2/Settings/BWImages`, value);
}
static async GetAprilFoolsToggle(apiUri: string) : Promise<boolean> {
return getData(`${apiUri}/v2/Settings/AprilFoolsMode`).then((state) => state as unknown as boolean);
}
static async UpdateAprilFoolsToggle(apiUri: string, value: boolean) {
return patchData(`${apiUri}/v2/Settings/AprilFoolsMode`, value);
}
}

View File

@ -1,22 +1,35 @@
import IFrontendSettings from "./interfaces/IFrontendSettings";
import '../styles/settings.css';
import '../styles/react-toggle.css';
import React, {useEffect, useState} from "react";
import React, {LegacyRef, MutableRefObject, Ref, RefObject, useEffect, useRef, useState} from "react";
import INotificationConnector, {NotificationConnectorItem} from "./interfaces/INotificationConnector";
import NotificationConnectorFunctions from "./NotificationConnectorFunctions";
import ILocalLibrary, {LocalLibraryItem} from "./interfaces/ILocalLibrary";
import LocalLibraryFunctions from "./LocalLibraryFunctions";
import IBackendSettings from "./interfaces/IBackendSettings";
import BackendSettings from "./BackendSettingsFunctions";
import Toggle from "react-toggle";
import Loader from "./Loader";
import {RequestType} from "./interfaces/IRequestLimits";
export default function Settings({backendConnected, apiUri, frontendSettings, setFrontendSettings} : {backendConnected: boolean, apiUri: string, frontendSettings: IFrontendSettings, setFrontendSettings: (settings: IFrontendSettings) => void}) {
let [showSettings, setShowSettings] = useState<boolean>(false);
let [notificationConnectors, setNotificationConnectors] = useState<INotificationConnector[]>([]);
let [localLibraries, setLocalLibraries] = useState<ILocalLibrary[]>([]);
export default function Settings({ backendConnected, apiUri, frontendSettings, setFrontendSettings } : {
backendConnected: boolean,
apiUri: string,
frontendSettings: IFrontendSettings,
setFrontendSettings: (settings: IFrontendSettings) => void
}) {
const [showSettings, setShowSettings] = useState<boolean>(false);
const [loadingBackend, setLoadingBackend] = useState(false);
const [backendSettings, setBackendSettings] = useState<IBackendSettings|null>(null);
const [notificationConnectors, setNotificationConnectors] = useState<INotificationConnector[]>([]);
const [localLibraries, setLocalLibraries] = useState<ILocalLibrary[]>([]);
useEffect(() => {
if(!backendConnected)
return;
NotificationConnectorFunctions.GetNotificationConnectors(apiUri).then(setNotificationConnectors);
LocalLibraryFunctions.GetLibraries(apiUri).then(setLocalLibraries);
BackendSettings.GetSettings(apiUri).then(setBackendSettings);
}, [backendConnected, showSettings]);
const dateToStr = (x: Date) => {
@ -26,6 +39,16 @@ export default function Settings({backendConnected, apiUri, frontendSettings, se
return ret;
}
const ChangeRequestLimit = (requestType: RequestType, limit: number) => {
if(backendSettings === null)
return;
setLoadingBackend(true);
BackendSettings.UpdateRequestLimit(apiUri, requestType, limit)
.then(() => setBackendSettings({...backendSettings, [requestType]: requestType}))
.finally(() => setLoadingBackend(false));
}
const ref : React.LegacyRef<HTMLInputElement> | undefined = useRef<HTMLInputElement>(null);
return (
<div id="Settings">
<div onClick={() => setShowSettings(true)}>
@ -38,14 +61,99 @@ export default function Settings({backendConnected, apiUri, frontendSettings, se
<img alt="Close Settings" className="close" src="../media/close-x.svg" onClick={() => setShowSettings(false)}/>
</div>
<div id="SettingsPopUpBody" className="popupBody">
<Loader loading={loadingBackend} style={{width: "64px", height: "64px", margin: "calc(sin(70)*(50% - 40px))", zIndex: 100, padding: 0, borderRadius: "50%", border: 0}}/>
<div className="settings-apiuri">
<label>ApiUri</label>
<input type="url" defaultValue={frontendSettings.apiUri} onChange={(e) => setFrontendSettings({...frontendSettings, apiUri:e.currentTarget.value})} id="ApiUri" />
</div>
<div className="settings-apiuri">
<div className="settings-jobinterval">
<label>Default Job-Interval</label>
<input type="time" min="00:30" max="23:59" defaultValue={dateToStr(new Date(frontendSettings.jobInterval))} onChange={(e) => setFrontendSettings({...frontendSettings, jobInterval: new Date(e.currentTarget.valueAsNumber-60*60*1000) ?? frontendSettings.jobInterval})}/>
</div>
<div className="settings-bwimages">
<h3>B/W Images</h3>
<Toggle defaultChecked={backendSettings ? backendSettings.bwImages : false} disabled={backendSettings ? false : !loadingBackend}
onChange={(e) => {
if(backendSettings === null)
return;
setLoadingBackend(true);
BackendSettings.UpdateBWImageToggle(apiUri, e.target.checked)
.then(() => setBackendSettings({...backendSettings, bwImages: e.target.checked}))
.finally(() => setLoadingBackend(false));
}} />
</div>
<div className="settings-aprilfools">
<h3>April Fools Mode</h3>
<Toggle defaultChecked={backendSettings ? backendSettings.aprilFoolsMode : false} disabled={backendSettings ? false : !loadingBackend}
onChange={(e) => {
if(backendSettings === null)
return;
setLoadingBackend(true);
BackendSettings.UpdateAprilFoolsToggle(apiUri, e.target.checked)
.then(() => setBackendSettings({...backendSettings, aprilFoolsMode: e.target.checked}))
.finally(() => setLoadingBackend(false));
}} />
</div>
<div className="settings-imagecompression">
<h3>Image Compression</h3>
<Toggle defaultChecked={backendSettings ? backendSettings.compression < 100 : false} disabled={backendSettings ? false : !loadingBackend}
onChange={(e) => {
if(backendSettings === null)
return;
setLoadingBackend(true);
BackendSettings.UpdateImageCompressionValue(apiUri, e.target.checked ? 40 : 100)
.then(() => setBackendSettings({...backendSettings, compression: e.target.checked ? 40 : 100}))
.then(() => {
if(ref.current != null){
ref.current.value = e.target.checked ? "40" : "100";
ref.current.disabled = !e.target.checked;
}
})
.finally(() => setLoadingBackend(false));
}} />
<input ref={ref} type="number" min={0} max={100} defaultValue={backendSettings ? backendSettings.compression : 0} disabled={backendSettings ? false : !loadingBackend}
onChange={(e) => {
if(backendSettings === null)
return;
setLoadingBackend(true);
BackendSettings.UpdateImageCompressionValue(apiUri, e.currentTarget.valueAsNumber)
.then(() => setBackendSettings({...backendSettings, compression: e.currentTarget.valueAsNumber}))
.finally(() => setLoadingBackend(false));
}} />
</div>
<div className="settings-requestLimits">
<h3>Request Limits:</h3>
<label htmlFor="Default">Default</label>
<input id="Default" type="number" defaultValue={backendSettings ? backendSettings.requestLimits.Default : 0} disabled={backendSettings ? false : !loadingBackend}
onChange={(e) => ChangeRequestLimit(RequestType.Default, e.currentTarget.valueAsNumber)} />
<label htmlFor="MangaInfo">MangaInfo</label>
<input id="MangaInfo" type="number" defaultValue={backendSettings ? backendSettings.requestLimits.MangaInfo : 0} disabled={backendSettings ? false : !loadingBackend}
onChange={(e) => ChangeRequestLimit(RequestType.MangaInfo, e.currentTarget.valueAsNumber)} />
<label htmlFor="MangaDexFeed">MangaDexFeed</label>
<input id="MangaDexFeed" type="number" defaultValue={backendSettings ? backendSettings.requestLimits.MangaDexFeed : 0} disabled={backendSettings ? false : !loadingBackend}
onChange={(e) => ChangeRequestLimit(RequestType.MangaDexFeed, e.currentTarget.valueAsNumber)} />
<label htmlFor="MangaDexImage">MangaDexImage</label>
<input id="MangaDexImage" type="number" defaultValue={backendSettings ? backendSettings.requestLimits.MangaDexImage : 0} disabled={backendSettings ? false : !loadingBackend}
onChange={(e) => ChangeRequestLimit(RequestType.MangaDexImage, e.currentTarget.valueAsNumber)} />
<label htmlFor="MangaImage">MangaImage</label>
<input id="MangaImage" type="number" defaultValue={backendSettings ? backendSettings.requestLimits.MangaImage : 0} disabled={backendSettings ? false : !loadingBackend}
onChange={(e) => ChangeRequestLimit(RequestType.MangaImage, e.currentTarget.valueAsNumber)} />
<label htmlFor="MangaCover">MangaCover</label>
<input id="MangaCover" type="number" defaultValue={backendSettings ? backendSettings.requestLimits.MangaCover : 0} disabled={backendSettings ? false : !loadingBackend}
onChange={(e) => ChangeRequestLimit(RequestType.MangaCover, e.currentTarget.valueAsNumber)} />
</div>
<div className="settings-useragent">
<label>User Agent</label>
<input type="text" defaultValue={backendSettings ? backendSettings.userAgent : ""}
onSubmit={(e) => {
if(backendSettings === null)
return;
setLoadingBackend(true);
BackendSettings.UpdateUserAgent(apiUri, e.currentTarget.value)
.then(() => setBackendSettings({...backendSettings, userAgent: e.currentTarget.value}))
.finally(() => setLoadingBackend(false));
}} />
</div>
<h3>Notification Connectors:</h3>
{notificationConnectors.map(c => <NotificationConnectorItem apiUri={apiUri} notificationConnector={c} key={c.name} />)}
<NotificationConnectorItem apiUri={apiUri} notificationConnector={null} key="New Notification Connector" />

View File

@ -1,15 +0,0 @@
export default interface IBackendSettings {
"downloadLocation": string;
"userAgent": string;
"aprilFoolsMode": boolean;
"compression": number;
"bwImages": boolean;
"requestLimits": {
"MangaInfo": number;
"MangaDexFeed": number;
"MangaDexImage": number;
"MangaImage": number;
"MangaCover": number;
"Default": number
}
}

View File

@ -0,0 +1,17 @@
export default interface IBackendSettings {
downloadLocation: string;
workingDirectory: string;
userAgent: string;
aprilFoolsMode: boolean;
requestLimits: {
Default: number,
MangaInfo: number,
MangaDexFeed: number,
MangaDexImage: number,
MangaImage: number,
MangaCover: number,
};
compression: number;
bwImages: boolean;
startNewJobTimeoutMs: number;
}

View File

@ -0,0 +1,17 @@
export default interface IRequestLimits {
Default: number;
MangaDexFeed: number;
MangaImage: number;
MangaCover: number;
MangaDexImage: number;
MangaInfo: number;
}
export enum RequestType {
Default = "Default",
MangaDexFeed = "MangaDexFeed",
MangaImage = "MangaImage",
MangaCover = "MangaCover",
MangaDexImage = "MangaDexImage",
MangaInfo = "MangaInfo"
}

View File

@ -40,5 +40,8 @@
left: 0;
width: calc(100% - 30px);
height: calc(100% - 50px);
margin: 5px 15px;
padding: 5px 15px;
overflow-y: auto;
overflow-x: hidden;
scrollbar-width: thin;
}

View File

@ -43,6 +43,15 @@
#SettingsPopUpBody h1, #SettingsPopUpBody h2, #SettingsPopUpBody h3 {
border: 0;
margin: 5px 0 0 0;
margin: 5px 0 2px 0;
padding: 0;
}
.settings-requestLimits {
display: flex;
flex-direction: column;
}
.settings-requestLimits input {
width: min-content;
}