feat: zentrales Pushover-Logging + neuer Admin-Tab Logs

This commit is contained in:
2026-07-16 00:18:56 +02:00
parent 7e6ea9b010
commit 2b2a2dce2d
11 changed files with 213 additions and 26 deletions

View File

@@ -3185,6 +3185,95 @@ function WebDavSettings({ toast }) {
);
}
// Admin: Log aller verschickten Pushover-Nachrichten
const PRIORITY_LABELS = { '-2':'Silent', '-1':'Leise', '0':'Normal', '1':'Hoch', '2':'Emergency' };
const PRIORITY_COLORS = { '-2':'#888888', '-1':'#4ecdc4', '0':'rgba(255,255,255,0.6)', '1':'#ffe66d', '2':'#ff6b9d' };
function PushLogs({ toast }) {
const [logs, setLogs] = useState([]);
const [loading, setLoading] = useState(true);
const [filter, setFilter] = useState('');
const load = () => {
setLoading(true);
api('/admin/push-logs?limit=300').then(setLogs).catch(e=>toast(e.message,'error')).finally(()=>setLoading(false));
};
useEffect(() => { load(); }, []);
const fmtDate = iso => {
if (!iso) return '—';
const d = new Date(iso.replace(' ', 'T'));
if (Number.isNaN(d.getTime())) return iso;
return d.toLocaleString('de-DE', { day:'2-digit', month:'2-digit', year:'2-digit', hour:'2-digit', minute:'2-digit', second:'2-digit' });
};
const filtered = filter.trim()
? logs.filter(l => (l.username||'').toLowerCase().includes(filter.toLowerCase())
|| (l.title||'').toLowerCase().includes(filter.toLowerCase())
|| (l.message||'').toLowerCase().includes(filter.toLowerCase())
|| (l.source||'').toLowerCase().includes(filter.toLowerCase()))
: logs;
return (
<div>
<Sec title={`PUSHOVER-VERLAUF (${filtered.length})`}>
<div style={{ display:'flex', gap:8, marginBottom:12 }}>
<input value={filter} onChange={e=>setFilter(e.target.value)}
placeholder="Filtern nach Benutzer, Text, Quelle…"
style={{ ...S.inp, flex:1 }} />
<button onClick={load} style={{ ...S.btn('#4ecdc4'), flexShrink:0 }}></button>
</div>
{loading ? (
<div style={{ color:'rgba(255,255,255,0.3)', fontFamily:'monospace', fontSize:12 }}>Lädt</div>
) : filtered.length === 0 ? (
<div style={{ color:'rgba(255,255,255,0.3)', fontFamily:'monospace', fontSize:12 }}>Keine Einträge.</div>
) : (
<div style={{ display:'flex', flexDirection:'column', gap:6, maxHeight:'65vh', overflowY:'auto' }}>
{filtered.map(l => (
<div key={l.id} style={{
border:'1px solid rgba(255,255,255,0.08)', borderRadius:8, padding:'8px 10px',
background: l.success ? 'transparent' : 'rgba(248,113,113,0.06)',
}}>
<div style={{ display:'flex', justifyContent:'space-between', alignItems:'baseline', gap:8, marginBottom:3, flexWrap:'wrap' }}>
<span style={{ color:'#4ecdc4', fontFamily:"'Space Mono',monospace", fontSize:11, fontWeight:700 }}>
{l.username || `User #${l.user_id ?? '?'}`}
</span>
<span style={{ color:'rgba(255,255,255,0.35)', fontFamily:'monospace', fontSize:10 }}>
{fmtDate(l.created_at)}
</span>
</div>
<div style={{ color:'rgba(255,255,255,0.85)', fontFamily:'monospace', fontSize:12, marginBottom:2 }}>
{l.title}
</div>
<div style={{ color:'rgba(255,255,255,0.5)', fontFamily:'monospace', fontSize:11, marginBottom:6, whiteSpace:'pre-wrap' }}>
{l.message}
</div>
<div style={{ display:'flex', gap:8, flexWrap:'wrap', alignItems:'center' }}>
<span style={{
color: PRIORITY_COLORS[String(l.priority)] || 'rgba(255,255,255,0.5)',
fontFamily:'monospace', fontSize:9, letterSpacing:1,
border:`1px solid ${PRIORITY_COLORS[String(l.priority)] || 'rgba(255,255,255,0.2)'}`,
borderRadius:4, padding:'1px 6px',
}}>
{PRIORITY_LABELS[String(l.priority)] || l.priority}
</span>
<span style={{ color:'rgba(255,255,255,0.3)', fontFamily:'monospace', fontSize:9 }}>
{l.source || '—'}
</span>
{!l.success && (
<span style={{ color:'#f87171', fontFamily:'monospace', fontSize:9 }}> fehlgeschlagen</span>
)}
</div>
</div>
))}
</div>
)}
</Sec>
</div>
);
}
function AdminPanel({ toast, mobile, user, nav }) {
const [section, setSection] = useState('profil');
useEffect(() => { if (nav?.section) setSection(nav.section); }, [nav?.ts]);
@@ -3246,7 +3335,7 @@ function AdminPanel({ toast, mobile, user, nav }) {
</div>
<div style={{ display:'flex', gap:6, marginBottom:18, flexWrap:'wrap' }}>
{[['profil','Profil'],['sicherheit','Sicherheit'],['dashboard','Dashboard'], ...(user?.role==='admin'?[['benutzer','Benutzer'],['backup','Backup']]:[])]
{[['profil','Profil'],['sicherheit','Sicherheit'],['dashboard','Dashboard'], ...(user?.role==='admin'?[['benutzer','Benutzer'],['backup','Backup'],['logs','Logs']]:[])]
.map(([k,l]) => (
<button key={k} onClick={()=>setSection(k)} style={{
padding:'6px 16px', borderRadius:20, fontFamily:'monospace', fontSize:12, cursor:'pointer',
@@ -3382,6 +3471,10 @@ function AdminPanel({ toast, mobile, user, nav }) {
</Sec>
</div>
)}
{section === 'logs' && user?.role==='admin' && (
<PushLogs toast={toast}/>
)}
</div>
);
}