345 lines
15 KiB
Plaintext
345 lines
15 KiB
Plaintext
<% content_for :title, "Event #{@event.request_id} - Baffle Hub" %>
|
|
|
|
<div class="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8" data-controller="timeline" data-timeline-mode-value="events">
|
|
<!-- Header -->
|
|
<div class="mb-8">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<nav class="flex" aria-label="Breadcrumb">
|
|
<ol class="flex items-center space-x-4">
|
|
<li>
|
|
<%= link_to "Events", events_path, class: "text-gray-500 hover:text-gray-700" %>
|
|
</li>
|
|
<li>
|
|
<div class="flex items-center">
|
|
<svg class="flex-shrink-0 h-5 w-5 text-gray-400" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true">
|
|
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
|
|
</svg>
|
|
<span class="ml-4 text-gray-700 font-medium"><%= @event.request_id %></span>
|
|
</div>
|
|
</li>
|
|
</ol>
|
|
</nav>
|
|
<div class="mt-2 flex items-center space-x-3">
|
|
<h1 class="text-3xl font-bold text-gray-900">Event Details</h1>
|
|
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium
|
|
<%= case @event.waf_action
|
|
when 'allow' then 'bg-green-100 text-green-800'
|
|
when 'deny' then 'bg-red-100 text-red-800'
|
|
when 'redirect' then 'bg-blue-100 text-blue-800'
|
|
when 'challenge' then 'bg-yellow-100 text-yellow-800'
|
|
else 'bg-gray-100 text-gray-800'
|
|
end %>">
|
|
<%= @event.waf_action.upcase %>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="flex space-x-3">
|
|
<%= link_to "Back to Events", events_path, class: "inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50" %>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Event Overview -->
|
|
<div class="bg-white shadow rounded-lg mb-6">
|
|
<div class="px-6 py-4 border-b border-gray-200">
|
|
<h3 class="text-lg font-medium text-gray-900">Event Overview</h3>
|
|
</div>
|
|
<div class="px-6 py-4">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
<div>
|
|
<dt class="text-sm font-medium text-gray-500">Request ID</dt>
|
|
<dd class="mt-1 text-sm text-gray-900 font-mono break-all"><%= @event.request_id %></dd>
|
|
</div>
|
|
<div>
|
|
<dt class="text-sm font-medium text-gray-500">Timestamp</dt>
|
|
<dd class="mt-1 text-sm text-gray-900">
|
|
<div data-timeline-target="timestamp" data-iso="<%= @event.timestamp.iso8601 %>">
|
|
<%= @event.timestamp.strftime("%Y-%m-%d %H:%M:%S %Z") %>
|
|
</div>
|
|
<div class="text-xs text-gray-500 mt-1">
|
|
<%= time_ago_in_words(@event.timestamp) %> ago
|
|
</div>
|
|
</dd>
|
|
</div>
|
|
<div>
|
|
<dt class="text-sm font-medium text-gray-500">Action</dt>
|
|
<dd class="mt-1">
|
|
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium
|
|
<%= case @event.waf_action
|
|
when 'allow' then 'bg-green-100 text-green-800'
|
|
when 'deny' then 'bg-red-100 text-red-800'
|
|
when 'redirect' then 'bg-blue-100 text-blue-800'
|
|
when 'challenge' then 'bg-yellow-100 text-yellow-800'
|
|
else 'bg-gray-100 text-gray-800'
|
|
end %>">
|
|
<%= @event.waf_action %>
|
|
</span>
|
|
</dd>
|
|
</div>
|
|
<% if @event.rule.present? %>
|
|
<div>
|
|
<dt class="text-sm font-medium text-gray-500">Rule Matched</dt>
|
|
<dd class="mt-1 text-sm text-gray-900">
|
|
<%= link_to "Rule ##{@event.rule.id}", @event.rule, class: "text-blue-600 hover:text-blue-800" %>
|
|
<span class="text-gray-500">(<%= @event.rule.waf_rule_type %>)</span>
|
|
<% if @event.waf_policy.present? %>
|
|
<br>
|
|
<span class="text-xs text-gray-500">Policy: <%= link_to @event.waf_policy.name, @event.waf_policy, class: "text-blue-600 hover:text-blue-800" %></span>
|
|
<% end %>
|
|
</dd>
|
|
</div>
|
|
<% end %>
|
|
<% if @event.blocked_reason.present? %>
|
|
<div>
|
|
<dt class="text-sm font-medium text-gray-500">Blocked Reason</dt>
|
|
<dd class="mt-1 text-sm text-gray-900"><%= @event.blocked_reason %></dd>
|
|
</div>
|
|
<% end %>
|
|
<% if @event.response_status.present? %>
|
|
<div>
|
|
<dt class="text-sm font-medium text-gray-500">Response Status</dt>
|
|
<dd class="mt-1 text-sm text-gray-900"><%= @event.response_status %></dd>
|
|
</div>
|
|
<% end %>
|
|
<% if @event.response_time_ms.present? %>
|
|
<div>
|
|
<dt class="text-sm font-medium text-gray-500">Response Time</dt>
|
|
<dd class="mt-1 text-sm text-gray-900"><%= @event.response_time_ms %> ms</dd>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Request Details -->
|
|
<div class="bg-white shadow rounded-lg mb-6">
|
|
<div class="px-6 py-4 border-b border-gray-200">
|
|
<h3 class="text-lg font-medium text-gray-900">Request Details</h3>
|
|
</div>
|
|
<div class="px-6 py-4">
|
|
<div class="grid grid-cols-1 gap-6">
|
|
<div>
|
|
<dt class="text-sm font-medium text-gray-500">Request URL</dt>
|
|
<dd class="mt-1 text-sm text-gray-900 font-mono break-all"><%= @event.request_url || @event.request_path %></dd>
|
|
</div>
|
|
<div>
|
|
<dt class="text-sm font-medium text-gray-500">Request Path</dt>
|
|
<dd class="mt-1 text-sm text-gray-900 font-mono break-all"><%= @event.request_path %></dd>
|
|
</div>
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
<div>
|
|
<dt class="text-sm font-medium text-gray-500">Method</dt>
|
|
<dd class="mt-1 text-sm text-gray-900 font-mono"><%= @event.request_method ? @event.request_method.upcase : '-' %></dd>
|
|
</div>
|
|
<div>
|
|
<dt class="text-sm font-medium text-gray-500">Protocol</dt>
|
|
<dd class="mt-1 text-sm text-gray-900 font-mono"><%= @event.request_protocol || '-' %></dd>
|
|
</div>
|
|
<% if @event.request_host %>
|
|
<div>
|
|
<dt class="text-sm font-medium text-gray-500">Host</dt>
|
|
<dd class="mt-1 text-sm text-gray-900 font-mono"><%= @event.request_host.hostname %></dd>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Network Intelligence -->
|
|
<div class="bg-white shadow rounded-lg mb-6">
|
|
<div class="px-6 py-4 border-b border-gray-200">
|
|
<h3 class="text-lg font-medium text-gray-900">Network Intelligence</h3>
|
|
</div>
|
|
<div class="px-6 py-4">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
<div>
|
|
<dt class="text-sm font-medium text-gray-500">IP Address</dt>
|
|
<dd class="mt-1 text-sm text-gray-900">
|
|
<% if @network_range %>
|
|
<%= link_to @event.ip_address, network_range_path(@event.ip_address),
|
|
class: "text-blue-600 hover:text-blue-800 hover:underline font-mono" %>
|
|
<% else %>
|
|
<span class="font-mono"><%= @event.ip_address %></span>
|
|
<% end %>
|
|
</dd>
|
|
</div>
|
|
<% if @network_range %>
|
|
<div>
|
|
<dt class="text-sm font-medium text-gray-500">Network Range</dt>
|
|
<dd class="mt-1 text-sm text-gray-900">
|
|
<%= link_to @network_range.cidr, network_range_path(@network_range),
|
|
class: "text-blue-600 hover:text-blue-800 hover:underline font-mono" %>
|
|
</dd>
|
|
</div>
|
|
<% if @network_range.company.present? %>
|
|
<div>
|
|
<dt class="text-sm font-medium text-gray-500">Company</dt>
|
|
<dd class="mt-1 text-sm text-gray-900"><%= @network_range.company %></dd>
|
|
</div>
|
|
<% end %>
|
|
<% if @network_range.asn.present? %>
|
|
<div>
|
|
<dt class="text-sm font-medium text-gray-500">ASN</dt>
|
|
<dd class="mt-1 text-sm text-gray-900">
|
|
<%= link_to "#{@network_range.asn} (#{@network_range.asn_org})", network_ranges_path(asn: @network_range.asn),
|
|
class: "text-blue-600 hover:text-blue-900 hover:underline" %>
|
|
</dd>
|
|
</div>
|
|
<% end %>
|
|
<% if @network_range.country.present? %>
|
|
<div>
|
|
<dt class="text-sm font-medium text-gray-500">Country</dt>
|
|
<dd class="mt-1 text-sm text-gray-900">
|
|
<%= link_to @network_range.country, events_path(country: @network_range.country),
|
|
class: "text-blue-600 hover:text-blue-900 hover:underline" %>
|
|
</dd>
|
|
</div>
|
|
<% end %>
|
|
<% if @network_range.is_datacenter? || @network_range.is_vpn? || @network_range.is_proxy? %>
|
|
<div class="md:col-span-2 lg:col-span-3">
|
|
<dt class="text-sm font-medium text-gray-500 mb-2">Classification</dt>
|
|
<dd class="flex flex-wrap gap-2">
|
|
<% if @network_range.is_datacenter? %>
|
|
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-orange-100 text-orange-800">Datacenter</span>
|
|
<% end %>
|
|
<% if @network_range.is_vpn? %>
|
|
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-purple-100 text-purple-800">VPN</span>
|
|
<% end %>
|
|
<% if @network_range.is_proxy? %>
|
|
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-red-100 text-red-800">Proxy</span>
|
|
<% end %>
|
|
</dd>
|
|
</div>
|
|
<% end %>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- User Agent -->
|
|
<% if @event.user_agent.present? %>
|
|
<div class="bg-white shadow rounded-lg mb-6">
|
|
<div class="px-6 py-4 border-b border-gray-200">
|
|
<h3 class="text-lg font-medium text-gray-900">User Agent</h3>
|
|
</div>
|
|
<div class="px-6 py-4">
|
|
<% ua = parse_user_agent(@event.user_agent) %>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
<div>
|
|
<dt class="text-sm font-medium text-gray-500">Browser</dt>
|
|
<dd class="mt-1 text-sm text-gray-900">
|
|
<% if ua[:name].present? %>
|
|
<%= ua[:name] %>
|
|
<% if ua[:version].present? %>
|
|
<span class="text-gray-500"><%= ua[:version] %></span>
|
|
<% end %>
|
|
<% else %>
|
|
<span class="text-gray-400">-</span>
|
|
<% end %>
|
|
</dd>
|
|
</div>
|
|
<div>
|
|
<dt class="text-sm font-medium text-gray-500">Operating System</dt>
|
|
<dd class="mt-1 text-sm text-gray-900">
|
|
<% if ua[:os_name].present? %>
|
|
<%= ua[:os_name] %>
|
|
<% if ua[:os_version].present? %>
|
|
<span class="text-gray-500"><%= ua[:os_version] %></span>
|
|
<% end %>
|
|
<% else %>
|
|
<span class="text-gray-400">-</span>
|
|
<% end %>
|
|
</dd>
|
|
</div>
|
|
<div>
|
|
<dt class="text-sm font-medium text-gray-500">Device Type</dt>
|
|
<dd class="mt-1 text-sm text-gray-900"><%= ua[:device_type]&.humanize || "-" %></dd>
|
|
</div>
|
|
<% if ua[:bot] %>
|
|
<div>
|
|
<dt class="text-sm font-medium text-gray-500">Bot Detection</dt>
|
|
<dd class="mt-1">
|
|
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-orange-100 text-orange-800">
|
|
🤖 <%= ua[:bot_name] || 'Bot' %>
|
|
</span>
|
|
</dd>
|
|
</div>
|
|
<% end %>
|
|
<div class="md:col-span-2 lg:col-span-3">
|
|
<dt class="text-sm font-medium text-gray-500">Raw User Agent</dt>
|
|
<dd class="mt-1 text-sm text-gray-900 font-mono break-all bg-gray-50 p-3 rounded"><%= @event.user_agent %></dd>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
|
|
<!-- Tags -->
|
|
<% if @event.tags.any? %>
|
|
<div class="bg-white shadow rounded-lg mb-6">
|
|
<div class="px-6 py-4 border-b border-gray-200">
|
|
<h3 class="text-lg font-medium text-gray-900">Tags</h3>
|
|
</div>
|
|
<div class="px-6 py-4">
|
|
<div class="flex flex-wrap gap-2">
|
|
<% @event.tags.each do |tag| %>
|
|
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800">
|
|
<%= tag %>
|
|
</span>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
|
|
<!-- Server Information -->
|
|
<% if @event.server_name.present? || @event.environment.present? || @event.agent_name.present? %>
|
|
<div class="bg-white shadow rounded-lg mb-6">
|
|
<div class="px-6 py-4 border-b border-gray-200">
|
|
<h3 class="text-lg font-medium text-gray-900">Server & Agent Information</h3>
|
|
</div>
|
|
<div class="px-6 py-4">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
<% if @event.server_name.present? %>
|
|
<div>
|
|
<dt class="text-sm font-medium text-gray-500">Server Name</dt>
|
|
<dd class="mt-1 text-sm text-gray-900"><%= @event.server_name %></dd>
|
|
</div>
|
|
<% end %>
|
|
<% if @event.environment.present? %>
|
|
<div>
|
|
<dt class="text-sm font-medium text-gray-500">Environment</dt>
|
|
<dd class="mt-1 text-sm text-gray-900"><%= @event.environment %></dd>
|
|
</div>
|
|
<% end %>
|
|
<% if @event.agent_name.present? %>
|
|
<div>
|
|
<dt class="text-sm font-medium text-gray-500">Agent</dt>
|
|
<dd class="mt-1 text-sm text-gray-900">
|
|
<%= @event.agent_name %>
|
|
<% if @event.agent_version.present? %>
|
|
<span class="text-gray-500">v<%= @event.agent_version %></span>
|
|
<% end %>
|
|
</dd>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
|
|
<!-- Raw Payload -->
|
|
<% if @event.payload.present? %>
|
|
<div class="bg-white shadow rounded-lg mb-6">
|
|
<div class="px-6 py-4 border-b border-gray-200">
|
|
<h3 class="text-lg font-medium text-gray-900">Raw Event Payload</h3>
|
|
</div>
|
|
<div class="px-6 py-4">
|
|
<pre class="bg-gray-50 p-4 rounded-md text-xs overflow-x-auto"><%= JSON.pretty_generate(@event.payload) %></pre>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
</div>
|