Add all types of Notification Connectors to settings

This commit is contained in:
2025-03-19 22:01:07 +01:00
parent 60f957ede2
commit ecd76712d8
11 changed files with 236 additions and 66 deletions

View File

@ -15,6 +15,12 @@ export default function Settings({backendConnected, apiUri, frontendSettings, se
NotificationConnectorFunctions.GetNotificationConnectors(apiUri).then(setNotificationConnectors);
}, []);
const dateToStr = (x: Date) => {
const ret = (x.getHours() < 10 ? "0" + x.getHours() : x.getHours())
+ ":" +
(x.getMinutes() < 10 ? "0" + x.getMinutes() : x.getMinutes());
return ret;
}
return (
<div id="Settings">
@ -30,20 +36,11 @@ export default function Settings({backendConnected, apiUri, frontendSettings, se
<div id="SettingsPopUpBody" className="popupBody">
<div className="settings-apiuri">
<label>ApiUri</label>
<input type="url" defaultValue={frontendSettings.apiUri} onChange={(e) => {
let newSettings = frontendSettings;
newSettings.apiUri = e.currentTarget.value;
setFrontendSettings(newSettings);
}} id="ApiUri" />
<input type="url" defaultValue={frontendSettings.apiUri} onChange={(e) => setFrontendSettings({...frontendSettings, apiUri:e.currentTarget.value})} id="ApiUri" />
</div>
<div className="settings-apiuri">
<label>Default Job-Interval</label>
<input type="time" defaultValue={new Date(frontendSettings.jobInterval).getTime()} onChange={(e) => {
let newSettings = frontendSettings;
newSettings.jobInterval = e.currentTarget.valueAsDate ?? frontendSettings.jobInterval;
setFrontendSettings(newSettings);
console.log(frontendSettings);
}}/>
<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>
{notificationConnectors.map(c => <NotificationConnectorItem apiUri={apiUri} notificationConnector={c} />)}
<NotificationConnectorItem apiUri={apiUri} notificationConnector={null} />

View File

@ -2,6 +2,9 @@ import {ReactElement, ReactEventHandler, useState} from "react";
import "../../styles/notificationConnector.css";
import Loader from "../Loader";
import NotificationConnectorFunctions from "../NotificationConnectorFunctions";
import {LunaseaItem} from "./records/ILunaseaRecord";
import {GotifyItem} from "./records/IGotifyRecord";
import {NtfyItem} from "./records/INtfyRecord";
export default interface INotificationConnector {
name: string;
@ -12,7 +15,32 @@ export default interface INotificationConnector {
}
export function NotificationConnectorItem({apiUri, notificationConnector} : {apiUri: string, notificationConnector: INotificationConnector | null}) : ReactElement {
const AddHeader : ReactEventHandler<HTMLButtonElement> = (e) => {
if(notificationConnector != null)
return <DefaultItem apiUri={apiUri} notificationConnector={notificationConnector} />
const [selectedConnectorElement, setSelectedConnectorElement] = useState<ReactElement>(<DefaultItem apiUri={apiUri} notificationConnector={null} />);
return <>
<p>New Notification Connector</p>
<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}
</>;
}
function DefaultItem({apiUri, notificationConnector}:{apiUri: string, notificationConnector: INotificationConnector | null}) : ReactElement {
const AddHeader : ReactEventHandler<HTMLButtonElement> = () => {
let header : Record<string, string> = {};
let x = info;
x.headers = [header, ...x.headers];
@ -20,7 +48,6 @@ export function NotificationConnectorItem({apiUri, notificationConnector} : {api
setHeaderElements([...headerElements, <HeaderElement record={header} />])
}
const [headerElements, setHeaderElements] = useState<ReactElement[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [info, setInfo] = useState<INotificationConnector>({
name: "",
url: "",
@ -28,30 +55,22 @@ export function NotificationConnectorItem({apiUri, notificationConnector} : {api
httpMethod: "",
body: ""
});
return (<div className="NotificationConnectorItem" key={notificationConnector ? notificationConnector.name : "new"}>
<p className="NotificationConnectorItem-Name">{notificationConnector ? notificationConnector.name : "New Notification Connector"}</p>
const [loading, setLoading] = useState<boolean>(false);
return <div className="NotificationConnectorItem">
<input className="NotificationConnectorItem-Name" placeholder="Name" defaultValue={notificationConnector ? notificationConnector.name : ""}
disabled={notificationConnector != null} onChange={(e) => setInfo({...info, name: e.currentTarget.value})} />
<div className="NotificationConnectorItem-Url">
<select className="NotificationConnectorItem-RequestMethod" defaultValue={notificationConnector ? notificationConnector.httpMethod : ""} disabled={notificationConnector != null} onChange={(e) => {
let x = info;
x.httpMethod = e.currentTarget.value;
setInfo(x);
}}>
<select className="NotificationConnectorItem-RequestMethod" defaultValue={notificationConnector ? notificationConnector.httpMethod : ""}
disabled={notificationConnector != null} onChange={(e)=> setInfo({...info, httpMethod: e.currentTarget.value})} >
<option value="" disabled hidden>Request Method</option>
<option value="GET">GET</option>
<option value="POST">POST</option>
</select>
<input type="url" className="NotificationConnectorItem-RequestUrl" placeholder="URL" defaultValue={notificationConnector ? notificationConnector.url : ""} disabled={notificationConnector != null} onChange={(e) => {
let x = info;
x.url = e.currentTarget.value;
setInfo(x);
}} />
<input type="url" className="NotificationConnectorItem-RequestUrl" placeholder="URL" defaultValue={notificationConnector ? notificationConnector.url : ""}
disabled={notificationConnector != null} onChange={(e) => setInfo({...info, url: e.currentTarget.value})} />
</div>
<textarea className="NotificationConnectorItem-Body" placeholder="Request-Body" defaultValue={notificationConnector ? notificationConnector.body : ""} disabled={notificationConnector != null} onChange={(e) => {
let x = info;
x.body = e.currentTarget.value;
setInfo(x);
}} />
<textarea className="NotificationConnectorItem-Body" placeholder="Request-Body" defaultValue={notificationConnector ? notificationConnector.body : ""}
disabled={notificationConnector != null} onChange={(e)=> setInfo({...info, body: e.currentTarget.value})} />
{notificationConnector != null ? null :
(
<p className="NotificationConnectorItem-Explanation">Explanation Text</p>
@ -67,17 +86,15 @@ export function NotificationConnectorItem({apiUri, notificationConnector} : {api
)
}
</div>
{notificationConnector != null ? null : (
<>
<button className="NotificationConnectorItem-Save" onClick={(e) => {
setLoading(true);
NotificationConnectorFunctions.CreateNotificationConnector(apiUri, info)
.finally(() => setLoading(false));
}}>Add</button>
<Loader loading={loading} style={{width:"40px",height:"40px",margin:"calc(sin(70)*(50% - 40px))"}}/>
</>
)}
</div>);
<>
<button className="NotificationConnectorItem-Save" onClick={(e) => {
setLoading(true);
NotificationConnectorFunctions.CreateNotificationConnector(apiUri, info)
.finally(() => setLoading(false));
}}>Add</button>
<Loader loading={loading} style={{width:"40px",height:"40px",margin:"calc(sin(70)*(50% - 40px))"}}/>
</>
</div>
}
function HeaderElement({record, disabled} : {record: Record<string, string>, disabled?: boolean | null}) : ReactElement {

View File

@ -1,5 +0,0 @@
export default interface IGotifyRecord {
endpoint: string;
appToken: string;
priority: number;
}

View File

@ -0,0 +1,51 @@
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",margin:"calc(sin(70)*(50% - 40px))"}}/>
</>
</div>;
}

View File

@ -1,3 +0,0 @@
export default interface ILunaseaRecord {
id: string;
}

View File

@ -0,0 +1,36 @@
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:"calc(sin(70)*(50% - 40px))"}}/>
</>
</div>;
}

View File

@ -1,7 +0,0 @@
export default interface INtfyRecord {
endpoint: string;
username: string;
password: string;
topic: string;
priority: number;
}

View File

@ -0,0 +1,62 @@
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:"calc(sin(70)*(50% - 40px))"}}/>
</>
</div>;
}