← 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