feat: add Apprise sidecar user/admin notifications

This commit is contained in:
2026-02-25 22:54:18 +13:00
parent d045dd0b07
commit 1fe4a44eb5
9 changed files with 748 additions and 4 deletions

View File

@@ -72,6 +72,10 @@ export default function ProfilePage() {
const [currentPassword, setCurrentPassword] = useState('')
const [newPassword, setNewPassword] = useState('')
const [status, setStatus] = useState<string | null>(null)
const [notifyEnabled, setNotifyEnabled] = useState(false)
const [notifyUrls, setNotifyUrls] = useState('')
const [notifyStatus, setNotifyStatus] = useState<string | null>(null)
const [notifySaving, setNotifySaving] = useState(false)
const [loading, setLoading] = useState(true)
useEffect(() => {
@@ -97,6 +101,14 @@ export default function ProfilePage() {
})
setStats(data?.stats ?? null)
setActivity(data?.activity ?? null)
const notifyResponse = await authFetch(`${baseUrl}/auth/notifications`)
if (notifyResponse.ok) {
const notifyData = await notifyResponse.json()
setNotifyEnabled(Boolean(notifyData?.enabled))
const urls = Array.isArray(notifyData?.urls) ? notifyData.urls : []
setNotifyUrls(urls.join('\n'))
}
} catch (err) {
console.error(err)
setStatus('Could not load your profile.')
@@ -137,6 +149,59 @@ export default function ProfilePage() {
}
}
const saveNotifications = async (event: React.FormEvent) => {
event.preventDefault()
setNotifyStatus(null)
setNotifySaving(true)
try {
const baseUrl = getApiBase()
const response = await authFetch(`${baseUrl}/auth/notifications`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
enabled: notifyEnabled,
urls: notifyUrls,
}),
})
if (!response.ok) {
const text = await response.text()
throw new Error(text || 'Update failed')
}
setNotifyStatus('Notification settings saved.')
} catch (err) {
console.error(err)
const message =
err instanceof Error && err.message
? err.message.replace(/^\\{\"detail\":\"|\"\\}$/g, '')
: 'Could not save notification settings.'
setNotifyStatus(message)
} finally {
setNotifySaving(false)
}
}
const sendTest = async () => {
setNotifyStatus(null)
try {
const baseUrl = getApiBase()
const response = await authFetch(`${baseUrl}/auth/notifications/test`, {
method: 'POST',
})
if (!response.ok) {
const text = await response.text()
throw new Error(text || 'Test failed')
}
setNotifyStatus('Test notification sent.')
} catch (err) {
console.error(err)
const message =
err instanceof Error && err.message
? err.message.replace(/^\\{\"detail\":\"|\"\\}$/g, '')
: 'Could not send test notification.'
setNotifyStatus(message)
}
}
if (loading) {
return <main className="card">Loading profile...</main>
}
@@ -222,6 +287,42 @@ export default function ProfilePage() {
</div>
</section>
</div>
<section className="profile-section">
<h2>Notifications</h2>
<div className="status-banner">
Add Apprise URLs to receive notifications (one URL per line).
</div>
<form onSubmit={saveNotifications} className="auth-form">
<label>
Enable notifications
<select
value={notifyEnabled ? 'true' : 'false'}
onChange={(event) => setNotifyEnabled(event.target.value === 'true')}
>
<option value="true">Enabled</option>
<option value="false">Disabled</option>
</select>
</label>
<label>
Apprise URLs
<textarea
rows={4}
placeholder="discord://token@webhook_id\nmailto://user:pass@server"
value={notifyUrls}
onChange={(event) => setNotifyUrls(event.target.value)}
/>
</label>
{notifyStatus && <div className="status-banner">{notifyStatus}</div>}
<div className="auth-actions">
<button type="submit" disabled={notifySaving}>
{notifySaving ? 'Saving...' : 'Save notifications'}
</button>
<button type="button" className="ghost-button" onClick={sendTest}>
Send test
</button>
</div>
</form>
</section>
{profile?.auth_provider !== 'local' ? (
<div className="status-banner">
Password changes are only available for local Magent accounts.