mirror of
https://github.com/C9Glax/tranga-website.git
synced 2025-05-09 00:22:09 +02:00
Compare commits
No commits in common. "211db3d4d5d4f2f5c456dcb8897d6657509fd948" and "60f957ede2350cf02f59740345f67c923729c3c0" have entirely different histories.
211db3d4d5
...
60f957ede2
@ -42,9 +42,9 @@
|
|||||||
|----------------------------------------------------------------------------|----------------------------------------------------------------------------|----------------------------------------------------------------------------|
|
|----------------------------------------------------------------------------|----------------------------------------------------------------------------|----------------------------------------------------------------------------|
|
||||||
|  |  |  |
|
|  |  |  |
|
||||||
|
|
||||||
| | Settings Dialog | |
|
| | Rudimentary Settings | |
|
||||||
|-|----------------------------------------------------------------------------|-|
|
|-|----------------------------------------------------------------------------|-|
|
||||||
| |  | |
|
| |  | |
|
||||||
|
|
||||||
|
|
||||||
## About The Project
|
## About The Project
|
||||||
|
BIN
Screenshots/Screenshot 2025-03-19 at 02-42-21 Tranga.png
Normal file
BIN
Screenshots/Screenshot 2025-03-19 at 02-42-21 Tranga.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 159 KiB |
Binary file not shown.
Before Width: | Height: | Size: 192 KiB |
@ -16,16 +16,18 @@ export default function App(){
|
|||||||
const [updateInterval, setUpdateInterval] = React.useState<number | undefined>(undefined);
|
const [updateInterval, setUpdateInterval] = React.useState<number | undefined>(undefined);
|
||||||
const checkConnectedInterval = 1000;
|
const checkConnectedInterval = 1000;
|
||||||
|
|
||||||
|
const apiUri = frontendSettings.apiUri;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCookie('apiUri', frontendSettings.apiUri);
|
setCookie('apiUri', frontendSettings.apiUri);
|
||||||
setCookie('jobInterval', frontendSettings.jobInterval);
|
setCookie('jobInterval', frontendSettings.jobInterval);
|
||||||
updateConnected(frontendSettings.apiUri, connected, setConnected);
|
updateConnected(apiUri, connected, setConnected);
|
||||||
}, [frontendSettings]);
|
}, [frontendSettings]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(updateInterval === undefined){
|
if(updateInterval === undefined){
|
||||||
setUpdateInterval(setInterval(() => {
|
setUpdateInterval(setInterval(() => {
|
||||||
updateConnected(frontendSettings.apiUri, connected, setConnected);
|
updateConnected(apiUri, connected, setConnected);
|
||||||
}, checkConnectedInterval));
|
}, checkConnectedInterval));
|
||||||
}else{
|
}else{
|
||||||
clearInterval(updateInterval);
|
clearInterval(updateInterval);
|
||||||
@ -34,23 +36,23 @@ export default function App(){
|
|||||||
}, [connected]);
|
}, [connected]);
|
||||||
|
|
||||||
return(<div>
|
return(<div>
|
||||||
<Header apiUri={frontendSettings.apiUri} backendConnected={connected} settings={frontendSettings} setFrontendSettings={setFrontendSettings} />
|
<Header apiUri={apiUri} backendConnected={connected} settings={frontendSettings} setFrontendSettings={setFrontendSettings} />
|
||||||
{connected
|
{connected
|
||||||
? <>
|
? <>
|
||||||
{showSearch
|
{showSearch
|
||||||
? <>
|
? <>
|
||||||
<Search apiUri={frontendSettings.apiUri} jobInterval={frontendSettings.jobInterval} closeSearch={() => setShowSearch(false)} />
|
<Search apiUri={apiUri} jobInterval={frontendSettings.jobInterval} closeSearch={() => setShowSearch(false)} />
|
||||||
<hr/>
|
<hr/>
|
||||||
</>
|
</>
|
||||||
: <></>}
|
: <></>}
|
||||||
<MonitorJobsList apiUri={frontendSettings.apiUri} onStartSearch={() => setShowSearch(true)} connectedToBackend={connected} checkConnectedInterval={checkConnectedInterval} />
|
<MonitorJobsList apiUri={apiUri} onStartSearch={() => setShowSearch(true)} connectedToBackend={connected} checkConnectedInterval={checkConnectedInterval} />
|
||||||
</>
|
</>
|
||||||
: <>
|
: <>
|
||||||
<h1>No connection to the Backend.</h1>
|
<h1>No connection to the Backend.</h1>
|
||||||
<h3>Check the Settings ApiUri.</h3>
|
<h3>Check the Settings ApiUri.</h3>
|
||||||
<Loader loading={true} />
|
<Loader loading={true} />
|
||||||
</>}
|
</>}
|
||||||
<Footer apiUri={frontendSettings.apiUri} connectedToBackend={connected} checkConnectedInterval={checkConnectedInterval} />
|
<Footer apiUri={apiUri} connectedToBackend={connected} checkConnectedInterval={checkConnectedInterval} />
|
||||||
</div>)
|
</div>)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +60,7 @@ export function getData(uri: string) : Promise<object> {
|
|||||||
return makeRequest("GET", uri, null) as Promise<object>;
|
return makeRequest("GET", uri, null) as Promise<object>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function postData(uri: string, content: object | string | number | boolean) : Promise<object> {
|
export function postData(uri: string, content: object | string | number) : Promise<object> {
|
||||||
return makeRequest("POST", uri, content) as Promise<object>;
|
return makeRequest("POST", uri, content) as Promise<object>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,20 +68,15 @@ export function deleteData(uri: string) : Promise<void> {
|
|||||||
return makeRequest("DELETE", uri, null) as Promise<void>;
|
return makeRequest("DELETE", uri, null) as Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function patchData(uri: string, content: object | string | number | boolean) : Promise<object> {
|
export function patchData(uri: string, content: object | string | number) : Promise<object> {
|
||||||
return makeRequest("patch", uri, content) as Promise<object>;
|
return makeRequest("patch", uri, content) as Promise<object>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function putData(uri: string, content: object | string | number | boolean) : Promise<object> {
|
export function putData(uri: string, content: object | string | number) : Promise<object> {
|
||||||
return makeRequest("PUT", uri, content) as Promise<object>;
|
return makeRequest("PUT", uri, content) as Promise<object>;
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentlyRequestedEndpoints: string[] = [];
|
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> {
|
|
||||||
const id = method + uri;
|
|
||||||
if(currentlyRequestedEndpoints.find(x => x == id) != undefined)
|
|
||||||
return Promise.reject(`Already requested: ${method} ${uri}`);
|
|
||||||
currentlyRequestedEndpoints.push(id);
|
|
||||||
return fetch(uri,
|
return fetch(uri,
|
||||||
{
|
{
|
||||||
method: method,
|
method: method,
|
||||||
@ -118,7 +115,7 @@ function makeRequest(method: string, uri: string, content: object | string | num
|
|||||||
.catch(function(err : Error){
|
.catch(function(err : Error){
|
||||||
console.error(`Error ${method}ing Data ${uri}\n${err}`);
|
console.error(`Error ${method}ing Data ${uri}\n${err}`);
|
||||||
return Promise.reject();
|
return Promise.reject();
|
||||||
}).finally(() => currentlyRequestedEndpoints.splice(currentlyRequestedEndpoints.indexOf(id), 1));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isValidUri(uri: string) : boolean{
|
export function isValidUri(uri: string) : boolean{
|
||||||
@ -144,8 +141,8 @@ export const checkConnection = async (apiUri: string): Promise<boolean> =>{
|
|||||||
{
|
{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) =>{
|
||||||
return response.ok;
|
return response.type != "error";
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
return Promise.reject();
|
return Promise.reject();
|
||||||
|
@ -1,61 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -26,7 +26,7 @@ export default function Footer({connectedToBackend, apiUri, checkConnectedInterv
|
|||||||
if(countUpdateInterval === undefined){
|
if(countUpdateInterval === undefined){
|
||||||
setCountUpdateInterval(setInterval(() => {
|
setCountUpdateInterval(setInterval(() => {
|
||||||
UpdateBackendState();
|
UpdateBackendState();
|
||||||
}, checkConnectedInterval * 5));
|
}, checkConnectedInterval));
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
clearInterval(countUpdateInterval);
|
clearInterval(countUpdateInterval);
|
||||||
|
@ -47,11 +47,4 @@ export default class LocalLibraryFunctions
|
|||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static async UpdateLibrary(apiUri: string, libraryId: string, record: INewLibraryRecord): Promise<void> {
|
|
||||||
return patchData(`${apiUri}/v2/LocalLibraries/${libraryId}`, record)
|
|
||||||
.then(()=> {
|
|
||||||
return Promise.resolve()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import React, {ReactElement, useEffect, useState} from 'react';
|
import React, {EventHandler, ReactElement, useEffect, useState} from 'react';
|
||||||
import JobFunctions from './JobFunctions';
|
import JobFunctions from './JobFunctions';
|
||||||
import '../styles/monitorMangaList.css';
|
import '../styles/monitorMangaList.css';
|
||||||
import {JobType} from "./interfaces/Jobs/IJob";
|
import {JobType} from "./interfaces/Jobs/IJob";
|
||||||
|
@ -14,7 +14,7 @@ export default function QueuePopUp({connectedToBackend, children, apiUri, checkC
|
|||||||
const [queueListInterval, setQueueListInterval] = React.useState<number | undefined>(undefined);
|
const [queueListInterval, setQueueListInterval] = React.useState<number | undefined>(undefined);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(connectedToBackend && showQueuePopup) {
|
if(connectedToBackend) {
|
||||||
UpdateMonitoringJobsList();
|
UpdateMonitoringJobsList();
|
||||||
if(queueListInterval === undefined){
|
if(queueListInterval === undefined){
|
||||||
setQueueListInterval(setInterval(() => {
|
setQueueListInterval(setInterval(() => {
|
||||||
@ -25,7 +25,7 @@ export default function QueuePopUp({connectedToBackend, children, apiUri, checkC
|
|||||||
clearInterval(queueListInterval);
|
clearInterval(queueListInterval);
|
||||||
setQueueListInterval(undefined);
|
setQueueListInterval(undefined);
|
||||||
}
|
}
|
||||||
}, [connectedToBackend, showQueuePopup]);
|
}, [connectedToBackend]);
|
||||||
|
|
||||||
function UpdateMonitoringJobsList(){
|
function UpdateMonitoringJobsList(){
|
||||||
JobFunctions.GetJobsInState(apiUri, JobState.Waiting)
|
JobFunctions.GetJobsInState(apiUri, JobState.Waiting)
|
||||||
|
@ -127,7 +127,7 @@ export default function Search({apiUri, jobInterval, closeSearch} : {apiUri: str
|
|||||||
: selectedConnector.supportedLanguages.map(language => <option value={language} key={language}>{language}</option>)}
|
: selectedConnector.supportedLanguages.map(language => <option value={language} key={language}>{language}</option>)}
|
||||||
</select>
|
</select>
|
||||||
<button id="Searchbox-button" type="submit" onClick={ExecuteSearch} disabled={loading}>Search</button>
|
<button id="Searchbox-button" type="submit" onClick={ExecuteSearch} disabled={loading}>Search</button>
|
||||||
<Loader loading={loading} style={{width:"40px", height:"40px", zIndex: 50}}/>
|
<Loader loading={loading} style={{width:"40px", height:"40px"}}/>
|
||||||
</div>
|
</div>
|
||||||
<img alt="Close Search" id="closeSearch" src="../media/close-x.svg" onClick={closeSearch} />
|
<img alt="Close Search" id="closeSearch" src="../media/close-x.svg" onClick={closeSearch} />
|
||||||
<div id="SearchResults">
|
<div id="SearchResults">
|
||||||
|
@ -1,53 +1,20 @@
|
|||||||
import IFrontendSettings from "./interfaces/IFrontendSettings";
|
import IFrontendSettings from "./interfaces/IFrontendSettings";
|
||||||
import '../styles/settings.css';
|
import '../styles/settings.css';
|
||||||
import '../styles/react-toggle.css';
|
import '../styles/react-toggle.css';
|
||||||
import React, {useEffect, useRef, useState} from "react";
|
import React, {useEffect, useState} from "react";
|
||||||
import INotificationConnector, {NotificationConnectorItem} from "./interfaces/INotificationConnector";
|
import INotificationConnector, {NotificationConnectorItem} from "./interfaces/INotificationConnector";
|
||||||
import NotificationConnectorFunctions from "./NotificationConnectorFunctions";
|
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 } : {
|
export default function Settings({backendConnected, apiUri, frontendSettings, setFrontendSettings} : {backendConnected: boolean, apiUri: string, frontendSettings: IFrontendSettings, setFrontendSettings: (settings: IFrontendSettings) => void}) {
|
||||||
backendConnected: boolean,
|
let [showSettings, setShowSettings] = useState<boolean>(false);
|
||||||
apiUri: string,
|
let [notificationConnectors, setNotificationConnectors] = useState<INotificationConnector[]>([]);
|
||||||
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(() => {
|
useEffect(() => {
|
||||||
if(!backendConnected)
|
if(!backendConnected)
|
||||||
return;
|
return;
|
||||||
NotificationConnectorFunctions.GetNotificationConnectors(apiUri).then(setNotificationConnectors);
|
NotificationConnectorFunctions.GetNotificationConnectors(apiUri).then(setNotificationConnectors);
|
||||||
LocalLibraryFunctions.GetLibraries(apiUri).then(setLocalLibraries);
|
}, []);
|
||||||
BackendSettings.GetSettings(apiUri).then(setBackendSettings);
|
|
||||||
}, [backendConnected, showSettings]);
|
|
||||||
|
|
||||||
const dateToStr = (x: Date) => {
|
|
||||||
const ret = (x.getHours() < 10 ? "0" + x.getHours() : x.getHours())
|
|
||||||
+ ":" +
|
|
||||||
(x.getMinutes() < 10 ? "0" + x.getMinutes() : x.getMinutes());
|
|
||||||
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 (
|
return (
|
||||||
<div id="Settings">
|
<div id="Settings">
|
||||||
@ -61,109 +28,25 @@ export default function Settings({ backendConnected, apiUri, frontendSettings, s
|
|||||||
<img alt="Close Settings" className="close" src="../media/close-x.svg" onClick={() => setShowSettings(false)}/>
|
<img alt="Close Settings" className="close" src="../media/close-x.svg" onClick={() => setShowSettings(false)}/>
|
||||||
</div>
|
</div>
|
||||||
<div id="SettingsPopUpBody" className="popupBody">
|
<div id="SettingsPopUpBody" className="popupBody">
|
||||||
<Loader loading={loadingBackend} style={{width: "64px", height: "64px", margin: "25vh calc(sin(70)*(50% - 40px))", zIndex: 100, padding: 0, borderRadius: "50%", border: 0, minWidth: "initial", maxWidth: "initial"}}/>
|
|
||||||
<div className="settings-apiuri">
|
<div className="settings-apiuri">
|
||||||
<h3>ApiUri</h3>
|
<label>ApiUri</label>
|
||||||
<input type="url" defaultValue={frontendSettings.apiUri} onChange={(e) => setFrontendSettings({...frontendSettings, apiUri:e.currentTarget.value})} id="ApiUri" />
|
<input type="url" defaultValue={frontendSettings.apiUri} onChange={(e) => {
|
||||||
|
let newSettings = frontendSettings;
|
||||||
|
newSettings.apiUri = e.currentTarget.value;
|
||||||
|
setFrontendSettings(newSettings);
|
||||||
|
}} id="ApiUri" />
|
||||||
</div>
|
</div>
|
||||||
<div className="settings-jobinterval">
|
<div className="settings-apiuri">
|
||||||
<h3>Default Job-Interval</h3>
|
<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})}/>
|
<input type="time" defaultValue={new Date(frontendSettings.jobInterval).getTime()} onChange={(e) => {
|
||||||
</div>
|
let newSettings = frontendSettings;
|
||||||
<div className="settings-bwimages">
|
newSettings.jobInterval = e.currentTarget.valueAsDate ?? frontendSettings.jobInterval;
|
||||||
<h3>B/W Images</h3>
|
setFrontendSettings(newSettings);
|
||||||
<Toggle defaultChecked={backendSettings ? backendSettings.bwImages : false} disabled={backendSettings ? false : !loadingBackend}
|
console.log(frontendSettings);
|
||||||
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-useragent">
|
|
||||||
<h3>User Agent</h3>
|
|
||||||
<input type="text" defaultValue={backendSettings ? backendSettings.userAgent : ""}
|
|
||||||
onChange={(e) => {
|
|
||||||
if(backendSettings === null)
|
|
||||||
return;
|
|
||||||
setLoadingBackend(true);
|
|
||||||
BackendSettings.UpdateUserAgent(apiUri, e.currentTarget.value)
|
|
||||||
.then(() => setBackendSettings({...backendSettings, userAgent: e.currentTarget.value}))
|
|
||||||
.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>
|
|
||||||
<h3>Notification Connectors:</h3>
|
|
||||||
{notificationConnectors.map(c => <NotificationConnectorItem apiUri={apiUri} notificationConnector={c} key={c.name} />)}
|
|
||||||
<NotificationConnectorItem apiUri={apiUri} notificationConnector={null} key="New Notification Connector" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3>Local Libraries:</h3>
|
|
||||||
{localLibraries.map(l => <LocalLibraryItem apiUri={apiUri} library={l} key={l.localLibraryId} />)}
|
|
||||||
<LocalLibraryItem apiUri={apiUri} library={null} key="New Local Library" />
|
|
||||||
</div>
|
</div>
|
||||||
|
{notificationConnectors.map(c => <NotificationConnectorItem apiUri={apiUri} notificationConnector={c} />)}
|
||||||
|
<NotificationConnectorItem apiUri={apiUri} notificationConnector={null} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
: null
|
: null
|
||||||
|
15
Website/modules/interfaces/IBackendSettings.ts
Normal file
15
Website/modules/interfaces/IBackendSettings.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -1,17 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
@ -1,8 +1,4 @@
|
|||||||
import {ReactElement, useState} from "react";
|
import {ReactElement} from "react";
|
||||||
import INewLibraryRecord, {Validate} from "./records/INewLibraryRecord";
|
|
||||||
import Loader from "../Loader";
|
|
||||||
import LocalLibraryFunctions from "../LocalLibraryFunctions";
|
|
||||||
import "../../styles/localLibrary.css";
|
|
||||||
|
|
||||||
export default interface ILocalLibrary {
|
export default interface ILocalLibrary {
|
||||||
localLibraryId: string;
|
localLibraryId: string;
|
||||||
@ -10,36 +6,9 @@ export default interface ILocalLibrary {
|
|||||||
libraryName: string;
|
libraryName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LocalLibraryItem({apiUri, library} : {apiUri: string, library: ILocalLibrary | null}) : ReactElement {
|
export function LocalLibrary(library: ILocalLibrary) : ReactElement {
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
return (<div key={library.localLibraryId}>
|
||||||
const [record, setRecord] = useState<INewLibraryRecord>({
|
<p className={"LocalLibraryFunctions-Name"}>{library.libraryName}</p>
|
||||||
path: library?.basePath ?? "",
|
<p className={"LocalLibraryFunctions-Path"}>{library.basePath}</p>
|
||||||
name: library?.libraryName ?? ""
|
|
||||||
});
|
|
||||||
|
|
||||||
return (<div className="LocalLibraryFunctions">
|
|
||||||
<label htmlFor="LocalLibraryFunctions-Name">Library Name</label>
|
|
||||||
<input id="LocalLibraryFunctions-Name" className="LocalLibraryFunctions-Name" placeholder="Library Name" defaultValue={library ? library.libraryName : "New Library"}
|
|
||||||
onChange={(e) => setRecord({...record, name: e.currentTarget.value})}/>
|
|
||||||
<label htmlFor="LocalLibraryFunctions-Path">Library Path</label>
|
|
||||||
<input id="LocalLibraryFunctions-Path" className="LocalLibraryFunctions-Path" placeholder="Library Path" defaultValue={library ? library.basePath : ""}
|
|
||||||
onChange={(e) => setRecord({...record, path: e.currentTarget.value})}/>
|
|
||||||
{library
|
|
||||||
? <button className="LocalLibraryFunctions-Action" onClick={() => {
|
|
||||||
if(record === null || Validate(record) === false)
|
|
||||||
return;
|
|
||||||
setLoading(true);
|
|
||||||
LocalLibraryFunctions.UpdateLibrary(apiUri, library.localLibraryId, record)
|
|
||||||
.finally(() => setLoading(false));
|
|
||||||
}}>Edit</button>
|
|
||||||
: <button className="LocalLibraryFunctions-Action" onClick={() => {
|
|
||||||
if(record === null || Validate(record) === false)
|
|
||||||
return;
|
|
||||||
setLoading(true);
|
|
||||||
LocalLibraryFunctions.CreateLibrary(apiUri, record)
|
|
||||||
.finally(() => setLoading(false));
|
|
||||||
}}>Add</button>
|
|
||||||
}
|
|
||||||
<Loader loading={loading} style={{width:"40px",height:"40px"}}/>
|
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
@ -115,8 +115,8 @@ export function MangaItem({apiUri, mangaId, children} : {apiUri: string, mangaId
|
|||||||
setSettingThreshold(true);
|
setSettingThreshold(true);
|
||||||
MangaFunctions.SetIgnoreThreshold(apiUri, mangaId, e.currentTarget.valueAsNumber).finally(()=>setSettingThreshold(false));
|
MangaFunctions.SetIgnoreThreshold(apiUri, mangaId, e.currentTarget.valueAsNumber).finally(()=>setSettingThreshold(false));
|
||||||
}} />
|
}} />
|
||||||
<Loader loading={settingThreshold} style={{margin: "-10px -45px"}}/>
|
<Loader loading={settingThreshold} />
|
||||||
out of <span className="MangaItem-Props-Threshold-Available">{latestChapterAvailable ? latestChapterAvailable.chapterNumber : <Loader loading={loadingChapterStats} style={{margin: "-10px -35px"}} />}</span>
|
out of <span className="MangaItem-Props-Threshold-Available">{latestChapterAvailable ? latestChapterAvailable.chapterNumber : <Loader loading={loadingChapterStats}/>}</span>
|
||||||
</div>
|
</div>
|
||||||
{children ? children.map(c => {
|
{children ? children.map(c => {
|
||||||
if(c instanceof Element)
|
if(c instanceof Element)
|
||||||
|
@ -2,9 +2,6 @@ import {ReactElement, ReactEventHandler, useState} from "react";
|
|||||||
import "../../styles/notificationConnector.css";
|
import "../../styles/notificationConnector.css";
|
||||||
import Loader from "../Loader";
|
import Loader from "../Loader";
|
||||||
import NotificationConnectorFunctions from "../NotificationConnectorFunctions";
|
import NotificationConnectorFunctions from "../NotificationConnectorFunctions";
|
||||||
import {LunaseaItem} from "./records/ILunaseaRecord";
|
|
||||||
import {GotifyItem} from "./records/IGotifyRecord";
|
|
||||||
import {NtfyItem} from "./records/INtfyRecord";
|
|
||||||
|
|
||||||
export default interface INotificationConnector {
|
export default interface INotificationConnector {
|
||||||
name: string;
|
name: string;
|
||||||
@ -15,33 +12,7 @@ export default interface INotificationConnector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function NotificationConnectorItem({apiUri, notificationConnector} : {apiUri: string, notificationConnector: INotificationConnector | null}) : ReactElement {
|
export function NotificationConnectorItem({apiUri, notificationConnector} : {apiUri: string, notificationConnector: INotificationConnector | null}) : ReactElement {
|
||||||
if(notificationConnector != null)
|
const AddHeader : ReactEventHandler<HTMLButtonElement> = (e) => {
|
||||||
return <DefaultItem apiUri={apiUri} notificationConnector={notificationConnector} />
|
|
||||||
|
|
||||||
const [selectedConnectorElement, setSelectedConnectorElement] = useState<ReactElement>(<DefaultItem apiUri={apiUri} notificationConnector={null} />);
|
|
||||||
|
|
||||||
return <div>
|
|
||||||
<div>New Notification Connector</div>
|
|
||||||
<label>Type</label>
|
|
||||||
<select defaultValue="default" onChange={(e) => {
|
|
||||||
switch (e.currentTarget.value){
|
|
||||||
case "default": setSelectedConnectorElement(<DefaultItem apiUri={apiUri} notificationConnector={null} />); break;
|
|
||||||
case "gotify": setSelectedConnectorElement(<GotifyItem apiUri={apiUri} />); break;
|
|
||||||
case "ntfy": setSelectedConnectorElement(<NtfyItem apiUri={apiUri} />); break;
|
|
||||||
case "lunasea": setSelectedConnectorElement(<LunaseaItem apiUri={apiUri} />); break;
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
<option value="default">Generic REST</option>
|
|
||||||
<option value="gotify">Gotify</option>
|
|
||||||
<option value="ntfy">Ntfy</option>
|
|
||||||
<option value="lunasea">Lunasea</option>
|
|
||||||
</select>
|
|
||||||
{selectedConnectorElement}
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function DefaultItem({apiUri, notificationConnector}:{apiUri: string, notificationConnector: INotificationConnector | null}) : ReactElement {
|
|
||||||
const AddHeader : ReactEventHandler<HTMLButtonElement> = () => {
|
|
||||||
let header : Record<string, string> = {};
|
let header : Record<string, string> = {};
|
||||||
let x = info;
|
let x = info;
|
||||||
x.headers = [header, ...x.headers];
|
x.headers = [header, ...x.headers];
|
||||||
@ -49,6 +20,7 @@ function DefaultItem({apiUri, notificationConnector}:{apiUri: string, notificati
|
|||||||
setHeaderElements([...headerElements, <HeaderElement record={header} />])
|
setHeaderElements([...headerElements, <HeaderElement record={header} />])
|
||||||
}
|
}
|
||||||
const [headerElements, setHeaderElements] = useState<ReactElement[]>([]);
|
const [headerElements, setHeaderElements] = useState<ReactElement[]>([]);
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
const [info, setInfo] = useState<INotificationConnector>({
|
const [info, setInfo] = useState<INotificationConnector>({
|
||||||
name: "",
|
name: "",
|
||||||
url: "",
|
url: "",
|
||||||
@ -56,25 +28,33 @@ function DefaultItem({apiUri, notificationConnector}:{apiUri: string, notificati
|
|||||||
httpMethod: "",
|
httpMethod: "",
|
||||||
body: ""
|
body: ""
|
||||||
});
|
});
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
|
||||||
return <div className="NotificationConnectorItem">
|
return (<div className="NotificationConnectorItem" key={notificationConnector ? notificationConnector.name : "new"}>
|
||||||
<input className="NotificationConnectorItem-Name" placeholder="Name" defaultValue={notificationConnector ? notificationConnector.name : ""}
|
<p className="NotificationConnectorItem-Name">{notificationConnector ? notificationConnector.name : "New Notification Connector"}</p>
|
||||||
disabled={notificationConnector != null} onChange={(e) => setInfo({...info, name: e.currentTarget.value})} />
|
|
||||||
<div className="NotificationConnectorItem-Url">
|
<div className="NotificationConnectorItem-Url">
|
||||||
<select className="NotificationConnectorItem-RequestMethod" defaultValue={notificationConnector ? notificationConnector.httpMethod : ""}
|
<select className="NotificationConnectorItem-RequestMethod" defaultValue={notificationConnector ? notificationConnector.httpMethod : ""} disabled={notificationConnector != null} onChange={(e) => {
|
||||||
disabled={notificationConnector != null} onChange={(e)=> setInfo({...info, httpMethod: e.currentTarget.value})} >
|
let x = info;
|
||||||
|
x.httpMethod = e.currentTarget.value;
|
||||||
|
setInfo(x);
|
||||||
|
}}>
|
||||||
<option value="" disabled hidden>Request Method</option>
|
<option value="" disabled hidden>Request Method</option>
|
||||||
<option value="GET">GET</option>
|
<option value="GET">GET</option>
|
||||||
<option value="POST">POST</option>
|
<option value="POST">POST</option>
|
||||||
</select>
|
</select>
|
||||||
<input type="url" className="NotificationConnectorItem-RequestUrl" placeholder="URL" defaultValue={notificationConnector ? notificationConnector.url : ""}
|
<input type="url" className="NotificationConnectorItem-RequestUrl" placeholder="URL" defaultValue={notificationConnector ? notificationConnector.url : ""} disabled={notificationConnector != null} onChange={(e) => {
|
||||||
disabled={notificationConnector != null} onChange={(e) => setInfo({...info, url: e.currentTarget.value})} />
|
let x = info;
|
||||||
|
x.url = e.currentTarget.value;
|
||||||
|
setInfo(x);
|
||||||
|
}} />
|
||||||
</div>
|
</div>
|
||||||
<textarea className="NotificationConnectorItem-Body" placeholder="Request-Body" defaultValue={notificationConnector ? notificationConnector.body : ""}
|
<textarea className="NotificationConnectorItem-Body" placeholder="Request-Body" defaultValue={notificationConnector ? notificationConnector.body : ""} disabled={notificationConnector != null} onChange={(e) => {
|
||||||
disabled={notificationConnector != null} onChange={(e)=> setInfo({...info, body: e.currentTarget.value})} />
|
let x = info;
|
||||||
|
x.body = e.currentTarget.value;
|
||||||
|
setInfo(x);
|
||||||
|
}} />
|
||||||
{notificationConnector != null ? null :
|
{notificationConnector != null ? null :
|
||||||
(
|
(
|
||||||
<p className="NotificationConnectorItem-Explanation">Formatting placeholders: "%title" and "%text" can be placed in url, header-values and body and will be replaced when notifications are sent</p>
|
<p className="NotificationConnectorItem-Explanation">Explanation Text</p>
|
||||||
)}
|
)}
|
||||||
<div className="NotificationConnectorItem-Headers">
|
<div className="NotificationConnectorItem-Headers">
|
||||||
{headerElements}
|
{headerElements}
|
||||||
@ -87,15 +67,17 @@ function DefaultItem({apiUri, notificationConnector}:{apiUri: string, notificati
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<>
|
{notificationConnector != null ? null : (
|
||||||
<button className="NotificationConnectorItem-Save" onClick={(e) => {
|
<>
|
||||||
setLoading(true);
|
<button className="NotificationConnectorItem-Save" onClick={(e) => {
|
||||||
NotificationConnectorFunctions.CreateNotificationConnector(apiUri, info)
|
setLoading(true);
|
||||||
.finally(() => setLoading(false));
|
NotificationConnectorFunctions.CreateNotificationConnector(apiUri, info)
|
||||||
}}>Add</button>
|
.finally(() => setLoading(false));
|
||||||
<Loader loading={loading} style={{width:"40px",height:"40px",margin:"25vh calc(sin(70)*(50% - 40px))"}}/>
|
}}>Add</button>
|
||||||
</>
|
<Loader loading={loading} style={{width:"40px",height:"40px",margin:"calc(sin(70)*(50% - 40px))"}}/>
|
||||||
</div>
|
</>
|
||||||
|
)}
|
||||||
|
</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
function HeaderElement({record, disabled} : {record: Record<string, string>, disabled?: boolean | null}) : ReactElement {
|
function HeaderElement({record, disabled} : {record: Record<string, string>, disabled?: boolean | null}) : ReactElement {
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
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"
|
|
||||||
}
|
|
5
Website/modules/interfaces/records/IGotifyRecord.ts
Normal file
5
Website/modules/interfaces/records/IGotifyRecord.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export default interface IGotifyRecord {
|
||||||
|
endpoint: string;
|
||||||
|
appToken: string;
|
||||||
|
priority: number;
|
||||||
|
}
|
@ -1,51 +0,0 @@
|
|||||||
import {ReactElement, useState} from "react";
|
|
||||||
import NotificationConnectorFunctions from "../../NotificationConnectorFunctions";
|
|
||||||
import Loader from "../../Loader";
|
|
||||||
import "../../../styles/notificationConnector.css";
|
|
||||||
import {isValidUri} from "../../../App";
|
|
||||||
|
|
||||||
export default interface IGotifyRecord {
|
|
||||||
endpoint: string;
|
|
||||||
appToken: string;
|
|
||||||
priority: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Validate(record: IGotifyRecord) : boolean {
|
|
||||||
if(!isValidUri(record.endpoint))
|
|
||||||
return false;
|
|
||||||
if(record.appToken.length < 1)
|
|
||||||
return false;
|
|
||||||
if(record.priority < 1 || record.priority > 5)
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GotifyItem ({apiUri} : {apiUri: string}) : ReactElement{
|
|
||||||
const [record, setRecord] = useState<IGotifyRecord>({
|
|
||||||
endpoint: "",
|
|
||||||
appToken: "",
|
|
||||||
priority: 3
|
|
||||||
});
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
return <div className="NotificationConnectorItem">
|
|
||||||
<input className="NotificationConnectorItem-Name" value="Gotify" disabled={true} />
|
|
||||||
<div className="NotificationConnectorItem-Url">
|
|
||||||
<input type="text" className="NotificationConnectorItem-RequestUrl" placeholder="URL" onChange={(e) => setRecord({...record, endpoint: e.currentTarget.value})} />
|
|
||||||
<input type="text" className="NotificationConnectorItem-AppToken" placeholder="Apptoken" onChange={(e) => setRecord({...record, appToken: e.currentTarget.value})} />
|
|
||||||
</div>
|
|
||||||
<div className="NotificationConnectorItem-Priority">
|
|
||||||
<label htmlFor="NotificationConnectorItem-Priority">Priority</label>
|
|
||||||
<input id="NotificationConnectorItem-Priority-Value" type="number" className="NotificationConnectorItem-Priority-Value" min={1} max={5} defaultValue={3} onChange={(e) => setRecord({...record, priority: e.currentTarget.valueAsNumber})} />
|
|
||||||
</div>
|
|
||||||
<>
|
|
||||||
<button className="NotificationConnectorItem-Save" onClick={(e) => {
|
|
||||||
if(record === null || Validate(record) === false)
|
|
||||||
return;
|
|
||||||
setLoading(true);
|
|
||||||
NotificationConnectorFunctions.CreateGotify(apiUri, record)
|
|
||||||
.finally(() => setLoading(false));
|
|
||||||
}}>Add</button>
|
|
||||||
<Loader loading={loading} style={{width:"40px",height:"40px"}}/>
|
|
||||||
</>
|
|
||||||
</div>;
|
|
||||||
}
|
|
3
Website/modules/interfaces/records/ILunaseaRecord.ts
Normal file
3
Website/modules/interfaces/records/ILunaseaRecord.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export default interface ILunaseaRecord {
|
||||||
|
id: string;
|
||||||
|
}
|
@ -1,36 +0,0 @@
|
|||||||
import {ReactElement, useState} from "react";
|
|
||||||
import NotificationConnectorFunctions from "../../NotificationConnectorFunctions";
|
|
||||||
import Loader from "../../Loader";
|
|
||||||
import "../../../styles/notificationConnector.css";
|
|
||||||
|
|
||||||
export default interface ILunaseaRecord {
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const regex = new RegExp("(?:device|user)\/[0-9a-zA-Z\-]+");
|
|
||||||
function Validate(record: ILunaseaRecord) : boolean {
|
|
||||||
return regex.test(record.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function LunaseaItem ({apiUri} : {apiUri: string}) : ReactElement{
|
|
||||||
const [record, setRecord] = useState<ILunaseaRecord>({
|
|
||||||
id: ""
|
|
||||||
});
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
return <div className="NotificationConnectorItem">
|
|
||||||
<input className="NotificationConnectorItem-Name" value="LunaSea" disabled={true} />
|
|
||||||
<div className="NotificationConnectorItem-Url">
|
|
||||||
<input type="text" className="NotificationConnectorItem-RequestUrl" placeholder="device/:device_id or user/:user_id" onChange={(e) => setRecord({...record, id: e.currentTarget.value})} />
|
|
||||||
</div>
|
|
||||||
<>
|
|
||||||
<button className="NotificationConnectorItem-Save" onClick={(e) => {
|
|
||||||
if(record === null || Validate(record) === false)
|
|
||||||
return;
|
|
||||||
setLoading(true);
|
|
||||||
NotificationConnectorFunctions.CreateLunasea(apiUri, record)
|
|
||||||
.finally(() => setLoading(false));
|
|
||||||
}}>Add</button>
|
|
||||||
<Loader loading={loading} style={{width:"40px",height:"40px",margin:"25vh calc(sin(70)*(50% - 40px))"}}/>
|
|
||||||
</>
|
|
||||||
</div>;
|
|
||||||
}
|
|
@ -1,12 +1,4 @@
|
|||||||
export default interface INewLibraryRecord {
|
export default interface INewLibraryRecord {
|
||||||
path: string;
|
path: string;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
|
||||||
|
|
||||||
export function Validate(record: INewLibraryRecord) : boolean {
|
|
||||||
if(record.path.length < 1)
|
|
||||||
return false;
|
|
||||||
if(record.name.length < 1)
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
7
Website/modules/interfaces/records/INtfyRecord.ts
Normal file
7
Website/modules/interfaces/records/INtfyRecord.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export default interface INtfyRecord {
|
||||||
|
endpoint: string;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
topic: string;
|
||||||
|
priority: number;
|
||||||
|
}
|
@ -1,62 +0,0 @@
|
|||||||
import {ReactElement, useState} from "react";
|
|
||||||
import NotificationConnectorFunctions from "../../NotificationConnectorFunctions";
|
|
||||||
import Loader from "../../Loader";
|
|
||||||
import "../../../styles/notificationConnector.css";
|
|
||||||
import {isValidUri} from "../../../App";
|
|
||||||
|
|
||||||
export default interface INtfyRecord {
|
|
||||||
endpoint: string;
|
|
||||||
username: string;
|
|
||||||
password: string;
|
|
||||||
topic: string;
|
|
||||||
priority: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Validate(record: INtfyRecord) : boolean {
|
|
||||||
if(!isValidUri(record.endpoint))
|
|
||||||
return false;
|
|
||||||
if(record.username.length < 1)
|
|
||||||
return false;
|
|
||||||
if(record.password.length < 1)
|
|
||||||
return false;
|
|
||||||
if(record.topic.length < 1)
|
|
||||||
return false;
|
|
||||||
if(record.priority < 1 || record.priority > 5)
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function NtfyItem ({apiUri} : {apiUri: string}) : ReactElement{
|
|
||||||
const [info, setInfo] = useState<INtfyRecord>({
|
|
||||||
endpoint: "",
|
|
||||||
username: "",
|
|
||||||
password: "",
|
|
||||||
topic: "",
|
|
||||||
priority: 0
|
|
||||||
});
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
return <div className="NotificationConnectorItem">
|
|
||||||
<input className="NotificationConnectorItem-Name" value="Ntfy" disabled={true} />
|
|
||||||
<div className="NotificationConnectorItem-Url">
|
|
||||||
<input type="text" className="NotificationConnectorItem-RequestUrl" placeholder="URL" onChange={(e) => setInfo({...info, endpoint: e.currentTarget.value})} />
|
|
||||||
<input type="text" className="NotificationConnectorItem-Topic" placeholder="Topic" onChange={(e) => setInfo({...info, topic: e.currentTarget.value})} />
|
|
||||||
</div>
|
|
||||||
<div className="NotificationConnectorItem-Ident">
|
|
||||||
<input type="text" className="NotificationConnectorItem-Username" placeholder="Username" onChange={(e) => setInfo({...info, username: e.currentTarget.value})} />
|
|
||||||
<input type="password" className="NotificationConnectorItem-Password" placeholder="***" onChange={(e) => setInfo({...info, password: e.currentTarget.value})} />
|
|
||||||
</div>
|
|
||||||
<div className="NotificationConnectorItem-Priority">
|
|
||||||
<label htmlFor="NotificationConnectorItem-Priority">Priority</label>
|
|
||||||
<input id="NotificationConnectorItem-Priority-Value" type="number" className="NotificationConnectorItem-Priority-Value" min={1} max={5} defaultValue={3} onChange={(e) => setInfo({...info, priority: e.currentTarget.valueAsNumber})} />
|
|
||||||
</div><>
|
|
||||||
<button className="NotificationConnectorItem-Save" onClick={(e) => {
|
|
||||||
if(info === null || Validate(info) === false)
|
|
||||||
return;
|
|
||||||
setLoading(true);
|
|
||||||
NotificationConnectorFunctions.CreateNtfy(apiUri, info)
|
|
||||||
.finally(() => setLoading(false));
|
|
||||||
}}>Add</button>
|
|
||||||
<Loader loading={loading} style={{width:"40px",height:"40px",margin:"25vh calc(sin(70)*(50% - 40px))"}}/>
|
|
||||||
</>
|
|
||||||
</div>;
|
|
||||||
}
|
|
@ -12,7 +12,6 @@ span[is-loading="loading"] {
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
background-color: var(--secondary-color);
|
background-color: var(--secondary-color);
|
||||||
background-blend-mode: lighten;
|
background-blend-mode: lighten;
|
||||||
margin: 25vh calc(sin(70)*(50% - 40px));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
span[is-loading="loading"]:before,
|
span[is-loading="loading"]:before,
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
.LocalLibraryFunctions {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.LocalLibraryFunctions input{
|
|
||||||
width: min-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
.LocalLibraryFunctions-Action {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
@ -1,8 +1,12 @@
|
|||||||
.NotificationConnectorItem{
|
.NotificationConnectorItem{
|
||||||
position: relative;
|
position: relative;
|
||||||
display: grid;
|
display: grid;
|
||||||
|
width: calc(100% - 10px);
|
||||||
grid-template-columns: 40% calc(60% - 10px);
|
grid-template-columns: 40% calc(60% - 10px);
|
||||||
grid-template-rows: 30px auto auto 30px;
|
margin: 0 auto;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
grid-template-rows: 30px 30px auto 30px;
|
||||||
column-gap: 4px;
|
column-gap: 4px;
|
||||||
row-gap: 4px;
|
row-gap: 4px;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
@ -11,6 +15,7 @@
|
|||||||
"headers body"
|
"headers body"
|
||||||
"footer footer";
|
"footer footer";
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
border: 1px solid var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.NotificationConnectorItem p{
|
.NotificationConnectorItem p{
|
||||||
@ -20,13 +25,6 @@
|
|||||||
.NotificationConnectorItem-Name{
|
.NotificationConnectorItem-Name{
|
||||||
grid-area: name;
|
grid-area: name;
|
||||||
justify-self: flex-start;
|
justify-self: flex-start;
|
||||||
width: fit-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
.NotificationConnectorItem-Name::before {
|
|
||||||
content: "Connector-Name";
|
|
||||||
position: absolute;
|
|
||||||
display: block;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.NotificationConnectorItem-Url{
|
.NotificationConnectorItem-Url{
|
||||||
@ -48,14 +46,6 @@
|
|||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.NotificationConnectorItem-Priority {
|
|
||||||
grid-area: explanation;
|
|
||||||
}
|
|
||||||
|
|
||||||
.NotificationConnectorItem-Ident {
|
|
||||||
grid-area: body;
|
|
||||||
}
|
|
||||||
|
|
||||||
.NotificationConnectorItem-Headers{
|
.NotificationConnectorItem-Headers{
|
||||||
grid-area: headers;
|
grid-area: headers;
|
||||||
justify-self: flex-end;
|
justify-self: flex-end;
|
||||||
@ -75,4 +65,5 @@
|
|||||||
.NotificationConnectorItem-Save{
|
.NotificationConnectorItem-Save{
|
||||||
grid-area: footer;
|
grid-area: footer;
|
||||||
justify-self: flex-end;
|
justify-self: flex-end;
|
||||||
|
padding: 0 15px;
|
||||||
}
|
}
|
@ -40,8 +40,5 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: calc(100% - 30px);
|
width: calc(100% - 30px);
|
||||||
height: calc(100% - 50px);
|
height: calc(100% - 50px);
|
||||||
padding: 5px 15px;
|
margin: 5px 15px;
|
||||||
overflow-y: auto;
|
|
||||||
overflow-x: hidden;
|
|
||||||
scrollbar-width: thin;
|
|
||||||
}
|
}
|
@ -16,59 +16,10 @@
|
|||||||
|
|
||||||
#SettingsPopUpBody {
|
#SettingsPopUpBody {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row wrap;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
#SettingsPopUpBody > * {
|
#SettingsPopUpBody > * {
|
||||||
margin: 5px 2px;
|
margin: 5px 0;
|
||||||
border: 1px solid var(--primary-color);
|
|
||||||
border-radius: 5px;
|
|
||||||
padding: 0 5px;
|
|
||||||
max-width: calc(100% - 10px);
|
|
||||||
min-width: calc(30% - 10px);
|
|
||||||
flex-grow: 1;
|
|
||||||
flex-basis: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#SettingsPopUpBody > * > .LocalLibraryFunctions, #SettingsPopUpBody > * > div > .NotificationConnectorItem {
|
|
||||||
border: 1px solid var(--primary-color);
|
|
||||||
border-left: 0;
|
|
||||||
border-right: 0;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin: 5px -5px -1px -5px;
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#SettingsPopUpBody > *:has(.NotificationConnectorItem) {
|
|
||||||
width: 100%;
|
|
||||||
flex-basis: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#SettingsPopUpBody label {
|
|
||||||
width: max-content;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#SettingsPopUpBody label::after {
|
|
||||||
content: ':';
|
|
||||||
}
|
|
||||||
|
|
||||||
#SettingsPopUpBody button {
|
|
||||||
padding: 0 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#SettingsPopUpBody h1, #SettingsPopUpBody h2, #SettingsPopUpBody h3 {
|
|
||||||
border: 0;
|
|
||||||
margin: 5px 0 2px 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-requestLimits {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-requestLimits input {
|
|
||||||
width: min-content;
|
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user