solid_cache_dashboard 0.0.1

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 (41) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +121 -0
  3. data/Rakefile +10 -0
  4. data/app/assets/javascripts/solid_cache_dashboard/alpine.js +5 -0
  5. data/app/assets/javascripts/solid_cache_dashboard/application.js +60 -0
  6. data/app/assets/stylesheets/solid_cache_dashboard/application.css +1468 -0
  7. data/app/assets/stylesheets/solid_cache_dashboard/tailwind.css +625 -0
  8. data/app/controllers/solid_cache_dashboard/appearance_controller.rb +9 -0
  9. data/app/controllers/solid_cache_dashboard/application_controller.rb +12 -0
  10. data/app/controllers/solid_cache_dashboard/cache_entries_controller.rb +37 -0
  11. data/app/controllers/solid_cache_dashboard/cache_events_controller.rb +9 -0
  12. data/app/controllers/solid_cache_dashboard/dashboard_controller.rb +69 -0
  13. data/app/controllers/solid_cache_dashboard/stats_controller.rb +19 -0
  14. data/app/helpers/solid_cache_dashboard/application_helper.rb +20 -0
  15. data/app/views/layouts/solid_cache_dashboard/application.html.erb +28 -0
  16. data/app/views/solid_cache_dashboard/application/_flash_messages.html.erb +10 -0
  17. data/app/views/solid_cache_dashboard/application/_footer.html.erb +12 -0
  18. data/app/views/solid_cache_dashboard/application/_navbar.html.erb +32 -0
  19. data/app/views/solid_cache_dashboard/cache_entries/index.html.erb +136 -0
  20. data/app/views/solid_cache_dashboard/cache_entries/show.html.erb +118 -0
  21. data/app/views/solid_cache_dashboard/cache_events/index.html.erb +152 -0
  22. data/app/views/solid_cache_dashboard/dashboard/index.html.erb +163 -0
  23. data/app/views/solid_cache_dashboard/stats/index.html.erb +302 -0
  24. data/config/routes.rb +17 -0
  25. data/lib/generators/solid_cache_dashboard/install/install_generator.rb +17 -0
  26. data/lib/generators/solid_cache_dashboard/install/templates/create_solid_cache_dashboard_events.rb +16 -0
  27. data/lib/solid_cache_dashboard/cache_entry.rb +18 -0
  28. data/lib/solid_cache_dashboard/cache_event.rb +22 -0
  29. data/lib/solid_cache_dashboard/configuration.rb +17 -0
  30. data/lib/solid_cache_dashboard/decorators/cache_entries_decorator.rb +27 -0
  31. data/lib/solid_cache_dashboard/decorators/cache_entry_decorator.rb +59 -0
  32. data/lib/solid_cache_dashboard/decorators/cache_event_decorator.rb +72 -0
  33. data/lib/solid_cache_dashboard/decorators/cache_events_decorator.rb +44 -0
  34. data/lib/solid_cache_dashboard/engine.rb +19 -0
  35. data/lib/solid_cache_dashboard/instrumentation.rb +58 -0
  36. data/lib/solid_cache_dashboard/models/cache_event.rb +51 -0
  37. data/lib/solid_cache_dashboard/version.rb +5 -0
  38. data/lib/solid_cache_dashboard.rb +39 -0
  39. data/package-lock.json +1040 -0
  40. data/package.json +16 -0
  41. metadata +125 -0
@@ -0,0 +1,69 @@
1
+ module SolidCacheDashboard
2
+ class DashboardController < ApplicationController
3
+ def index
4
+ @cache_entries = SolidCacheDashboard.decorate(SolidCache::Entry.all)
5
+ @cache_events = SolidCacheDashboard.decorate(SolidCacheDashboard::CacheEvent.all)
6
+ load_charts
7
+ end
8
+
9
+ private
10
+
11
+ def load_charts
12
+ case params[:chart_period] || "30m"
13
+ when "15m"
14
+ n = 1
15
+ last = 15
16
+ when "30m"
17
+ n = 1
18
+ last = 30
19
+ when "1h"
20
+ n = 2
21
+ last = 30
22
+ when "3h"
23
+ n = 6
24
+ last = 30
25
+ when "6h"
26
+ n = 12
27
+ last = 30
28
+ when "12h"
29
+ n = 20
30
+ last = 36
31
+ when "1d"
32
+ n = 30
33
+ last = 48
34
+ when "3d"
35
+ n = 90 # 1.5 hours
36
+ last = 48
37
+ when "1w"
38
+ n = 180 # 3 hours
39
+ last = 56
40
+ else
41
+ n = 1
42
+ last = 30
43
+ end
44
+
45
+ @charts = [
46
+ {
47
+ name: "Hits",
48
+ data: SolidCacheDashboard::CacheEvent.hits.group_by_minute(:created_at, last: last, n: n).count,
49
+ color: "#23C55E"
50
+ },
51
+ {
52
+ name: "Misses",
53
+ data: SolidCacheDashboard::CacheEvent.misses.group_by_minute(:created_at, last: last, n: n).count,
54
+ color: "#FBBF26"
55
+ },
56
+ {
57
+ name: "Writes",
58
+ data: SolidCacheDashboard::CacheEvent.writes.group_by_minute(:created_at, last: last, n: n).count,
59
+ color: "#38BDF8"
60
+ },
61
+ {
62
+ name: "Deletes",
63
+ data: SolidCacheDashboard::CacheEvent.deletes.group_by_minute(:created_at, last: last, n: n).count,
64
+ color: "#F04444"
65
+ }
66
+ ]
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,19 @@
1
+ module SolidCacheDashboard
2
+ class StatsController < ApplicationController
3
+ def index
4
+ @cache_entries = SolidCacheDashboard.decorate(SolidCache::Entry.all)
5
+ @cache_entries_count = SolidCache::Entry.count
6
+ @cache_entries_size = SolidCache::Entry.sum(:byte_size)
7
+ @cache_entries_human_size = ActiveSupport::NumberHelper.number_to_human_size(@cache_entries_size)
8
+
9
+ @cache_events = SolidCacheDashboard.decorate(SolidCacheDashboard::CacheEvent.all)
10
+ @hit_count = SolidCacheDashboard::CacheEvent.hits.count
11
+ @miss_count = SolidCacheDashboard::CacheEvent.misses.count
12
+ @write_count = SolidCacheDashboard::CacheEvent.writes.count
13
+ @delete_count = SolidCacheDashboard::CacheEvent.deletes.count
14
+
15
+ @hit_ratio = @hit_count + @miss_count > 0 ? @hit_count.to_f / (@hit_count + @miss_count) : 0
16
+ @hit_percentage = "#{(@hit_ratio * 100).round(2)}%"
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ module SolidCacheDashboard
2
+ module ApplicationHelper
3
+ def page_title
4
+ [
5
+ content_for(:page_title),
6
+ SolidCacheDashboard.configuration.title
7
+ ].compact.join(" - ")
8
+ end
9
+
10
+ def badge(text, type)
11
+ tag.span(text, class: "px-2 py-1 text-xs font-medium text-#{type}-700 bg-#{type}-100 rounded-full dark:bg-#{type}-700 dark:text-#{type}-100")
12
+ end
13
+
14
+ def format_date(date)
15
+ return "N/A" unless date.is_a?(Time)
16
+
17
+ date.strftime("%Y-%m-%d %H:%M:%S")
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,28 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="<%= dark_mode? ? 'dark' : '' %>">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title><%= page_title %></title>
7
+ <%= csrf_meta_tags %>
8
+ <%= csp_meta_tag %>
9
+ <%= stylesheet_link_tag "solid_cache_dashboard/application", media: "all" %>
10
+ <%= javascript_include_tag "solid_cache_dashboard/application", defer: true %>
11
+ <%= javascript_include_tag "chartkick" %>
12
+ <%= javascript_include_tag "Chart.bundle" %>
13
+ <%= javascript_include_tag "solid_cache_dashboard/alpine.js", defer: true %>
14
+ <%= yield :head %>
15
+ </head>
16
+ <body class="antialiased bg-white dark:bg-gray-900 text-gray-900 dark:text-white">
17
+ <div class="min-h-screen flex flex-col">
18
+ <%= render "solid_cache_dashboard/application/navbar" %>
19
+
20
+ <main class="grow container mx-auto px-4 py-8">
21
+ <%= render "solid_cache_dashboard/application/flash_messages" %>
22
+ <%= yield %>
23
+ </main>
24
+
25
+ <%= render "solid_cache_dashboard/application/footer" %>
26
+ </div>
27
+ </body>
28
+ </html>
@@ -0,0 +1,10 @@
1
+ <% if flash.any? %>
2
+ <div class="mb-4">
3
+ <% flash.each do |type, message| %>
4
+ <% alert_class = type.to_s == "notice" ? "bg-green-100 text-green-700 dark:bg-green-700 dark:text-green-100" : "bg-red-100 text-red-700 dark:bg-red-700 dark:text-red-100" %>
5
+ <div class="<%= alert_class %> p-4 rounded mb-4" role="alert">
6
+ <%= message %>
7
+ </div>
8
+ <% end %>
9
+ </div>
10
+ <% end %>
@@ -0,0 +1,12 @@
1
+ <footer class="bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 py-4 mt-auto">
2
+ <div class="container mx-auto px-4">
3
+ <div class="flex justify-between text-gray-500 dark:text-gray-400 text-sm">
4
+ <div>
5
+ <p>&copy; <%= Time.current.year %> Solid Cache Dashboard</p>
6
+ </div>
7
+ <div>
8
+ <span>Version <%= SolidCacheDashboard::VERSION %></span>
9
+ </div>
10
+ </div>
11
+ </div>
12
+ </footer>
@@ -0,0 +1,32 @@
1
+ <nav class="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
2
+ <div class="container mx-auto px-4">
3
+ <div class="flex justify-between h-16">
4
+ <div class="flex">
5
+ <div class="shrink-0 flex items-center">
6
+ <h1 class="text-xl font-bold">
7
+ <%= link_to SolidCacheDashboard.configuration.title, root_path, class: "text-gray-900 dark:text-white" %>
8
+ </h1>
9
+ </div>
10
+ <div class="hidden sm:ml-6 sm:flex sm:items-center sm:space-x-4">
11
+ <%= link_to "Dashboard", root_path, class: "px-3 py-2 rounded-md text-sm font-medium #{'bg-gray-100 dark:bg-gray-700' if controller_name == 'dashboard'} hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-900 dark:text-white" %>
12
+ <%= link_to "Cache Entries", cache_entries_path, class: "px-3 py-2 rounded-md text-sm font-medium #{'bg-gray-100 dark:bg-gray-700' if controller_name == 'cache_entries'} hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-900 dark:text-white" %>
13
+ <%= link_to "Cache Events", cache_events_path, class: "px-3 py-2 rounded-md text-sm font-medium #{'bg-gray-100 dark:bg-gray-700' if controller_name == 'cache_events'} hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-900 dark:text-white" %>
14
+ <%= link_to "Stats", stats_path, class: "px-3 py-2 rounded-md text-sm font-medium #{'bg-gray-100 dark:bg-gray-700' if controller_name == 'stats'} hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-900 dark:text-white" %>
15
+ </div>
16
+ </div>
17
+ <div class="hidden sm:ml-6 sm:flex sm:items-center">
18
+ <%= button_to toggle_appearance_path, method: :post, class: "bg-white dark:bg-gray-800 p-1 rounded-full text-gray-400 hover:text-gray-500 dark:hover:text-gray-300 focus:outline-hidden" do %>
19
+ <% if dark_mode? %>
20
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
21
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
22
+ </svg>
23
+ <% else %>
24
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
25
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
26
+ </svg>
27
+ <% end %>
28
+ <% end %>
29
+ </div>
30
+ </div>
31
+ </div>
32
+ </nav>
@@ -0,0 +1,136 @@
1
+ <% content_for :page_title, "Cache Entries" %>
2
+
3
+ <div class="bg-white dark:bg-gray-800 shadow-sm overflow-hidden rounded-lg mb-6">
4
+ <div class="px-4 py-5 sm:px-6 flex justify-between items-center">
5
+ <h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-white">Cache Entries</h3>
6
+ <div class="flex space-x-2">
7
+ <%= button_to clear_all_cache_entries_path, method: :delete, class: "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500", data: { confirm: "Are you sure you want to clear all cache entries? This cannot be undone." } do %>
8
+ <svg class="-ml-1 mr-2 h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
9
+ <path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" />
10
+ </svg>
11
+ Clear All Cache Entries
12
+ <% end %>
13
+ </div>
14
+ </div>
15
+ <div class="border-t border-gray-200 dark:border-gray-700">
16
+ <div class="overflow-x-auto">
17
+ <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
18
+ <thead class="bg-gray-50 dark:bg-gray-900">
19
+ <tr>
20
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Key</th>
21
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Size</th>
22
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Created</th>
23
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Actions</th>
24
+ </tr>
25
+ </thead>
26
+ <tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
27
+ <% if @cache_entries.present? %>
28
+ <% @cache_entries.each do |entry| %>
29
+ <tr>
30
+ <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white truncate max-w-[400px]">
31
+ <%= link_to entry.key, cache_entry_path(entry.key_hash), class: "hover:underline" %>
32
+ </td>
33
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
34
+ <%= entry.human_byte_size %>
35
+ </td>
36
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
37
+ <%= entry.created_at_ago %>
38
+ </td>
39
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
40
+ <%= button_to delete_cache_entry_path(entry.key_hash), method: :delete, class: "text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300" do %>
41
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
42
+ <path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
43
+ </svg>
44
+ <% end %>
45
+ </td>
46
+ </tr>
47
+ <% end %>
48
+ <% else %>
49
+ <tr>
50
+ <td colspan="4" class="px-6 py-4 text-sm text-center text-gray-500 dark:text-gray-400">
51
+ No cache entries found
52
+ </td>
53
+ </tr>
54
+ <% end %>
55
+ </tbody>
56
+ </table>
57
+ </div>
58
+ </div>
59
+ </div>
60
+
61
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
62
+ <!-- Total Entries -->
63
+ <div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm rounded-lg">
64
+ <div class="px-4 py-5 sm:p-6">
65
+ <div class="flex items-center">
66
+ <div class="shrink-0 bg-blue-100 dark:bg-blue-700 rounded-md p-3">
67
+ <svg class="h-6 w-6 text-blue-600 dark:text-blue-100" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
68
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"></path>
69
+ </svg>
70
+ </div>
71
+ <div class="ml-5 w-0 flex-1">
72
+ <dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
73
+ Total Entries
74
+ </dt>
75
+ <dd class="flex items-baseline">
76
+ <div class="text-2xl font-semibold text-gray-900 dark:text-white">
77
+ <%= SolidCache::Entry.count %>
78
+ </div>
79
+ </dd>
80
+ </div>
81
+ </div>
82
+ </div>
83
+ </div>
84
+
85
+ <!-- Total Size -->
86
+ <div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm rounded-lg">
87
+ <div class="px-4 py-5 sm:p-6">
88
+ <div class="flex items-center">
89
+ <div class="shrink-0 bg-purple-100 dark:bg-purple-700 rounded-md p-3">
90
+ <svg class="h-6 w-6 text-purple-600 dark:text-purple-100" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
91
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4"></path>
92
+ </svg>
93
+ </div>
94
+ <div class="ml-5 w-0 flex-1">
95
+ <dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
96
+ Total Size
97
+ </dt>
98
+ <dd class="flex items-baseline">
99
+ <div class="text-2xl font-semibold text-gray-900 dark:text-white">
100
+ <%= ActiveSupport::NumberHelper.number_to_human_size(SolidCache::Entry.sum(:byte_size)) %>
101
+ </div>
102
+ </dd>
103
+ </div>
104
+ </div>
105
+ </div>
106
+ </div>
107
+
108
+ <!-- Average Size -->
109
+ <div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm rounded-lg">
110
+ <div class="px-4 py-5 sm:p-6">
111
+ <div class="flex items-center">
112
+ <div class="shrink-0 bg-indigo-100 dark:bg-indigo-700 rounded-md p-3">
113
+ <svg class="h-6 w-6 text-indigo-600 dark:text-indigo-100" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
114
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path>
115
+ </svg>
116
+ </div>
117
+ <div class="ml-5 w-0 flex-1">
118
+ <dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
119
+ Average Entry Size
120
+ </dt>
121
+ <dd class="flex items-baseline">
122
+ <div class="text-2xl font-semibold text-gray-900 dark:text-white">
123
+ <% count = SolidCache::Entry.count %>
124
+ <% size = SolidCache::Entry.sum(:byte_size) %>
125
+ <% if count > 0 %>
126
+ <%= ActiveSupport::NumberHelper.number_to_human_size(size / count) %>
127
+ <% else %>
128
+ 0 B
129
+ <% end %>
130
+ </div>
131
+ </dd>
132
+ </div>
133
+ </div>
134
+ </div>
135
+ </div>
136
+ </div>
@@ -0,0 +1,118 @@
1
+ <% content_for :page_title, "Cache Entry: #{@cache_entry.key}" %>
2
+
3
+ <div class="mb-4">
4
+ <%= link_to cache_entries_path, class: "inline-flex items-center text-sm font-medium text-blue-600 dark:text-blue-400 hover:underline" do %>
5
+ <svg class="mr-1 w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
6
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path>
7
+ </svg>
8
+ Back to Cache Entries
9
+ <% end %>
10
+ </div>
11
+
12
+ <div class="bg-white dark:bg-gray-800 shadow-sm overflow-hidden rounded-lg mb-6">
13
+ <div class="px-4 py-5 sm:px-6 flex justify-between items-center">
14
+ <h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-white">Cache Entry Details</h3>
15
+ <div>
16
+ <%= button_to delete_cache_entry_path(@cache_entry.key_hash), method: :delete, class: "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500", data: { confirm: "Are you sure you want to delete this cache entry? This cannot be undone." } do %>
17
+ <svg class="-ml-1 mr-2 h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
18
+ <path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" />
19
+ </svg>
20
+ Delete Cache Entry
21
+ <% end %>
22
+ </div>
23
+ </div>
24
+ <div class="border-t border-gray-200 dark:border-gray-700">
25
+ <dl>
26
+ <div class="bg-gray-50 dark:bg-gray-900 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
27
+ <dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Key</dt>
28
+ <dd class="mt-1 text-sm text-gray-900 dark:text-white sm:mt-0 sm:col-span-2 break-all">
29
+ <%= @cache_entry.key %>
30
+ </dd>
31
+ </div>
32
+ <div class="bg-white dark:bg-gray-800 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
33
+ <dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Key Hash</dt>
34
+ <dd class="mt-1 text-sm text-gray-900 dark:text-white sm:mt-0 sm:col-span-2">
35
+ <%= @cache_entry.key_hash %>
36
+ </dd>
37
+ </div>
38
+ <div class="bg-gray-50 dark:bg-gray-900 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
39
+ <dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Size</dt>
40
+ <dd class="mt-1 text-sm text-gray-900 dark:text-white sm:mt-0 sm:col-span-2">
41
+ <%= @cache_entry.human_byte_size %> (<%= @cache_entry.byte_size %> bytes)
42
+ </dd>
43
+ </div>
44
+ <div class="bg-white dark:bg-gray-800 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
45
+ <dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Created At</dt>
46
+ <dd class="mt-1 text-sm text-gray-900 dark:text-white sm:mt-0 sm:col-span-2">
47
+ <%= @cache_entry.created_at.to_fs(:long) %> (<%= @cache_entry.created_at_ago %>)
48
+ </dd>
49
+ </div>
50
+ <div class="bg-gray-50 dark:bg-gray-900 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
51
+ <dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Value Type</dt>
52
+ <dd class="mt-1 text-sm text-gray-900 dark:text-white sm:mt-0 sm:col-span-2">
53
+ <%= @cache_entry.value.class.name rescue "Unknown" %>
54
+ </dd>
55
+ </div>
56
+ </dl>
57
+ </div>
58
+ </div>
59
+
60
+ <div class="bg-white dark:bg-gray-800 shadow-sm overflow-hidden rounded-lg">
61
+ <div class="px-4 py-5 sm:px-6">
62
+ <h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-white">Cache Value</h3>
63
+ </div>
64
+ <div class="border-t border-gray-200 dark:border-gray-700 px-4 py-5 sm:p-6">
65
+ <div class="bg-gray-100 dark:bg-gray-900 rounded-md p-4 overflow-x-auto">
66
+ <pre class="text-sm text-gray-800 dark:text-gray-300 whitespace-pre-wrap"><%= @cache_entry.value.inspect rescue "Unable to inspect value" %></pre>
67
+ </div>
68
+ </div>
69
+ </div>
70
+
71
+ <% if defined?(SolidCacheDashboard::CacheEvent) %>
72
+ <div class="mt-6 bg-white dark:bg-gray-800 shadow-sm overflow-hidden rounded-lg">
73
+ <div class="px-4 py-5 sm:px-6">
74
+ <h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-white">Recent Events for this Key</h3>
75
+ </div>
76
+ <div class="border-t border-gray-200 dark:border-gray-700">
77
+ <div class="overflow-x-auto">
78
+ <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
79
+ <thead class="bg-gray-50 dark:bg-gray-900">
80
+ <tr>
81
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Type</th>
82
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Time</th>
83
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Duration</th>
84
+ </tr>
85
+ </thead>
86
+ <tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
87
+ <% events = SolidCacheDashboard.decorate(SolidCacheDashboard::CacheEvent.where(key_hash: @cache_entry.key_hash).order(created_at: :desc).limit(10)) %>
88
+ <% if events.present? %>
89
+ <% events.each do |event| %>
90
+ <tr>
91
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
92
+ <%= badge event.event_type.to_s.capitalize, event.color %>
93
+ </td>
94
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
95
+ <%= event.created_at_ago %>
96
+ </td>
97
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
98
+ <% if event.duration %>
99
+ <%= "#{event.duration.round(2)} ms" %>
100
+ <% else %>
101
+ -
102
+ <% end %>
103
+ </td>
104
+ </tr>
105
+ <% end %>
106
+ <% else %>
107
+ <tr>
108
+ <td colspan="3" class="px-6 py-4 text-sm text-center text-gray-500 dark:text-gray-400">
109
+ No events found for this key
110
+ </td>
111
+ </tr>
112
+ <% end %>
113
+ </tbody>
114
+ </table>
115
+ </div>
116
+ </div>
117
+ </div>
118
+ <% end %>
@@ -0,0 +1,152 @@
1
+ <% content_for :page_title, "Cache Events" %>
2
+
3
+ <div class="bg-white dark:bg-gray-800 shadow-sm overflow-hidden rounded-lg mb-6">
4
+ <div class="px-4 py-5 sm:px-6 flex justify-between items-center">
5
+ <h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-white">Cache Events</h3>
6
+ <div class="flex space-x-2">
7
+ <div class="inline-flex rounded-md shadow-xs">
8
+ <% ["all", "hit", "miss", "write", "delete"].each do |type| %>
9
+ <%= link_to type.capitalize, cache_events_path(event_type: type), class: "relative inline-flex items-center px-3 py-2 text-sm font-medium #{params[:event_type] == type ? 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-white' : 'bg-white dark:bg-gray-800 text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700'} border border-gray-300 dark:border-gray-600" %>
10
+ <% end %>
11
+ </div>
12
+ </div>
13
+ </div>
14
+ <div class="border-t border-gray-200 dark:border-gray-700">
15
+ <div class="overflow-x-auto">
16
+ <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
17
+ <thead class="bg-gray-50 dark:bg-gray-900">
18
+ <tr>
19
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Type</th>
20
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Key</th>
21
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Time</th>
22
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Duration</th>
23
+ </tr>
24
+ </thead>
25
+ <tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
26
+ <% if @cache_events.present? %>
27
+ <% @cache_events.each do |event| %>
28
+ <tr>
29
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
30
+ <%= badge event.event_type.to_s.capitalize, event.color %>
31
+ </td>
32
+ <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white truncate max-w-[400px]">
33
+ <%= event.key_string %>
34
+ </td>
35
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
36
+ <%= event.created_at_ago %>
37
+ </td>
38
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
39
+ <% if event.duration %>
40
+ <%= "#{event.duration.round(2)} ms" %>
41
+ <% else %>
42
+ -
43
+ <% end %>
44
+ </td>
45
+ </tr>
46
+ <% end %>
47
+ <% else %>
48
+ <tr>
49
+ <td colspan="4" class="px-6 py-4 text-sm text-center text-gray-500 dark:text-gray-400">
50
+ No cache events found
51
+ </td>
52
+ </tr>
53
+ <% end %>
54
+ </tbody>
55
+ </table>
56
+ </div>
57
+ </div>
58
+ </div>
59
+
60
+ <div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-6">
61
+ <!-- Hit Count -->
62
+ <div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm rounded-lg">
63
+ <div class="px-4 py-5 sm:p-6">
64
+ <div class="flex items-center">
65
+ <div class="shrink-0 bg-green-100 dark:bg-green-700 rounded-md p-3">
66
+ <svg class="h-6 w-6 text-green-600 dark:text-green-100" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
67
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
68
+ </svg>
69
+ </div>
70
+ <div class="ml-5 w-0 flex-1">
71
+ <dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
72
+ Cache Hits
73
+ </dt>
74
+ <dd class="flex items-baseline">
75
+ <div class="text-2xl font-semibold text-gray-900 dark:text-white">
76
+ <%= SolidCacheDashboard::CacheEvent.hits.count %>
77
+ </div>
78
+ </dd>
79
+ </div>
80
+ </div>
81
+ </div>
82
+ </div>
83
+
84
+ <!-- Miss Count -->
85
+ <div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm rounded-lg">
86
+ <div class="px-4 py-5 sm:p-6">
87
+ <div class="flex items-center">
88
+ <div class="shrink-0 bg-amber-100 dark:bg-amber-700 rounded-md p-3">
89
+ <svg class="h-6 w-6 text-amber-600 dark:text-amber-100" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
90
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
91
+ </svg>
92
+ </div>
93
+ <div class="ml-5 w-0 flex-1">
94
+ <dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
95
+ Cache Misses
96
+ </dt>
97
+ <dd class="flex items-baseline">
98
+ <div class="text-2xl font-semibold text-gray-900 dark:text-white">
99
+ <%= SolidCacheDashboard::CacheEvent.misses.count %>
100
+ </div>
101
+ </dd>
102
+ </div>
103
+ </div>
104
+ </div>
105
+ </div>
106
+
107
+ <!-- Write Count -->
108
+ <div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm rounded-lg">
109
+ <div class="px-4 py-5 sm:p-6">
110
+ <div class="flex items-center">
111
+ <div class="shrink-0 bg-blue-100 dark:bg-blue-700 rounded-md p-3">
112
+ <svg class="h-6 w-6 text-blue-600 dark:text-blue-100" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
113
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"></path>
114
+ </svg>
115
+ </div>
116
+ <div class="ml-5 w-0 flex-1">
117
+ <dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
118
+ Cache Writes
119
+ </dt>
120
+ <dd class="flex items-baseline">
121
+ <div class="text-2xl font-semibold text-gray-900 dark:text-white">
122
+ <%= SolidCacheDashboard::CacheEvent.writes.count %>
123
+ </div>
124
+ </dd>
125
+ </div>
126
+ </div>
127
+ </div>
128
+ </div>
129
+
130
+ <!-- Delete Count -->
131
+ <div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm rounded-lg">
132
+ <div class="px-4 py-5 sm:p-6">
133
+ <div class="flex items-center">
134
+ <div class="shrink-0 bg-red-100 dark:bg-red-700 rounded-md p-3">
135
+ <svg class="h-6 w-6 text-red-600 dark:text-red-100" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
136
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
137
+ </svg>
138
+ </div>
139
+ <div class="ml-5 w-0 flex-1">
140
+ <dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
141
+ Cache Deletes
142
+ </dt>
143
+ <dd class="flex items-baseline">
144
+ <div class="text-2xl font-semibold text-gray-900 dark:text-white">
145
+ <%= SolidCacheDashboard::CacheEvent.deletes.count %>
146
+ </div>
147
+ </dd>
148
+ </div>
149
+ </div>
150
+ </div>
151
+ </div>
152
+ </div>