Tidy up homepage and navigation

This commit is contained in:
Dan Milne
2025-11-09 20:58:13 +11:00
parent c9e2992fe0
commit 1f4428348d
56 changed files with 2822 additions and 955 deletions

View File

@@ -1,112 +1,157 @@
<div class="d-flex justify-content-between align-items-center mb-4">
<h1><%= @project.name %> - Events</h1>
<% content_for :title, "Events - Baffle Hub" %>
<div class="space-y-6">
<!-- Header -->
<div>
<%= link_to "← Back to Project", @project, class: "btn btn-secondary" %>
<%= link_to "Analytics", analytics_project_path(@project), class: "btn btn-info" %>
<h1 class="text-3xl font-bold text-gray-900">Events</h1>
<p class="mt-2 text-gray-600">WAF event log and analysis</p>
</div>
</div>
<!-- Filters -->
<div class="card mb-4">
<div class="card-header">
<h5>Filters</h5>
<!-- Filters -->
<div class="bg-white shadow rounded-lg">
<div class="px-6 py-4 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-900">Filters</h3>
</div>
<div class="p-6">
<%= form_with url: events_path, method: :get, local: true, class: "space-y-4" do |form| %>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div>
<%= form.label :ip, "IP Address", class: "block text-sm font-medium text-gray-700" %>
<%= form.text_field :ip, value: params[:ip],
class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm",
placeholder: "Filter by IP" %>
</div>
<div>
<%= form.label :waf_action, "Action", class: "block text-sm font-medium text-gray-700" %>
<%= form.select :waf_action,
options_for_select([['All', ''], ['Allow', 'allow'], ['Block', 'block'], ['Challenge', 'challenge']], params[:waf_action]),
{ }, { class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" } %>
</div>
<div>
<%= form.label :country, "Country", class: "block text-sm font-medium text-gray-700" %>
<%= form.text_field :country, value: params[:country],
class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm",
placeholder: "Country code (e.g. US)" %>
</div>
<div class="flex items-end space-x-2">
<%= form.submit "Apply Filters",
class: "inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" %>
<%= link_to "Clear", events_path,
class: "inline-flex justify-center py-2 px-4 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" %>
</div>
</div>
<% end %>
</div>
</div>
<div class="card-body">
<%= form_with url: project_events_path(@project), method: :get, local: true, class: "row g-3" do |form| %>
<div class="col-md-3">
<%= form.label :ip, "IP Address", class: "form-label" %>
<%= form.text_field :ip, value: params[:ip], class: "form-control", placeholder: "Filter by IP" %>
</div>
<div class="col-md-3">
<%= form.label :waf_action, "Action", class: "form-label" %>
<%= form.select :waf_action,
options_for_select([['All', ''], ['Allow', 'allow'], ['Block', 'block'], ['Challenge', 'challenge']], params[:waf_action]),
{}, { class: "form-select" } %>
</div>
<div class="col-md-3">
<%= form.label :country, "Country", class: "form-label" %>
<%= form.text_field :country, value: params[:country], class: "form-control", placeholder: "Country code (e.g. US)" %>
</div>
<div class="col-md-3 d-flex align-items-end">
<%= form.submit "Apply Filters", class: "btn btn-primary me-2" %>
<%= link_to "Clear", project_events_path(@project), class: "btn btn-outline-secondary" %>
</div>
<% end %>
</div>
</div>
<!-- Events Table -->
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5>Events (<%= @events.count %>)</h5>
</div>
<div class="card-body">
<% if @events.any? %>
<div class="table-responsive">
<table class="table">
<thead>
<!-- Events Table -->
<div class="bg-white shadow rounded-lg">
<div class="px-6 py-4 border-b border-gray-200">
<div class="flex items-center justify-between">
<h3 class="text-lg font-medium text-gray-900">Events (<%= number_with_delimiter(@events.count) %>)</h3>
<div class="flex items-center space-x-4">
<%= link_to "📊 Analytics Dashboard", analytics_path,
class: "text-sm text-blue-600 hover:text-blue-800 font-medium" %>
<% if @pagy.pages > 1 %>
<span class="text-sm text-gray-500">
Page <%= @pagy.page %> of <%= @pagy.pages %>
</span>
<% end %>
</div>
</div>
<!-- Top Pagination -->
<% if @pagy.pages > 1 %>
<div class="mt-4">
<%= pagy_nav_tailwind(@pagy, pagy_id: 'events_top') %>
</div>
<% end %>
</div>
<div class="overflow-x-auto">
<% if @events.any? %>
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th>Time</th>
<th>IP Address</th>
<th>Action</th>
<th>Path</th>
<th>Method</th>
<th>Status</th>
<th>Country</th>
<th>User Agent</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Time</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">IP Address</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Action</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Path</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Method</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Country</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">User Agent</th>
</tr>
</thead>
<tbody>
<tbody class="bg-white divide-y divide-gray-200">
<% @events.each do |event| %>
<tr>
<td><%= event.timestamp.strftime("%Y-%m-%d %H:%M:%S") %></td>
<td><code><%= event.ip_address %></code></td>
<td>
<span class="badge bg-<%= event.blocked? ? 'danger' : event.allowed? ? 'success' : 'warning' %>">
<tr class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
<%= event.timestamp.strftime("%Y-%m-%d %H:%M:%S") %>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-mono text-gray-900">
<%= event.ip_address %>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
<%= case event.waf_action
when 'allow' then 'bg-green-100 text-green-800'
when 'deny', 'block' then 'bg-red-100 text-red-800'
when 'challenge' then 'bg-yellow-100 text-yellow-800'
else 'bg-gray-100 text-gray-800'
end %>">
<%= event.waf_action %>
</span>
</td>
<td><code><%= event.request_path %></code></td>
<td><%= event.request_method %></td>
<td><%= event.response_status %></td>
<td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-mono text-gray-900 max-w-xs truncate" title="<%= event.request_path %>">
<%= event.request_path || '-' %>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
<%= event.request_method ? event.request_method.upcase : '-' %>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
<%= event.response_status || '-' %>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
<% if event.country_code.present? %>
<span class="badge bg-light text-dark"><%= event.country_code %></span>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">
<%= event.country_code %>
</span>
<% else %>
<span class="text-muted">-</span>
<span class="text-gray-400">-</span>
<% end %>
</td>
<td class="text-truncate" style="max-width: 200px;" title="<%= event.user_agent %>">
<%= event.user_agent&.truncate(30) || '-' %>
<td class="px-6 py-4 text-sm text-gray-900 max-w-xs truncate" title="<%= event.user_agent %>">
<%= event.user_agent&.truncate(50) || '-' %>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
<!-- Pagination -->
<% if @pagy.pages > 1 %>
<div class="d-flex justify-content-center mt-4">
<%== pagy_nav(@pagy) %>
</div>
<div class="text-center text-muted mt-2">
Showing <%= @pagy.from %> to <%= @pagy.to %> of <%= @pagy.count %> events
<!-- Bottom Pagination -->
<% if @pagy.pages > 1 %>
<%= pagy_nav_tailwind(@pagy, pagy_id: 'events_bottom') %>
<% end %>
<% else %>
<div class="px-6 py-12 text-center">
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<h3 class="mt-2 text-sm font-medium text-gray-900">No events</h3>
<p class="mt-1 text-sm text-gray-500">
<% if params[:ip].present? || params[:waf_action].present? || params[:country].present? %>
No events found matching your filters.
<% else %>
No events have been received yet.
<% end %>
</p>
<% if params[:ip].present? || params[:waf_action].present? || params[:country].present? %>
<div class="mt-6">
<%= link_to "Clear Filters", events_path,
class: "inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-blue-600 bg-blue-100 hover:bg-blue-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" %>
</div>
<% end %>
</div>
<% end %>
<% else %>
<div class="text-center py-5">
<p class="text-muted mb-3">
<% if params[:ip].present? || params[:waf_action].present? || params[:country].present? %>
No events found matching your filters.
<% else %>
No events have been received yet.
<% end %>
</p>
<% if params[:ip].present? || params[:waf_action].present? || params[:country].present? %>
<%= link_to "Clear Filters", project_events_path(@project), class: "btn btn-outline-primary" %>
<% end %>
</div>
<% end %>
</div>
</div>
</div>