Add test notification button (#996)

* Add api to test notifications before you save

* Add button to UI

* Inspect apprise logs to get errors

* Better formatting around errors coming back from the test notification endpoint

* Use apprise's built in log capture

* Instruct the user to get error from container log instead of intercepting and parsing apprise logs

* refac move to test method on notification class

---------

Co-authored-by: Simon <simobilleter@gmail.com>
This commit is contained in:
Craig Alexander
2025-07-11 05:30:50 -05:00
committed by GitHub
parent 624a5f9bd4
commit aefd678dca
9 changed files with 222 additions and 39 deletions

View File

@@ -0,0 +1,16 @@
import APIClient from '../../functions/APIClient';
import { AppriseTaskNameType } from './createAppriseNotificationUrl';
export type TestNotificationResponseType = {
success: boolean;
message: string;
};
const testAppriseNotificationUrl = async (taskName: AppriseTaskNameType, url: string) => {
return APIClient<TestNotificationResponseType>('/api/task/notification/test/', {
method: 'POST',
body: { task_name: taskName, url },
});
};
export default testAppriseNotificationUrl;

View File

@@ -9,6 +9,7 @@ export interface ButtonProps {
children?: string | ReactNode | ReactNode[];
value?: string;
title?: string;
disabled?: boolean;
onClick?: () => void;
}
@@ -21,6 +22,7 @@ const Button = ({
children,
value,
title,
disabled,
onClick,
}: ButtonProps) => {
return (
@@ -31,6 +33,7 @@ const Button = ({
type={type}
value={value}
title={title}
disabled={disabled}
onClick={onClick}
>
{label}

View File

@@ -12,6 +12,9 @@ import createTaskSchedule from '../api/actions/createTaskSchedule';
import createAppriseNotificationUrl, {
AppriseTaskNameType,
} from '../api/actions/createAppriseNotificationUrl';
import testAppriseNotificationUrl, {
TestNotificationResponseType,
} from '../api/actions/testAppriseNotificationUrl';
import deleteAppriseNotificationUrl from '../api/actions/deleteAppriseNotificationUrl';
import { ApiError, ApiResponseType } from '../functions/APIClient';
@@ -62,6 +65,10 @@ const SettingsScheduling = () => {
const [downloadPendingError, setDownloadPendingError] = useState<string | null>(null);
const [thumnailCheckError, setThumnailCheckError] = useState<string | null>(null);
const [zipBackupError, setZipBackupError] = useState<string | null>(null);
const [testNotificationMessage, setTestNotificationMessage] = useState<{
message: string;
success: boolean;
} | null>(null);
const { data: appriseNotificationData } = appriseNotification ?? {};
@@ -539,49 +546,99 @@ const SettingsScheduling = () => {
</div>
<div className="settings-item">
<p>
<i>
Send notification on completed tasks with the help of the{' '}
<a href="https://github.com/caronc/apprise" target="_blank">
Apprise
</a>{' '}
library.
</i>
</p>
<div>
<p>
<i>
Send notification on completed tasks with the help of the{' '}
<a href="https://github.com/caronc/apprise" target="_blank">
Apprise
</a>{' '}
library.
</i>
</p>
<select
value={notificationTask}
onChange={e => {
setNotificationTask(e.currentTarget.value);
}}
>
<option value="">-- select task --</option>
<option value="update_subscribed">Rescan your Subscriptions</option>
<option value="extract_download">Add to download queue</option>
<option value="download_pending">Downloading</option>
<option value="check_reindex">Reindex Documents</option>
</select>
<select
value={notificationTask}
onChange={e => {
setNotificationTask(e.currentTarget.value);
}}
>
<option value="">-- select task --</option>
<option value="update_subscribed">Rescan your Subscriptions</option>
<option value="extract_download">Add to download queue</option>
<option value="download_pending">Downloading</option>
<option value="check_reindex">Reindex Documents</option>
</select>
<input
type="text"
placeholder="Apprise notification URL"
value={notificationUrl}
onChange={e => {
setNotificationUrl(e.currentTarget.value);
}}
/>
<input
type="text"
placeholder="Apprise notification URL"
value={notificationUrl}
onChange={e => {
setNotificationUrl(e.currentTarget.value);
}}
/>
<Button
label="Save"
onClick={async () => {
await createAppriseNotificationUrl(
notificationTask as AppriseTaskNameType,
notificationUrl || '',
);
<div className="button-box">
<Button
label="Save"
disabled={!notificationUrl || !notificationTask}
onClick={async () => {
await createAppriseNotificationUrl(
notificationTask as AppriseTaskNameType,
notificationUrl || '',
);
setRefresh(true);
}}
/>
setRefresh(true);
}}
/>
<Button
label="Test"
disabled={!notificationUrl || !notificationTask}
onClick={async () => {
setTestNotificationMessage(null);
try {
const response: ApiResponseType<TestNotificationResponseType> =
await testAppriseNotificationUrl(
notificationTask as AppriseTaskNameType,
notificationUrl || '',
);
if (response.data?.success) {
setTestNotificationMessage({
message: response.data.message,
success: true,
});
} else {
setTestNotificationMessage({
message: response.data?.message || 'Test notification failed',
success: true,
});
}
} catch (error) {
const apiError = error as ApiError;
if (apiError.status && apiError.message) {
setTestNotificationMessage({ message: apiError.message, success: false });
} else {
setTestNotificationMessage({
message: 'An unexpected error occurred while testing notification.',
success: false,
});
}
}
setRefresh(true);
}}
/>
</div>
{testNotificationMessage && (
<div
className={!testNotificationMessage.success ? 'danger-zone' : ''}
style={{ margin: '10px 5px' }}
>
<p>{testNotificationMessage.message}</p>
</div>
)}
</div>
</div>
</div>

View File

@@ -133,6 +133,17 @@ button:hover {
color: var(--main-bg);
}
button:disabled {
cursor: not-allowed;
opacity: 0.6;
}
button:disabled:hover {
background-color: var(--accent-font-dark);
transform: none;
color: #ffffff;
}
.video-item.table {
overflow-x: auto;
-webkit-overflow-scrolling: touch;