# frozen_string_literal: true # AnalyticsController - Overview dashboard with statistics and charts class AnalyticsController < ApplicationController # All actions require authentication def index authorize :analytics, :index? # Time period selector (default: last 24 hours) @time_period = params[:period]&.to_sym || :day @start_time = calculate_start_time(@time_period) # Core statistics @total_events = Event.where("timestamp >= ?", @start_time).count @total_rules = Rule.enabled.count @network_ranges_with_events = NetworkRange.with_events.count @total_network_ranges = NetworkRange.count # Event breakdown by action @event_breakdown = Event.where("timestamp >= ?", @start_time) .group(:waf_action) .count .transform_keys do |action_id| case action_id when 0 then 'allow' when 1 then 'deny' when 2 then 'redirect' when 3 then 'challenge' else 'unknown' end end # Top countries by event count @top_countries = Event.joins("JOIN network_ranges ON events.ip_address <<= network_ranges.network") .where("timestamp >= ? AND network_ranges.country IS NOT NULL", @start_time) .group("network_ranges.country") .count .sort_by { |_, count| -count } .first(10) # Top blocked IPs @top_blocked_ips = Event.where("timestamp >= ?", @start_time) .where(waf_action: 1) # deny action in enum .group(:ip_address) .count .sort_by { |_, count| -count } .first(10) # Network range intelligence breakdown @network_intelligence = { datacenter_ranges: NetworkRange.datacenter.count, vpn_ranges: NetworkRange.vpn.count, proxy_ranges: NetworkRange.proxy.count, total_ranges: NetworkRange.count } # Recent activity @recent_events = Event.recent.limit(10) @recent_rules = Rule.order(created_at: :desc).limit(5) # System health indicators @system_health = { total_users: User.count, active_rules: Rule.enabled.count, disabled_rules: Rule.where(enabled: false).count, recent_errors: Event.where("timestamp >= ? AND waf_action = ?", @start_time, 1).count # 1 = deny } # Prepare data for charts @chart_data = prepare_chart_data respond_to do |format| format.html format.turbo_stream end end private def calculate_start_time(period) case period when :hour 1.hour.ago when :day 24.hours.ago when :week 1.week.ago when :month 1.month.ago else 24.hours.ago end end def prepare_chart_data # Events over time (hourly buckets for last 24 hours) events_by_hour = Event.where("timestamp >= ?", 24.hours.ago) .group("DATE_TRUNC('hour', timestamp)") .count # Convert to chart format - keep everything in UTC for consistency timeline_data = (0..23).map do |hour_ago| hour_time = hour_ago.hours.ago hour_key = hour_time.utc.beginning_of_hour { # Store as ISO string for JavaScript to handle timezone conversion time_iso: hour_time.iso8601, total: events_by_hour[hour_key] || 0 } end # Action distribution for pie chart action_distribution = @event_breakdown.map do |action, count| { action: action.humanize, count: count, percentage: ((count.to_f / [@total_events, 1].max) * 100).round(1) } end { timeline: timeline_data, actions: action_distribution, countries: @top_countries.map { |country, count| { country: country, count: count } }, network_types: [ { type: "Datacenter", count: @network_intelligence[:datacenter_ranges] }, { type: "VPN", count: @network_intelligence[:vpn_ranges] }, { type: "Proxy", count: @network_intelligence[:proxy_ranges] }, { type: "Standard", count: @network_intelligence[:total_ranges] - @network_intelligence[:datacenter_ranges] - @network_intelligence[:vpn_ranges] - @network_intelligence[:proxy_ranges] } ] } end end