Pushover: Emergency-Priorität mit Retry, has_pushover Badge in Admin

This commit is contained in:
2026-05-29 09:00:10 +02:00
parent 8f7dbbf3c9
commit 9b54bb700b
5 changed files with 123 additions and 26 deletions

View File

@@ -757,30 +757,46 @@ function NachrichtenWidget({ unreadMsgs, setActive }) {
// ── Pushover Settings ─────────────────────────────────────────────────────────
function PushoverSettings({ toast }) {
const [form, setForm] = useState({ user_key:'', app_token:'' });
const [form, setForm] = useState({ user_key:'', app_token:'', retry: '', expire: '' });
const [busy, setBusy] = useState(false);
const [hasSaved, setHasSaved] = useState(false);
const [emergency,setEmergency]= useState(false);
useEffect(() => {
api('/tools/nachrichten/pushover')
.then(d => {
const v = { user_key: d.user_key||'', app_token: d.app_token||'' };
const v = { user_key: d.user_key||'', app_token: d.app_token||'',
retry: d.retry||'', expire: d.expire||'' };
setForm(v);
setHasSaved(!!(v.user_key && v.app_token));
setEmergency(!!(d.retry && d.expire));
}).catch(()=>{});
}, []);
const save = async () => {
if (!form.user_key.trim() || !form.app_token.trim()) { toast('Beide Felder ausfüllen', 'error'); return; }
setBusy(true);
try { await api('/tools/nachrichten/pushover', { body: form }); toast('Gespeichert'); setHasSaved(true); }
catch(e) { toast(e.message, 'error'); }
try {
await api('/tools/nachrichten/pushover', { body: {
...form,
retry: emergency ? (parseInt(form.retry) || 60) : null,
expire: emergency ? (parseInt(form.expire) || 3600) : null,
}});
toast('Gespeichert'); setHasSaved(true);
} catch(e) { toast(e.message, 'error'); }
setBusy(false);
};
const test = async () => {
setBusy(true);
try { await api('/tools/nachrichten/pushover/test', { method:'POST' }); toast('Testbenachrichtigung gesendet ✓'); }
try { await api('/tools/nachrichten/pushover/test', { method:'POST', body:{ emergency: false } }); toast('Test gesendet ✓ (normale Priorität)'); }
catch(e) { toast(e.message, 'error'); }
setBusy(false);
};
const testEmergency = async () => {
setBusy(true);
try { await api('/tools/nachrichten/pushover/test', { method:'POST', body:{ emergency: true } }); toast('🚨 Emergency-Test gesendet quittiere in der Pushover-App!'); }
catch(e) { toast(e.message, 'error'); }
setBusy(false);
};
@@ -788,8 +804,8 @@ function PushoverSettings({ toast }) {
const clear = async () => {
try {
await api('/tools/nachrichten/pushover', { method:'DELETE' });
setForm({ user_key:'', app_token:'' });
setHasSaved(false);
setForm({ user_key:'', app_token:'', retry:'', expire:'' });
setHasSaved(false); setEmergency(false);
toast('Pushover entfernt');
} catch(e) { toast(e.message, 'error'); }
};
@@ -797,12 +813,47 @@ function PushoverSettings({ toast }) {
return (
<div>
<p style={{ color:'rgba(255,255,255,0.42)', fontSize:11, fontFamily:'monospace', marginBottom:12, lineHeight:1.7 }}>
Push-Benachrichtigungen bei neuen Nachrichten via{' '}
Push-Benachrichtigungen via{' '}
<a href="https://pushover.net" target="_blank" rel="noopener noreferrer" style={{ color:'#4ecdc4' }}>pushover.net</a>.
Du brauchst einen Account und eine eigene App (kostenlos).
</p>
<Field label="USER KEY" value={form.user_key} onChange={e=>setForm(p=>({...p,user_key:e.target.value}))} />
<Field label="APP TOKEN" value={form.app_token} onChange={e=>setForm(p=>({...p,app_token:e.target.value}))} />
{/* Emergency / Wiederholung */}
<div style={{ marginBottom:14, background:'rgba(255,255,255,0.03)', border:'1px solid rgba(255,255,255,0.08)',
borderRadius:8, padding:'10px 12px' }}>
<label style={{ display:'flex', alignItems:'center', gap:8, cursor:'pointer', marginBottom: emergency ? 10 : 0 }}>
<input type="checkbox" checked={emergency} onChange={e=>setEmergency(e.target.checked)}
style={{ width:14, height:14, accentColor:'#ff6b9d', cursor:'pointer' }}/>
<span style={{ color:'rgba(255,255,255,0.7)', fontFamily:'monospace', fontSize:11 }}>
🚨 Wiederholen bis Quittierung (Emergency-Priorität)
</span>
</label>
{emergency && (
<div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:8, marginTop:6 }}>
<div>
<label style={{ ...S.head, display:'block', marginBottom:3, fontSize:9 }}>WIEDERHOLUNG (SEK)</label>
<input type="number" value={form.retry||60} min={30} max={3600}
onChange={e=>setForm(p=>({...p,retry:e.target.value}))}
style={{ ...S.inp, fontSize:13 }}/>
<div style={{ color:'rgba(255,255,255,0.2)', fontFamily:'monospace', fontSize:8, marginTop:2 }}>
min. 30s · z.B. 60 = jede Minute
</div>
</div>
<div>
<label style={{ ...S.head, display:'block', marginBottom:3, fontSize:9 }}>MAX. DAUER (SEK)</label>
<input type="number" value={form.expire||3600} min={60} max={86400}
onChange={e=>setForm(p=>({...p,expire:e.target.value}))}
style={{ ...S.inp, fontSize:13 }}/>
<div style={{ color:'rgba(255,255,255,0.2)', fontFamily:'monospace', fontSize:8, marginTop:2 }}>
z.B. 3600 = max. 1 Stunde
</div>
</div>
</div>
)}
</div>
<div style={{ display:'flex', gap:8 }}>
<button onClick={save} disabled={busy}
style={{ ...S.btn('#4ecdc4'), flex:1, textAlign:'center', padding:'10px 0', opacity:busy?0.5:1 }}>
@@ -810,11 +861,15 @@ function PushoverSettings({ toast }) {
</button>
{hasSaved && (
<button onClick={test} disabled={busy}
style={{ ...S.btn('#ffe66d', true), padding:'0 14px', opacity:busy?0.5:1 }}>Test</button>
style={{ ...S.btn('#ffe66d', true), padding:'0 10px', opacity:busy?0.5:1, fontSize:10 }}>Test</button>
)}
{hasSaved && emergency && (
<button onClick={testEmergency} disabled={busy}
style={{ ...S.btn('#ff6b9d', true), padding:'0 10px', opacity:busy?0.5:1, fontSize:10 }}>🚨 Test</button>
)}
{hasSaved && (
<button onClick={clear}
style={{ ...S.btn('#ff6b9d', true), padding:'0 14px' }}></button>
style={{ ...S.btn('#ff6b9d', true), padding:'0 10px' }}></button>
)}
</div>
</div>
@@ -1499,6 +1554,15 @@ function UserManagement({ toast }) {
border:`1px solid ${u.role==='admin'?'rgba(255,230,109,0.3)':'rgba(78,205,196,0.3)'}`,
borderRadius:4, padding:'1px 6px',
}}>{u.role}</span>
{!!u.has_pushover && (
<span title="Pushover eingerichtet" style={{
marginLeft:4, fontSize:9, fontFamily:'monospace',
color:'rgba(255,107,157,0.8)',
background:'rgba(255,107,157,0.1)',
border:'1px solid rgba(255,107,157,0.25)',
borderRadius:4, padding:'1px 6px',
}}>🔔 Push</span>
)}
{u.last_active_at && (
<span style={{
marginLeft:6, fontSize:9, fontFamily:'monospace',