rails_event_viewer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +14 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +451 -0
  5. data/Rakefile +16 -0
  6. data/app/assets/stylesheets/rails_event_viewer/application.css +32 -0
  7. data/app/assets/stylesheets/rails_event_viewer/tailwind.css +54 -0
  8. data/app/controllers/rails_event_viewer/analytics_controller.rb +78 -0
  9. data/app/controllers/rails_event_viewer/application_controller.rb +84 -0
  10. data/app/controllers/rails_event_viewer/dashboard_controller.rb +18 -0
  11. data/app/controllers/rails_event_viewer/event_types_controller.rb +16 -0
  12. data/app/controllers/rails_event_viewer/events_controller.rb +72 -0
  13. data/app/controllers/rails_event_viewer/groups_controller.rb +52 -0
  14. data/app/helpers/rails_event_viewer/application_helper.rb +163 -0
  15. data/app/models/rails_event_viewer/application_record.rb +5 -0
  16. data/app/models/rails_event_viewer/entry.rb +34 -0
  17. data/app/views/layouts/rails_event_viewer/application.html.erb +45 -0
  18. data/app/views/rails_event_viewer/analytics/overview.html.erb +134 -0
  19. data/app/views/rails_event_viewer/dashboard/index.html.erb +113 -0
  20. data/app/views/rails_event_viewer/event_types/index.html.erb +59 -0
  21. data/app/views/rails_event_viewer/event_types/show.html.erb +102 -0
  22. data/app/views/rails_event_viewer/events/_event_row.html.erb +42 -0
  23. data/app/views/rails_event_viewer/events/_filters_sidebar.html.erb +66 -0
  24. data/app/views/rails_event_viewer/events/index.html.erb +65 -0
  25. data/app/views/rails_event_viewer/events/show.html.erb +155 -0
  26. data/app/views/rails_event_viewer/groups/index.html.erb +115 -0
  27. data/app/views/rails_event_viewer/groups/show.html.erb +134 -0
  28. data/app/views/rails_event_viewer/shared/_navigation.html.erb +47 -0
  29. data/app/views/rails_event_viewer/shared/_pagination.html.erb +60 -0
  30. data/app/views/rails_event_viewer/shared/_stat_card.html.erb +36 -0
  31. data/config/routes.rb +18 -0
  32. data/db/migrate/20241208000001_create_rails_event_viewer_entries.rb +36 -0
  33. data/lib/generators/rails_event_viewer/install/USAGE +33 -0
  34. data/lib/generators/rails_event_viewer/install/install_generator.rb +86 -0
  35. data/lib/generators/rails_event_viewer/install/templates/create_rails_event_viewer_entries.rb.erb +35 -0
  36. data/lib/generators/rails_event_viewer/install/templates/initializer.rb.erb +90 -0
  37. data/lib/rails_event_viewer/adapter.rb +93 -0
  38. data/lib/rails_event_viewer/adapters/active_record.rb +227 -0
  39. data/lib/rails_event_viewer/adapters/concerns/in_memory_filtering.rb +136 -0
  40. data/lib/rails_event_viewer/adapters/memory.rb +128 -0
  41. data/lib/rails_event_viewer/adapters/null.rb +63 -0
  42. data/lib/rails_event_viewer/adapters/redis.rb +213 -0
  43. data/lib/rails_event_viewer/engine.rb +24 -0
  44. data/lib/rails_event_viewer/events_relation.rb +130 -0
  45. data/lib/rails_event_viewer/json_query.rb +111 -0
  46. data/lib/rails_event_viewer/pagy_countable.rb +31 -0
  47. data/lib/rails_event_viewer/subscriber.rb +255 -0
  48. data/lib/rails_event_viewer/time_utils.rb +24 -0
  49. data/lib/rails_event_viewer/version.rb +3 -0
  50. data/lib/rails_event_viewer.rb +122 -0
  51. data/lib/tasks/rails_event_viewer_tasks.rake +81 -0
  52. metadata +151 -0
@@ -0,0 +1,113 @@
1
+ <% content_for(:title, "Dashboard - Rails Event Viewer") %>
2
+
3
+ <div class="space-y-6">
4
+ <h1 class="text-2xl font-bold text-gray-900">Dashboard</h1>
5
+
6
+ <!-- Stats cards -->
7
+ <div class="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-4">
8
+ <%= render "rails_event_viewer/shared/stat_card",
9
+ title: "Total Events",
10
+ value: number_with_delimiter(@total_events),
11
+ icon: "database" %>
12
+ <%= render "rails_event_viewer/shared/stat_card",
13
+ title: "Events Today",
14
+ value: number_with_delimiter(@events_today),
15
+ icon: "calendar" %>
16
+ <%= render "rails_event_viewer/shared/stat_card",
17
+ title: "Event Types",
18
+ value: @event_names.count,
19
+ icon: "tag" %>
20
+ <%= render "rails_event_viewer/shared/stat_card",
21
+ title: "Avg/Hour (Today)",
22
+ value: @events_today > 0 ? (@events_today.to_f / [Time.current.hour, 1].max).round(1) : 0,
23
+ icon: "clock" %>
24
+ </div>
25
+
26
+ <!-- Charts row -->
27
+ <% if analytics_supported? %>
28
+ <div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
29
+ <!-- Events over time chart -->
30
+ <div class="bg-white rounded-lg shadow p-6 overflow-hidden">
31
+ <h3 class="text-lg font-medium text-gray-900 mb-4">Events (Last 24 Hours)</h3>
32
+ <div class="h-72 overflow-hidden">
33
+ <% if @events_over_time.any? %>
34
+ <%= line_chart @events_over_time,
35
+ id: "events-timeline",
36
+ height: "100%",
37
+ colors: ["#4F46E5"],
38
+ library: { plugins: { legend: { display: false } } } %>
39
+ <% else %>
40
+ <div class="flex items-center justify-center h-full text-gray-400">
41
+ No events in the last 24 hours
42
+ </div>
43
+ <% end %>
44
+ </div>
45
+ </div>
46
+
47
+ <!-- Events by type chart -->
48
+ <div class="bg-white rounded-lg shadow p-6 overflow-hidden">
49
+ <h3 class="text-lg font-medium text-gray-900 mb-4">Events by Type</h3>
50
+ <div class="h-72 overflow-hidden">
51
+ <% if @events_by_type.any? %>
52
+ <%= pie_chart @events_by_type,
53
+ id: "events-by-type",
54
+ donut: true,
55
+ height: "100%",
56
+ colors: ["#4F46E5", "#10B981", "#F59E0B", "#EF4444", "#8B5CF6", "#EC4899", "#06B6D4", "#84CC16"] %>
57
+ <% else %>
58
+ <div class="flex items-center justify-center h-full text-gray-400">
59
+ No events to display
60
+ </div>
61
+ <% end %>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ <% end %>
66
+
67
+ <!-- Recent events -->
68
+ <div class="bg-white rounded-lg shadow">
69
+ <div class="px-6 py-4 border-b border-gray-200 flex justify-between items-center">
70
+ <h3 class="text-lg font-medium text-gray-900">Recent Events</h3>
71
+ <%= link_to "View all", rails_event_viewer.events_path, class: "text-indigo-600 hover:text-indigo-500 text-sm font-medium" %>
72
+ </div>
73
+
74
+ <% if @recent_events.any? %>
75
+ <div class="overflow-x-auto">
76
+ <table class="min-w-full divide-y divide-gray-200">
77
+ <thead class="bg-gray-50">
78
+ <tr>
79
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Event</th>
80
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Payload</th>
81
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Time</th>
82
+ </tr>
83
+ </thead>
84
+ <tbody class="bg-white divide-y divide-gray-200">
85
+ <% @recent_events.each do |event| %>
86
+ <tr class="hover:bg-gray-50 cursor-pointer" onclick="window.location='<%= rails_event_viewer.event_path(event_id(event)) %>'">
87
+ <td class="px-6 py-4 whitespace-nowrap">
88
+ <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-indigo-100 text-indigo-800">
89
+ <%= event_name(event) %>
90
+ </span>
91
+ </td>
92
+ <td class="px-6 py-4 text-sm text-gray-500">
93
+ <code class="text-xs bg-gray-100 px-2 py-1 rounded"><%= truncate_json(event_payload(event), length: 60) %></code>
94
+ </td>
95
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
96
+ <%= time_ago_with_title(event_occurred_at(event)) %>
97
+ </td>
98
+ </tr>
99
+ <% end %>
100
+ </tbody>
101
+ </table>
102
+ </div>
103
+ <% else %>
104
+ <div class="px-6 py-12 text-center text-gray-500">
105
+ <svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
106
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
107
+ </svg>
108
+ <h3 class="mt-2 text-sm font-medium text-gray-900">No events yet</h3>
109
+ <p class="mt-1 text-sm text-gray-500">Events will appear here once they are captured.</p>
110
+ </div>
111
+ <% end %>
112
+ </div>
113
+ </div>
@@ -0,0 +1,59 @@
1
+ <% content_for(:title, "Event Types - Rails Event Viewer") %>
2
+
3
+ <div class="space-y-6">
4
+ <div class="flex justify-between items-center">
5
+ <h1 class="text-2xl font-bold text-gray-900">Event Types</h1>
6
+ <div class="text-sm text-gray-500">
7
+ <%= @event_types.count %> event types
8
+ </div>
9
+ </div>
10
+
11
+ <% if @event_types.any? %>
12
+ <div class="bg-white shadow rounded-lg overflow-hidden">
13
+ <table class="min-w-full divide-y divide-gray-200">
14
+ <thead class="bg-gray-50">
15
+ <tr>
16
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Event Type</th>
17
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Count</th>
18
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Last Event</th>
19
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"></th>
20
+ </tr>
21
+ </thead>
22
+ <tbody class="bg-white divide-y divide-gray-200">
23
+ <% @event_types.each do |event_type| %>
24
+ <tr class="hover:bg-gray-50">
25
+ <td class="px-6 py-4 whitespace-nowrap">
26
+ <%= link_to rails_event_viewer.event_type_path(name: event_type[:name]), class: "text-indigo-600 hover:text-indigo-900 font-medium" do %>
27
+ <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-sm font-medium bg-indigo-100 text-indigo-800">
28
+ <%= event_type[:name] %>
29
+ </span>
30
+ <% end %>
31
+ </td>
32
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
33
+ <%= number_with_delimiter(event_type[:count]) %>
34
+ </td>
35
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
36
+ <% if event_type[:last_event_at] %>
37
+ <%= time_ago_with_title(event_type[:last_event_at]) %>
38
+ <% else %>
39
+ -
40
+ <% end %>
41
+ </td>
42
+ <td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
43
+ <%= link_to "View events", rails_event_viewer.events_path(name: event_type[:name]), class: "text-indigo-600 hover:text-indigo-900" %>
44
+ </td>
45
+ </tr>
46
+ <% end %>
47
+ </tbody>
48
+ </table>
49
+ </div>
50
+ <% else %>
51
+ <div class="bg-white rounded-lg shadow px-6 py-12 text-center text-gray-500">
52
+ <svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
53
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A2 2 0 013 12V7a4 4 0 014-4z" />
54
+ </svg>
55
+ <h3 class="mt-2 text-sm font-medium text-gray-900">No event types</h3>
56
+ <p class="mt-1 text-sm text-gray-500">Event types will appear here once events are captured.</p>
57
+ </div>
58
+ <% end %>
59
+ </div>
@@ -0,0 +1,102 @@
1
+ <% content_for(:title, "#{@event_type_name} - Rails Event Viewer") %>
2
+
3
+ <div class="space-y-6">
4
+ <!-- Header -->
5
+ <div class="flex items-center justify-between">
6
+ <div class="flex items-center space-x-4">
7
+ <%= link_to rails_event_viewer.event_types_path, class: "text-gray-400 hover:text-gray-600" do %>
8
+ <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
9
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
10
+ </svg>
11
+ <% end %>
12
+ <div>
13
+ <h1 class="text-2xl font-bold text-gray-900">
14
+ <span class="inline-flex items-center px-3 py-1 rounded-full text-xl font-medium bg-indigo-100 text-indigo-800">
15
+ <%= @event_type_name %>
16
+ </span>
17
+ </h1>
18
+ </div>
19
+ </div>
20
+ </div>
21
+
22
+ <!-- Stats -->
23
+ <div class="grid grid-cols-1 gap-5 sm:grid-cols-3">
24
+ <%= render "rails_event_viewer/shared/stat_card",
25
+ title: "Total Events",
26
+ value: number_with_delimiter(@total_count),
27
+ icon: "database" %>
28
+ <%= render "rails_event_viewer/shared/stat_card",
29
+ title: "Events Today",
30
+ value: number_with_delimiter(@events_today),
31
+ icon: "calendar" %>
32
+ <%= render "rails_event_viewer/shared/stat_card",
33
+ title: "Page",
34
+ value: "#{@pagy.page} of #{@pagy.pages}",
35
+ icon: "tag" %>
36
+ </div>
37
+
38
+ <!-- Events list -->
39
+ <div class="bg-white shadow rounded-lg overflow-hidden">
40
+ <div class="px-6 py-4 border-b border-gray-200">
41
+ <h3 class="text-lg font-medium text-gray-900">Events</h3>
42
+ </div>
43
+
44
+ <% if @events.any? %>
45
+ <div class="overflow-x-auto">
46
+ <table class="min-w-full divide-y divide-gray-200">
47
+ <thead class="bg-gray-50">
48
+ <tr>
49
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Payload</th>
50
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Tags</th>
51
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Time</th>
52
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Source</th>
53
+ </tr>
54
+ </thead>
55
+ <tbody class="bg-white divide-y divide-gray-200">
56
+ <% @events.each do |event| %>
57
+ <tr class="hover:bg-gray-50 cursor-pointer" onclick="window.location='<%= rails_event_viewer.event_path(event_id(event)) %>'">
58
+ <td class="px-6 py-4">
59
+ <code class="text-xs bg-gray-100 px-2 py-1 rounded"><%= truncate_json(event_payload(event), length: 80) %></code>
60
+ </td>
61
+ <td class="px-6 py-4 whitespace-nowrap">
62
+ <% tags = event_tags(event) %>
63
+ <% if tags.present? %>
64
+ <div class="flex flex-wrap gap-1">
65
+ <% tags.first(2).each do |key, value| %>
66
+ <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-800">
67
+ <%= key %>: <%= value.to_s.truncate(15) %>
68
+ </span>
69
+ <% end %>
70
+ <% if tags.size > 2 %>
71
+ <span class="text-xs text-gray-500">+<%= tags.size - 2 %></span>
72
+ <% end %>
73
+ </div>
74
+ <% else %>
75
+ <span class="text-gray-400 text-sm">-</span>
76
+ <% end %>
77
+ </td>
78
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
79
+ <%= time_ago_with_title(event_occurred_at(event)) %>
80
+ </td>
81
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
82
+ <% filepath = event_source_file(event) %>
83
+ <% if filepath.present? %>
84
+ <code class="text-xs"><%= event_short_filepath(event) %>:<%= event_source_line(event) %></code>
85
+ <% else %>
86
+ <span class="text-gray-400">-</span>
87
+ <% end %>
88
+ </td>
89
+ </tr>
90
+ <% end %>
91
+ </tbody>
92
+ </table>
93
+ </div>
94
+
95
+ <%= render "rails_event_viewer/shared/pagination", pagy: @pagy %>
96
+ <% else %>
97
+ <div class="px-6 py-12 text-center text-gray-500">
98
+ No events of this type yet.
99
+ </div>
100
+ <% end %>
101
+ </div>
102
+ </div>
@@ -0,0 +1,42 @@
1
+ <tr class="hover:bg-gray-50 cursor-pointer" onclick="window.location='<%= rails_event_viewer.event_path(event_id(event)) %>'">
2
+ <td class="px-6 py-4">
3
+ <div class="flex flex-col max-w-md">
4
+ <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-indigo-100 text-indigo-800 w-fit">
5
+ <%= event_name(event) %>
6
+ </span>
7
+ <div class="text-sm text-gray-500 mt-1 truncate">
8
+ <code class="text-xs"><%= truncate_json(event_payload(event), length: 50) %></code>
9
+ </div>
10
+ </div>
11
+ </td>
12
+ <td class="px-6 py-4">
13
+ <% tags = event_tags(event) %>
14
+ <% if tags.present? %>
15
+ <div class="flex flex-wrap gap-1 max-w-xs">
16
+ <% tags.first(3).each do |key, value| %>
17
+ <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-800">
18
+ <%= key %>: <%= value.to_s.truncate(15) %>
19
+ </span>
20
+ <% end %>
21
+ <% if tags.size > 3 %>
22
+ <span class="text-xs text-gray-500">+<%= tags.size - 3 %> more</span>
23
+ <% end %>
24
+ </div>
25
+ <% else %>
26
+ <span class="text-gray-400 text-sm">-</span>
27
+ <% end %>
28
+ </td>
29
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
30
+ <%= time_ago_with_title(event_occurred_at(event)) %>
31
+ </td>
32
+ <td class="px-6 py-4 text-sm text-gray-500">
33
+ <% filepath = event_source_file(event) %>
34
+ <% if filepath.present? %>
35
+ <code class="text-xs bg-gray-100 px-2 py-1 rounded block truncate max-w-xs" title="<%= filepath %>">
36
+ <%= truncate(event_short_filepath(event), length: 30) %>:<%= event_source_line(event) %>
37
+ </code>
38
+ <% else %>
39
+ <span class="text-gray-400">-</span>
40
+ <% end %>
41
+ </td>
42
+ </tr>
@@ -0,0 +1,66 @@
1
+ <div class="bg-white rounded-lg shadow p-5 space-y-5">
2
+ <h3 class="font-medium text-gray-900">Filters</h3>
3
+
4
+ <%= form_with url: rails_event_viewer.events_path, method: :get, data: { turbo_frame: "events_list" }, class: "space-y-5" do |f| %>
5
+
6
+ <!-- Event name filter -->
7
+ <div>
8
+ <label for="name" class="block text-sm font-medium text-gray-700 mb-2">Event Name</label>
9
+ <%= f.select :name,
10
+ options_for_select([["All events", ""]] + event_names.map { |n| [n, n] }, params[:name]),
11
+ {},
12
+ class: "block w-full rounded-md border border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 py-2 px-3" %>
13
+ </div>
14
+
15
+ <!-- Date range -->
16
+ <div>
17
+ <label class="block text-sm font-medium text-gray-700 mb-2">Date Range</label>
18
+ <div class="space-y-2">
19
+ <%= f.date_field :start_date,
20
+ value: @start_date,
21
+ placeholder: "From",
22
+ class: "block w-full rounded-md border border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 py-2 px-3" %>
23
+ <%= f.date_field :end_date,
24
+ value: @end_date,
25
+ placeholder: "To",
26
+ class: "block w-full rounded-md border border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 py-2 px-3" %>
27
+ </div>
28
+ </div>
29
+
30
+ <!-- Tag filter -->
31
+ <div>
32
+ <label class="block text-sm font-medium text-gray-700 mb-1">Tag</label>
33
+ <p class="text-xs text-gray-500 mb-2">Filter by event tags (e.g., env, user_id)</p>
34
+ <%= f.text_field :tag_key,
35
+ value: params[:tag_key],
36
+ placeholder: "e.g., environment",
37
+ class: "block w-full rounded-md border border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 py-2 px-3 mb-2" %>
38
+ <%= f.text_field :tag_value,
39
+ value: params[:tag_value],
40
+ placeholder: "e.g., production",
41
+ class: "block w-full rounded-md border border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 py-2 px-3" %>
42
+ </div>
43
+
44
+ <!-- Context filter -->
45
+ <div>
46
+ <label class="block text-sm font-medium text-gray-700 mb-1">Context</label>
47
+ <p class="text-xs text-gray-500 mb-2">Filter by request context (e.g., request_id)</p>
48
+ <%= f.text_field :context_key,
49
+ value: params[:context_key],
50
+ placeholder: "e.g., request_id",
51
+ class: "block w-full rounded-md border border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 py-2 px-3 mb-2" %>
52
+ <%= f.text_field :context_value,
53
+ value: params[:context_value],
54
+ placeholder: "e.g., abc-123",
55
+ class: "block w-full rounded-md border border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 py-2 px-3" %>
56
+ </div>
57
+
58
+ <div class="flex space-x-3 pt-3">
59
+ <%= f.submit "Apply",
60
+ class: "flex-1 bg-indigo-600 text-white px-4 py-2.5 rounded-md font-medium hover:bg-indigo-700 cursor-pointer" %>
61
+ <%= link_to "Clear",
62
+ rails_event_viewer.events_path,
63
+ class: "flex-1 text-center bg-gray-100 text-gray-700 px-4 py-2.5 rounded-md font-medium hover:bg-gray-200" %>
64
+ </div>
65
+ <% end %>
66
+ </div>
@@ -0,0 +1,65 @@
1
+ <% content_for(:title, "Events - Rails Event Viewer") %>
2
+
3
+ <div class="space-y-6">
4
+ <div class="flex justify-between items-center">
5
+ <h1 class="text-2xl font-bold text-gray-900">
6
+ Events
7
+ <% if @query.present? %>
8
+ <span class="text-gray-500 font-normal text-lg">
9
+ matching "<%= @query %>"
10
+ </span>
11
+ <% end %>
12
+ </h1>
13
+ <div class="text-sm text-gray-500">
14
+ <%= @pagy.count %> total events
15
+ </div>
16
+ </div>
17
+
18
+ <div class="flex gap-6">
19
+ <!-- Filters sidebar -->
20
+ <aside class="w-72 flex-shrink-0">
21
+ <%= render "rails_event_viewer/events/filters_sidebar", event_names: @event_names %>
22
+ </aside>
23
+
24
+ <!-- Events list -->
25
+ <div class="flex-1 min-w-0">
26
+ <div class="bg-white shadow rounded-lg overflow-hidden">
27
+ <% if @events.any? %>
28
+ <div class="overflow-x-auto">
29
+ <table class="w-full divide-y divide-gray-200 table-fixed">
30
+ <thead class="bg-gray-50">
31
+ <tr>
32
+ <th class="w-2/5 px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Event</th>
33
+ <th class="w-1/5 px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Tags</th>
34
+ <th class="w-1/5 px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Time</th>
35
+ <th class="w-1/5 px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Source</th>
36
+ </tr>
37
+ </thead>
38
+ <tbody class="bg-white divide-y divide-gray-200">
39
+ <% @events.each do |event| %>
40
+ <%= render "rails_event_viewer/events/event_row", event: event %>
41
+ <% end %>
42
+ </tbody>
43
+ </table>
44
+ </div>
45
+
46
+ <%= render "rails_event_viewer/shared/pagination", pagy: @pagy %>
47
+ <% else %>
48
+ <div class="px-6 py-12 text-center text-gray-500">
49
+ <svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
50
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
51
+ </svg>
52
+ <h3 class="mt-2 text-sm font-medium text-gray-900">No events found</h3>
53
+ <p class="mt-1 text-sm text-gray-500">
54
+ <% if @query.present? || params[:name].present? %>
55
+ Try adjusting your filters or search query.
56
+ <% else %>
57
+ Events will appear here once they are captured.
58
+ <% end %>
59
+ </p>
60
+ </div>
61
+ <% end %>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ </div>
@@ -0,0 +1,155 @@
1
+ <% content_for(:title, "#{event_name(@event)} - Rails Event Viewer") %>
2
+
3
+ <div class="space-y-6">
4
+ <!-- Header -->
5
+ <div class="flex items-center space-x-4">
6
+ <%= link_to rails_event_viewer.events_path, class: "text-gray-400 hover:text-gray-600", aria: { label: "Back to events" } do %>
7
+ <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
8
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
9
+ </svg>
10
+ <% end %>
11
+ <div>
12
+ <h1 class="text-2xl font-bold text-gray-900"><%= event_name(@event) %></h1>
13
+ <p class="text-sm text-gray-500">Event ID: <%= event_id(@event) %></p>
14
+ </div>
15
+ </div>
16
+
17
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
18
+ <!-- Main content -->
19
+ <div class="lg:col-span-2 space-y-6">
20
+ <!-- Payload -->
21
+ <div class="bg-white rounded-lg shadow">
22
+ <div class="px-6 py-4 border-b border-gray-200 flex justify-between items-center">
23
+ <h3 class="text-lg font-medium text-gray-900">Payload</h3>
24
+ <button type="button"
25
+ onclick="navigator.clipboard.writeText('<%= j event_payload(@event).to_json %>'); this.textContent='Copied!'; setTimeout(() => this.textContent='Copy', 1500)"
26
+ class="text-indigo-600 hover:text-indigo-500 text-sm font-medium">
27
+ Copy
28
+ </button>
29
+ </div>
30
+ <div class="p-6">
31
+ <% payload = event_payload(@event) %>
32
+ <% if payload.present? %>
33
+ <div class="font-mono text-sm">
34
+ <%= json_tree(payload) %>
35
+ </div>
36
+ <% else %>
37
+ <span class="text-gray-400">Empty payload</span>
38
+ <% end %>
39
+ </div>
40
+ </div>
41
+
42
+ <!-- Tags -->
43
+ <% tags = event_tags(@event) %>
44
+ <% if tags.present? %>
45
+ <div class="bg-white rounded-lg shadow">
46
+ <div class="px-6 py-4 border-b border-gray-200">
47
+ <h3 class="text-lg font-medium text-gray-900">Tags</h3>
48
+ </div>
49
+ <div class="p-6">
50
+ <div class="font-mono text-sm">
51
+ <%= json_tree(tags) %>
52
+ </div>
53
+ </div>
54
+ </div>
55
+ <% end %>
56
+
57
+ <!-- Context -->
58
+ <% context = event_context(@event) %>
59
+ <% if context.present? %>
60
+ <div class="bg-white rounded-lg shadow">
61
+ <div class="px-6 py-4 border-b border-gray-200">
62
+ <h3 class="text-lg font-medium text-gray-900">Context</h3>
63
+ </div>
64
+ <div class="p-6">
65
+ <div class="font-mono text-sm">
66
+ <%= json_tree(context) %>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ <% end %>
71
+ </div>
72
+
73
+ <!-- Sidebar -->
74
+ <div class="space-y-6">
75
+ <!-- Metadata -->
76
+ <div class="bg-white rounded-lg shadow">
77
+ <div class="px-6 py-4 border-b border-gray-200">
78
+ <h3 class="text-lg font-medium text-gray-900">Metadata</h3>
79
+ </div>
80
+ <dl class="divide-y divide-gray-200">
81
+ <div class="px-6 py-4">
82
+ <dt class="text-sm font-medium text-gray-500">Event Timestamp</dt>
83
+ <dd class="mt-1 text-sm text-gray-900">
84
+ <%= format_event_time(@event) %>
85
+ </dd>
86
+ </div>
87
+ <% if @event.respond_to?(:created_at) && @event.created_at %>
88
+ <div class="px-6 py-4">
89
+ <dt class="text-sm font-medium text-gray-500">Created At</dt>
90
+ <dd class="mt-1 text-sm text-gray-900">
91
+ <%= @event.created_at.strftime("%Y-%m-%d %H:%M:%S %Z") %>
92
+ </dd>
93
+ </div>
94
+ <% end %>
95
+ <% filepath = event_source_file(@event) %>
96
+ <% if filepath.present? %>
97
+ <div class="px-6 py-4">
98
+ <dt class="text-sm font-medium text-gray-500">Source Location</dt>
99
+ <dd class="mt-1 text-sm text-gray-900">
100
+ <code class="bg-gray-100 px-2 py-1 rounded text-xs break-all">
101
+ <%= filepath %>:<%= event_source_line(@event) %>
102
+ </code>
103
+ <% label = event_source_label(@event) %>
104
+ <% if label.present? %>
105
+ <div class="mt-1 text-gray-600 text-xs"><%= label %></div>
106
+ <% end %>
107
+ </dd>
108
+ </div>
109
+ <% end %>
110
+ </dl>
111
+ </div>
112
+
113
+ <!-- Related Events -->
114
+ <% if @related_events.any? %>
115
+ <div class="bg-white rounded-lg shadow">
116
+ <div class="px-6 py-4 border-b border-gray-200">
117
+ <h3 class="text-lg font-medium text-gray-900">Related Events</h3>
118
+ <p class="text-xs text-gray-500">Same request context</p>
119
+ </div>
120
+ <ul class="divide-y divide-gray-200">
121
+ <% @related_events.each do |related| %>
122
+ <li class="px-6 py-3 hover:bg-gray-50">
123
+ <%= link_to rails_event_viewer.event_path(event_id(related)), class: "block" do %>
124
+ <div class="text-sm font-medium text-indigo-600 hover:text-indigo-500">
125
+ <%= event_name(related) %>
126
+ </div>
127
+ <div class="text-xs text-gray-500">
128
+ <%= time_ago_with_title(event_occurred_at(related)) %>
129
+ </div>
130
+ <% end %>
131
+ </li>
132
+ <% end %>
133
+ </ul>
134
+ </div>
135
+ <% end %>
136
+
137
+ <!-- Quick Links -->
138
+ <div class="bg-white rounded-lg shadow">
139
+ <div class="px-6 py-4 border-b border-gray-200">
140
+ <h3 class="text-lg font-medium text-gray-900">Quick Links</h3>
141
+ </div>
142
+ <div class="p-4 space-y-2">
143
+ <%= link_to rails_event_viewer.events_path(name: event_name(@event)),
144
+ class: "block text-sm text-indigo-600 hover:text-indigo-500" do %>
145
+ View all "<%= event_name(@event) %>" events
146
+ <% end %>
147
+ <%= link_to rails_event_viewer.event_type_path(name: event_name(@event)),
148
+ class: "block text-sm text-indigo-600 hover:text-indigo-500" do %>
149
+ View "<%= event_name(@event) %>" event type
150
+ <% end %>
151
+ </div>
152
+ </div>
153
+ </div>
154
+ </div>
155
+ </div>