feat: Logs zeigen jetzt auch Besuche oeffentlicher Links (IP+Ort), Dropdown-Filter ergaenzt, Logs erfassen jetzt auch Upload-Freigabe und Datei-Teilen Besuche, Filter-Dropdown erweitert

This commit is contained in:
2026-07-18 15:10:34 +02:00
parent e8130a35b3
commit ff04886986
7 changed files with 168 additions and 9 deletions

View File

@@ -3189,14 +3189,28 @@ function WebDavSettings({ toast }) {
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' };
const LOG_TYPE_OPTIONS = [
['alle', 'Alles'],
['pushover', 'Pushover-Nachrichten'],
['koepi_share', 'Öffentlicher Prospektlink-Zugriff'],
['upload_share', 'Öffentlicher Upload-Link-Zugriff'],
['file_share', 'Öffentlicher Datei-Teilen-Link-Zugriff'],
];
const PUBLIC_LINK_LABELS = {
koepi_share: '🔗 Prospektlink besucht',
upload_share: '📤 Upload-Link besucht',
file_share: '📁 Datei-Teilen-Link besucht',
};
function PushLogs({ toast }) {
const [logs, setLogs] = useState([]);
const [loading, setLoading] = useState(true);
const [filter, setFilter] = useState('');
const [typeFilter, setTypeFilter] = useState('alle');
const load = () => {
setLoading(true);
api('/admin/push-logs?limit=300').then(setLogs).catch(e=>toast(e.message,'error')).finally(()=>setLoading(false));
api('/admin/logs?limit=300').then(setLogs).catch(e=>toast(e.message,'error')).finally(()=>setLoading(false));
};
useEffect(() => { load(); }, []);
@@ -3207,19 +3221,33 @@ function PushLogs({ toast }) {
return d.toLocaleString('de-DE', { day:'2-digit', month:'2-digit', year:'2-digit', hour:'2-digit', minute:'2-digit', second:'2-digit' });
};
const byType = typeFilter === 'alle' ? logs
: typeFilter === 'pushover' ? logs.filter(l => l.logType === 'pushover')
: logs.filter(l => l.logType === 'public_access' && l.linkType === typeFilter);
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;
? byType.filter(l => {
const q = filter.toLowerCase();
if (l.logType === 'pushover') {
return (l.username||'').toLowerCase().includes(q) || (l.title||'').toLowerCase().includes(q)
|| (l.message||'').toLowerCase().includes(q) || (l.source||'').toLowerCase().includes(q);
}
return (l.path||'').toLowerCase().includes(q) || (l.ip||'').toLowerCase().includes(q)
|| (l.location||'').toLowerCase().includes(q) || (l.linkType||'').toLowerCase().includes(q);
})
: byType;
return (
<div>
<Sec title={`PUSHOVER-VERLAUF (${filtered.length})`}>
<Sec title={`LOGS (${filtered.length})`}>
<div style={{ display:'flex', gap:8, marginBottom:8, flexWrap:'wrap' }}>
<select value={typeFilter} onChange={e=>setTypeFilter(e.target.value)}
style={{ ...S.inp, flex:'1 1 200px' }}>
{LOG_TYPE_OPTIONS.map(([id,label]) => <option key={id} value={id}>{label}</option>)}
</select>
</div>
<div style={{ display:'flex', gap:8, marginBottom:12 }}>
<input value={filter} onChange={e=>setFilter(e.target.value)}
placeholder="Filtern nach Benutzer, Text, Quelle…"
placeholder="Filtern nach Benutzer, Text, IP, Ort…"
style={{ ...S.inp, flex:1 }} />
<button onClick={load} style={{ ...S.btn('#4ecdc4'), flexShrink:0 }}></button>
</div>
@@ -3230,7 +3258,32 @@ function PushLogs({ toast }) {
<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 => (
{filtered.map(l => l.logType === 'public_access' ? (
<div key={l.id} style={{
border:'1px solid rgba(78,205,196,0.2)', borderRadius:8, padding:'8px 10px',
background:'rgba(78,205,196,0.04)',
}}>
<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 }}>
{PUBLIC_LINK_LABELS[l.linkType] || '🔗 Öffentlicher Link besucht'}
</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:4 }}>
{l.path}
</div>
<div style={{ display:'flex', gap:12, flexWrap:'wrap' }}>
<span style={{ color:'rgba(255,255,255,0.5)', fontFamily:'monospace', fontSize:10 }}>
IP: {l.ip || 'unbekannt'}
</span>
<span style={{ color:'rgba(255,255,255,0.5)', fontFamily:'monospace', fontSize:10 }}>
📍 {l.location || 'unbekannt'}
</span>
</div>
</div>
) : (
<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)',