Nya notifikations alternativ
This commit is contained in:
+22
-1
@@ -190,12 +190,33 @@ export async function clearCache() {
|
||||
}
|
||||
|
||||
export async function testNotification(gotify) {
|
||||
return handleResponse(await fetch(`${BASE}/settings/test-notification`, {
|
||||
return handleResponse(await fetch(`${BASE}/settings/test-gotify`, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json', ...authHeaders() },
|
||||
body: JSON.stringify(gotify),
|
||||
}));
|
||||
}
|
||||
|
||||
export async function testNtfyNotification(ntfy) {
|
||||
return handleResponse(await fetch(`${BASE}/settings/test-ntfy`, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json', ...authHeaders() },
|
||||
body: JSON.stringify(ntfy),
|
||||
}));
|
||||
}
|
||||
|
||||
export async function testSmtpNotification(smtp) {
|
||||
return handleResponse(await fetch(`${BASE}/settings/test-smtp`, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json', ...authHeaders() },
|
||||
body: JSON.stringify(smtp),
|
||||
}));
|
||||
}
|
||||
|
||||
export async function testWebhookNotification(webhook) {
|
||||
return handleResponse(await fetch(`${BASE}/settings/test-webhook`, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json', ...authHeaders() },
|
||||
body: JSON.stringify(webhook),
|
||||
}));
|
||||
}
|
||||
|
||||
export async function getSyncStatus(provider) {
|
||||
return handleResponse(await fetch(`${BASE}/sync-status/${provider}`, { headers: authHeaders() }));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { getSettings, saveSettings, testNotification, clearCache, getUsers, createUser, deleteUser, getProviderHealth } from '../api/dns';
|
||||
import { getSettings, saveSettings, testNotification, testNtfyNotification, testSmtpNotification, testWebhookNotification, clearCache, getUsers, createUser, deleteUser, getProviderHealth } from '../api/dns';
|
||||
import ConfirmDialog from './ConfirmDialog';
|
||||
import { exportCsv } from '../utils/exportCsv';
|
||||
import { useProviderColors, providerBadgeStyle } from '../context/ProviderColors';
|
||||
@@ -16,18 +16,30 @@ const TABS = [
|
||||
|
||||
function NotificationsTab() {
|
||||
const [form, setForm] = useState({ enabled: false, url: '', token: '', priority: 5 });
|
||||
const [ntfy, setNtfy] = useState({ enabled: false, url: 'https://ntfy.sh', topic: '', token: '', priority: 3 });
|
||||
const [smtp, setSmtp] = useState({ enabled: false, host: '', port: 587, secure: false, username: '', password: '', from: '', to: '' });
|
||||
const [webhook, setWebhook] = useState({ enabled: false, url: '', secret: '' });
|
||||
const [events, setEvents] = useState({ dns_add: true, dns_update: true, dns_delete: true, secret_check: true, secret_check_time: '08:00', timezone: 'UTC' });
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [testing, setTesting] = useState(false);
|
||||
const [testingNtfy, setTestingNtfy] = useState(false);
|
||||
const [testingSmtp, setTestingSmtp] = useState(false);
|
||||
const [testingWebhook, setTestingWebhook] = useState(false);
|
||||
const [saved, setSaved] = useState(false);
|
||||
const [testResult, setTestResult] = useState(null);
|
||||
const [testResult, setTestResult] = useState(null);
|
||||
const [testResultNtfy, setTestResultNtfy] = useState(null);
|
||||
const [testResultSmtp, setTestResultSmtp] = useState(null);
|
||||
const [testResultWebhook, setTestResultWebhook] = useState(null);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
getSettings()
|
||||
.then(s => {
|
||||
setForm({ ...s.gotify });
|
||||
setForm({ ...{ enabled: false, url: '', token: '', priority: 5 }, ...s.gotify });
|
||||
setNtfy({ ...{ enabled: false, url: 'https://ntfy.sh', topic: '', token: '', priority: 3 }, ...s.ntfy });
|
||||
setSmtp({ ...{ enabled: false, host: '', port: 587, secure: false, username: '', password: '', from: '', to: '' }, ...s.smtp });
|
||||
setWebhook({ ...{ enabled: false, url: '', secret: '' }, ...s.webhook });
|
||||
setEvents({ ...{ dns_add: true, dns_update: true, dns_delete: true, secret_check: true, secret_check_time: '08:00', timezone: 'UTC' }, ...s.notifications });
|
||||
})
|
||||
.catch(e => setError(e.message))
|
||||
@@ -51,7 +63,7 @@ function NotificationsTab() {
|
||||
e.preventDefault();
|
||||
setSaving(true); setError(''); setSaved(false);
|
||||
try {
|
||||
await saveSettings({ gotify: { ...form, priority: Number(form.priority) }, notifications: events });
|
||||
await saveSettings({ gotify: { ...form, priority: Number(form.priority) }, ntfy: { ...ntfy, priority: Number(ntfy.priority) }, smtp: { ...smtp, port: Number(smtp.port) }, webhook, notifications: events });
|
||||
setSaved(true);
|
||||
setTimeout(() => setSaved(false), 3000);
|
||||
} catch (err) { setError(err.message); }
|
||||
@@ -68,6 +80,36 @@ function NotificationsTab() {
|
||||
} finally { setTesting(false); }
|
||||
}
|
||||
|
||||
async function handleTestNtfy() {
|
||||
setTestingNtfy(true); setTestResultNtfy(null);
|
||||
try {
|
||||
await testNtfyNotification({ url: ntfy.url, topic: ntfy.topic, token: ntfy.token, priority: Number(ntfy.priority) });
|
||||
setTestResultNtfy({ ok: true, message: 'Notification sent successfully.' });
|
||||
} catch (err) {
|
||||
setTestResultNtfy({ ok: false, message: err.message });
|
||||
} finally { setTestingNtfy(false); }
|
||||
}
|
||||
|
||||
async function handleTestSmtp() {
|
||||
setTestingSmtp(true); setTestResultSmtp(null);
|
||||
try {
|
||||
await testSmtpNotification({ ...smtp, port: Number(smtp.port) });
|
||||
setTestResultSmtp({ ok: true, message: 'Test email sent successfully.' });
|
||||
} catch (err) {
|
||||
setTestResultSmtp({ ok: false, message: err.message });
|
||||
} finally { setTestingSmtp(false); }
|
||||
}
|
||||
|
||||
async function handleTestWebhook() {
|
||||
setTestingWebhook(true); setTestResultWebhook(null);
|
||||
try {
|
||||
await testWebhookNotification({ url: webhook.url, secret: webhook.secret });
|
||||
setTestResultWebhook({ ok: true, message: 'Webhook delivered successfully.' });
|
||||
} catch (err) {
|
||||
setTestResultWebhook({ ok: false, message: err.message });
|
||||
} finally { setTestingWebhook(false); }
|
||||
}
|
||||
|
||||
if (loading) return <p className="hint">Loading…</p>;
|
||||
|
||||
return (
|
||||
@@ -167,14 +209,98 @@ function NotificationsTab() {
|
||||
</section>
|
||||
|
||||
<section className="settings-section">
|
||||
<h3>About Gotify</h3>
|
||||
<h3>ntfy</h3>
|
||||
<p className="settings-desc">Send notifications via <a href="https://ntfy.sh" target="_blank" rel="noreferrer" style={{ color: 'var(--accent)' }}>ntfy.sh</a> or a self-hosted ntfy instance.</p>
|
||||
<div className="settings-form">
|
||||
<label className="toggle-row">
|
||||
<span>Enable ntfy</span>
|
||||
<input type="checkbox" checked={!!ntfy.enabled} onChange={e => setNtfy(f => ({ ...f, enabled: e.target.checked }))} className="toggle" />
|
||||
</label>
|
||||
<div className="form-row">
|
||||
<label>Server URL<input value={ntfy.url} onChange={e => setNtfy(f => ({ ...f, url: e.target.value }))} placeholder="https://ntfy.sh" disabled={!ntfy.enabled} /></label>
|
||||
<label>Topic<input value={ntfy.topic} onChange={e => setNtfy(f => ({ ...f, topic: e.target.value }))} placeholder="your-topic" disabled={!ntfy.enabled} /></label>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label>Access Token (optional)<input type="password" value={ntfy.token} onChange={e => setNtfy(f => ({ ...f, token: e.target.value }))} placeholder="For protected topics" disabled={!ntfy.enabled} /></label>
|
||||
<label>Priority <span className="settings-hint">(1–5)</span><input type="number" min={1} max={5} value={ntfy.priority} onChange={e => setNtfy(f => ({ ...f, priority: e.target.value }))} disabled={!ntfy.enabled} /></label>
|
||||
</div>
|
||||
{testResultNtfy && <p className={testResultNtfy.ok ? 'test-ok' : 'test-fail'}>{testResultNtfy.ok ? '✓' : '✕'} {testResultNtfy.message}</p>}
|
||||
<div className="form-actions">
|
||||
<button type="button" className="btn-secondary" onClick={handleTestNtfy} disabled={testingNtfy || !ntfy.url || !ntfy.topic}>{testingNtfy ? 'Sending…' : 'Send Test'}</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="settings-section">
|
||||
<h3>Email (SMTP)</h3>
|
||||
<p className="settings-desc">Send notifications via email using any SMTP server.</p>
|
||||
<div className="settings-form">
|
||||
<label className="toggle-row">
|
||||
<span>Enable SMTP</span>
|
||||
<input type="checkbox" checked={!!smtp.enabled} onChange={e => setSmtp(f => ({ ...f, enabled: e.target.checked }))} className="toggle" />
|
||||
</label>
|
||||
<div className="form-row">
|
||||
<label>SMTP Host<input value={smtp.host} onChange={e => setSmtp(f => ({ ...f, host: e.target.value }))} placeholder="smtp.example.com" disabled={!smtp.enabled} /></label>
|
||||
<label>Port<input type="number" value={smtp.port} onChange={e => setSmtp(f => ({ ...f, port: e.target.value }))} placeholder="587" disabled={!smtp.enabled} /></label>
|
||||
</div>
|
||||
<label className="toggle-row" style={{ color: 'var(--text)', textTransform: 'none', letterSpacing: 0, fontSize: 14, fontWeight: 500 }}>
|
||||
<span>Use TLS/SSL (port 465)</span>
|
||||
<input type="checkbox" checked={!!smtp.secure} onChange={e => setSmtp(f => ({ ...f, secure: e.target.checked }))} className="toggle" disabled={!smtp.enabled} />
|
||||
</label>
|
||||
<div className="form-row">
|
||||
<label>Username<input value={smtp.username} onChange={e => setSmtp(f => ({ ...f, username: e.target.value }))} placeholder="Optional" disabled={!smtp.enabled} /></label>
|
||||
<label>Password<input type="password" value={smtp.password} onChange={e => setSmtp(f => ({ ...f, password: e.target.value }))} placeholder="Optional" disabled={!smtp.enabled} /></label>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label>From<input type="email" value={smtp.from} onChange={e => setSmtp(f => ({ ...f, from: e.target.value }))} placeholder="sloth@example.com" disabled={!smtp.enabled} /></label>
|
||||
<label>To<input type="email" value={smtp.to} onChange={e => setSmtp(f => ({ ...f, to: e.target.value }))} placeholder="you@example.com" disabled={!smtp.enabled} /></label>
|
||||
</div>
|
||||
{testResultSmtp && <p className={testResultSmtp.ok ? 'test-ok' : 'test-fail'}>{testResultSmtp.ok ? '✓' : '✕'} {testResultSmtp.message}</p>}
|
||||
<div className="form-actions">
|
||||
<button type="button" className="btn-secondary" onClick={handleTestSmtp} disabled={testingSmtp || !smtp.host || !smtp.from || !smtp.to}>{testingSmtp ? 'Sending…' : 'Send Test'}</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="settings-section">
|
||||
<h3>Webhook</h3>
|
||||
<p className="settings-desc">
|
||||
POST a JSON payload to any HTTP endpoint. Compatible with Discord, Slack, and custom receivers. For Discord use a <strong>Webhook URL</strong> from Server Settings → Integrations → Webhooks.
|
||||
</p>
|
||||
<div className="settings-form">
|
||||
<label className="toggle-row">
|
||||
<span>Enable Webhook</span>
|
||||
<input type="checkbox" checked={!!webhook.enabled} onChange={e => setWebhook(f => ({ ...f, enabled: e.target.checked }))} className="toggle" />
|
||||
</label>
|
||||
<label>Webhook URL
|
||||
<input type="url" value={webhook.url} onChange={e => setWebhook(f => ({ ...f, url: e.target.value }))} placeholder="https://discord.com/api/webhooks/…" disabled={!webhook.enabled} />
|
||||
</label>
|
||||
<label>Secret / Token <span className="settings-hint">(optional — sent as X-Webhook-Secret header)</span>
|
||||
<input type="password" value={webhook.secret} onChange={e => setWebhook(f => ({ ...f, secret: e.target.value }))} placeholder="Optional shared secret" disabled={!webhook.enabled} />
|
||||
</label>
|
||||
{testResultWebhook && <p className={testResultWebhook.ok ? 'test-ok' : 'test-fail'}>{testResultWebhook.ok ? '✓' : '✕'} {testResultWebhook.message}</p>}
|
||||
<div className="form-actions">
|
||||
<button type="button" className="btn-secondary" onClick={handleTestWebhook} disabled={testingWebhook || !webhook.url}>{testingWebhook ? 'Sending…' : 'Send Test'}</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="settings-section">
|
||||
<h3>About Notifications</h3>
|
||||
<p className="settings-desc" style={{ lineHeight: 1.8 }}>
|
||||
<a href="https://gotify.net" target="_blank" rel="noreferrer" style={{ color: 'var(--accent)' }}>Gotify</a> is a self-hosted push notification server.<br /><br />
|
||||
<strong>To get started:</strong><br />
|
||||
1. Log in to your Gotify instance<br />
|
||||
2. Go to <strong>Apps → Create application</strong><br />
|
||||
3. Copy the generated token and paste it into App Token<br /><br />
|
||||
Notifications fire on every record add, update, or delete — not on sync.
|
||||
Sloth Manager can deliver notifications through four independent channels — enable as many as you need. All enabled channels receive the same events simultaneously.<br /><br />
|
||||
|
||||
<strong>Gotify</strong> — self-hosted push notification server. Create an application in your Gotify instance and paste the app token here. <a href="https://gotify.net" target="_blank" rel="noreferrer" style={{ color: 'var(--accent)' }}>gotify.net</a><br /><br />
|
||||
|
||||
<strong>ntfy</strong> — open-source pub/sub push service. Use the public <a href="https://ntfy.sh" target="_blank" rel="noreferrer" style={{ color: 'var(--accent)' }}>ntfy.sh</a> server or a self-hosted instance. Set a topic name and optionally an access token for protected topics.<br /><br />
|
||||
|
||||
<strong>Email (SMTP)</strong> — send notifications via any SMTP server. TLS/SSL (port 465) and STARTTLS (port 587) are both supported. Username and password are optional for open relays.<br /><br />
|
||||
|
||||
<strong>Webhook</strong> — POST a JSON payload to any HTTP endpoint. The body uses Discord's <code>{`{"content":"…"}`}</code> format, which is also accepted by many other services. An optional shared secret is sent as the <code>X-Webhook-Secret</code> header for request verification.<br /><br />
|
||||
|
||||
<strong>Notify on</strong> controls which events trigger a notification. DNS record events fire immediately. The secret expiry reminder runs once daily at the configured time and timezone, sending one message listing all expiring or expired secrets.<br /><br />
|
||||
|
||||
Sync operations never trigger notifications regardless of settings.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user