← back to Dear Bubbe Admin
frontend/src/components/AlertsPage.js
470 lines
import React, { useState, useEffect } from 'react'
import axios from 'axios'
const AlertsPage = () => {
const [alerts, setAlerts] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const [filters, setFilters] = useState({
type: '',
severity: '',
status: '',
timeframe: '24h'
})
const [selectedAlert, setSelectedAlert] = useState(null)
const [stats, setStats] = useState({})
useEffect(() => {
fetchAlerts()
fetchAlertStats()
}, [filters])
const fetchAlerts = async () => {
try {
setError(null)
const params = new URLSearchParams()
Object.entries(filters).forEach(([key, value]) => {
if (value) params.append(key, value)
})
const response = await axios.get(`/api/alerts?${params.toString()}`)
if (response.data.success) {
setAlerts(response.data.alerts || [])
}
} catch (error) {
console.error('Failed to fetch alerts:', error)
setError('Failed to load alerts')
} finally {
setLoading(false)
}
}
const fetchAlertStats = async () => {
try {
const response = await axios.get(`/api/alerts/stats?timeframe=${filters.timeframe}`)
if (response.data.success) {
setStats(response.data)
}
} catch (error) {
console.error('Failed to fetch alert stats:', error)
}
}
const handleStatusUpdate = async (alertId, status, reviewNotes = '') => {
try {
await axios.put(`/api/alerts/${alertId}/status`, {
status,
reviewNotes
})
// Refresh alerts
fetchAlerts()
setSelectedAlert(null)
} catch (error) {
console.error('Failed to update alert status:', error)
alert('Failed to update alert status')
}
}
const handleBlockUser = async (alertId) => {
if (!confirm('Are you sure you want to block this user?')) return
try {
await axios.post(`/api/alerts/${alertId}/block-user`, {
reason: 'Blocked due to security violation'
})
alert('User has been blocked successfully')
fetchAlerts()
setSelectedAlert(null)
} catch (error) {
console.error('Failed to block user:', error)
alert('Failed to block user')
}
}
const getSeverityColor = (severity) => {
const colors = {
critical: '#ef4444',
high: '#f59e0b',
medium: '#3b82f6',
low: '#6b7280'
}
return colors[severity] || '#6b7280'
}
const getTypeIcon = (type) => {
const icons = {
antisemitism: '✡️',
racism: '⚠️',
harassment: '🚫',
spam: '📧',
system: '⚙️'
}
return icons[type] || '❗'
}
const formatTimestamp = (timestamp) => {
return new Date(timestamp).toLocaleString()
}
if (loading) {
return (
<div style={{ display: 'flex', justifyContent: 'center', padding: '40px' }}>
<div className="loading-spinner"></div>
<span style={{ marginLeft: '12px' }}>Loading alerts...</span>
</div>
)
}
return (
<div>
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '24px'
}}>
<h2 style={{ fontSize: '24px', fontWeight: '600', color: '#1e293b' }}>
Security Alerts
</h2>
<button
onClick={() => { fetchAlerts(); fetchAlertStats(); }}
className="btn btn-primary"
>
Refresh
</button>
</div>
{/* Alert Stats */}
<div className="stat-cards" style={{ marginBottom: '24px' }}>
<div className="stat-card critical">
<div className="stat-label">Critical Alerts</div>
<div className="stat-value-large" style={{ color: '#ef4444' }}>
{stats.summary?.critical || 0}
</div>
</div>
<div className="stat-card warning">
<div className="stat-label">High Priority</div>
<div className="stat-value-large" style={{ color: '#f59e0b' }}>
{stats.summary?.high || 0}
</div>
</div>
<div className="stat-card">
<div className="stat-label">Antisemitism</div>
<div className="stat-value-large" style={{ color: '#dc2626' }}>
{stats.summary?.antisemitism || 0}
</div>
</div>
<div className="stat-card">
<div className="stat-label">Racism</div>
<div className="stat-value-large" style={{ color: '#dc2626' }}>
{stats.summary?.racism || 0}
</div>
</div>
</div>
{/* Filters */}
<div className="card" style={{ marginBottom: '24px' }}>
<div className="card-header">
<h3 className="card-title">Filter Alerts</h3>
</div>
<div className="card-content">
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', gap: '16px' }}>
<div>
<label className="form-label">Type</label>
<select
className="form-select"
value={filters.type}
onChange={(e) => setFilters({...filters, type: e.target.value})}
>
<option value="">All Types</option>
<option value="antisemitism">Antisemitism</option>
<option value="racism">Racism</option>
<option value="harassment">Harassment</option>
<option value="spam">Spam</option>
<option value="system">System</option>
</select>
</div>
<div>
<label className="form-label">Severity</label>
<select
className="form-select"
value={filters.severity}
onChange={(e) => setFilters({...filters, severity: e.target.value})}
>
<option value="">All Severities</option>
<option value="critical">Critical</option>
<option value="high">High</option>
<option value="medium">Medium</option>
<option value="low">Low</option>
</select>
</div>
<div>
<label className="form-label">Status</label>
<select
className="form-select"
value={filters.status}
onChange={(e) => setFilters({...filters, status: e.target.value})}
>
<option value="">All Status</option>
<option value="active">Active</option>
<option value="reviewed">Reviewed</option>
<option value="resolved">Resolved</option>
<option value="false_positive">False Positive</option>
</select>
</div>
<div>
<label className="form-label">Timeframe</label>
<select
className="form-select"
value={filters.timeframe}
onChange={(e) => setFilters({...filters, timeframe: e.target.value})}
>
<option value="1h">Last Hour</option>
<option value="24h">Last 24 Hours</option>
<option value="7d">Last 7 Days</option>
<option value="30d">Last 30 Days</option>
</select>
</div>
</div>
</div>
</div>
{/* Alerts Table */}
<div className="card">
<div className="card-header">
<h3 className="card-title">
Alerts ({alerts.length})
</h3>
</div>
<div className="card-content">
{error && (
<div style={{ color: '#ef4444', textAlign: 'center', padding: '20px' }}>
{error}
</div>
)}
{alerts.length === 0 ? (
<div style={{ textAlign: 'center', padding: '40px', color: '#64748b' }}>
{filters.type || filters.severity || filters.status ?
'No alerts match the current filters' :
'No alerts found'
}
</div>
) : (
<div className="table-container">
<table className="table">
<thead>
<tr>
<th>Type</th>
<th>Severity</th>
<th>User/IP</th>
<th>Message</th>
<th>Location</th>
<th>Time</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{alerts.map((alert) => (
<tr key={alert._id}>
<td>
<div style={{ display: 'flex', alignItems: 'center' }}>
<span style={{ marginRight: '8px' }}>
{getTypeIcon(alert.type)}
</span>
{alert.type}
</div>
</td>
<td>
<span
className={`badge badge-${alert.severity}`}
style={{
backgroundColor: getSeverityColor(alert.severity) + '20',
color: getSeverityColor(alert.severity),
border: `1px solid ${getSeverityColor(alert.severity)}40`
}}
>
{alert.severity}
</span>
</td>
<td>
<div style={{ fontSize: '12px' }}>
<div>{alert.userInfo?.userId || 'Unknown'}</div>
<div style={{ color: '#64748b' }}>
{alert.userInfo?.ip || 'Unknown IP'}
</div>
</div>
</td>
<td>
<div style={{
maxWidth: '200px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
fontSize: '14px'
}}>
{alert.content?.message || 'No message'}
</div>
{alert.violation?.match && (
<div style={{
fontSize: '12px',
color: '#ef4444',
fontWeight: '500',
marginTop: '4px'
}}>
Matched: "{alert.violation.match}"
</div>
)}
</td>
<td style={{ fontSize: '12px', color: '#64748b' }}>
{alert.location?.city && alert.location?.country ?
`${alert.location.city}, ${alert.location.country}` :
'Unknown'
}
</td>
<td style={{ fontSize: '12px', color: '#64748b' }}>
{formatTimestamp(alert.timestamp)}
</td>
<td>
<span className={`badge badge-${alert.status === 'active' ? 'critical' : 'medium'}`}>
{alert.status}
</span>
</td>
<td>
<div style={{ display: 'flex', gap: '8px' }}>
<button
onClick={() => setSelectedAlert(alert)}
className="btn btn-small btn-secondary"
>
View
</button>
{alert.status === 'active' && (
<button
onClick={() => handleStatusUpdate(alert._id, 'reviewed')}
className="btn btn-small btn-primary"
>
Mark Reviewed
</button>
)}
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
</div>
{/* Alert Detail Modal */}
{selectedAlert && (
<div className="live-alert-modal" onClick={() => setSelectedAlert(null)}>
<div className="live-alert-content" onClick={e => e.stopPropagation()}>
<div className="alert-header">
<h3 className="alert-title">
{getTypeIcon(selectedAlert.type)} Alert Details
</h3>
<button
className="alert-close"
onClick={() => setSelectedAlert(null)}
>
×
</button>
</div>
<div className="alert-details">
<div className="alert-detail-row">
<span className="alert-detail-label">Type:</span>
<span className="alert-detail-value">{selectedAlert.type}</span>
</div>
<div className="alert-detail-row">
<span className="alert-detail-label">Severity:</span>
<span className={`badge badge-${selectedAlert.severity}`}>
{selectedAlert.severity}
</span>
</div>
<div className="alert-detail-row">
<span className="alert-detail-label">User ID:</span>
<span className="alert-detail-value">{selectedAlert.userInfo?.userId || 'Unknown'}</span>
</div>
<div className="alert-detail-row">
<span className="alert-detail-label">IP Address:</span>
<span className="alert-detail-value">{selectedAlert.userInfo?.ip || 'Unknown'}</span>
</div>
<div className="alert-detail-row">
<span className="alert-detail-label">Location:</span>
<span className="alert-detail-value">
{selectedAlert.location?.city && selectedAlert.location?.country ?
`${selectedAlert.location.city}, ${selectedAlert.location.state}, ${selectedAlert.location.country}` :
'Unknown'
}
</span>
</div>
<div className="alert-detail-row">
<span className="alert-detail-label">Message:</span>
<span className="alert-detail-value" style={{
fontFamily: 'monospace',
backgroundColor: '#f8fafc',
padding: '8px',
borderRadius: '4px',
display: 'block',
marginTop: '4px'
}}>
{selectedAlert.content?.message || 'No message'}
</span>
</div>
{selectedAlert.violation?.match && (
<div className="alert-detail-row">
<span className="alert-detail-label">Violation:</span>
<span className="alert-detail-value" style={{
color: '#ef4444',
fontWeight: '500'
}}>
"{selectedAlert.violation.match}"
</span>
</div>
)}
<div className="alert-detail-row">
<span className="alert-detail-label">Timestamp:</span>
<span className="alert-detail-value">{formatTimestamp(selectedAlert.timestamp)}</span>
</div>
</div>
<div className="alert-actions">
<button
onClick={() => handleStatusUpdate(selectedAlert._id, 'false_positive', 'Marked as false positive')}
className="btn btn-secondary"
>
False Positive
</button>
<button
onClick={() => handleStatusUpdate(selectedAlert._id, 'resolved', 'Resolved by admin')}
className="btn btn-primary"
>
Resolve
</button>
{selectedAlert.userInfo?.userId && (
<button
onClick={() => handleBlockUser(selectedAlert._id)}
className="btn btn-danger"
>
Block User
</button>
)}
</div>
</div>
</div>
)}
</div>
)
}
export default AlertsPage