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,163 @@
1
+ <% content_for :page_title, "Dashboard" %>
2
+
3
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
4
+ <div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm rounded-lg">
5
+ <div class="px-4 py-5 sm:p-6">
6
+ <div class="flex items-center">
7
+ <div class="shrink-0 bg-green-100 dark:bg-green-700 rounded-md p-3">
8
+ <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">
9
+ <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>
10
+ </svg>
11
+ </div>
12
+ <div class="ml-5 w-0 flex-1">
13
+ <dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
14
+ Cache Hits
15
+ </dt>
16
+ <dd class="flex items-baseline">
17
+ <div class="text-2xl font-semibold text-gray-900 dark:text-white">
18
+ <%= @cache_events.hit_count %>
19
+ </div>
20
+ </dd>
21
+ </div>
22
+ </div>
23
+ </div>
24
+ </div>
25
+
26
+ <div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm rounded-lg">
27
+ <div class="px-4 py-5 sm:p-6">
28
+ <div class="flex items-center">
29
+ <div class="shrink-0 bg-amber-100 dark:bg-amber-700 rounded-md p-3">
30
+ <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">
31
+ <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>
32
+ </svg>
33
+ </div>
34
+ <div class="ml-5 w-0 flex-1">
35
+ <dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
36
+ Cache Misses
37
+ </dt>
38
+ <dd class="flex items-baseline">
39
+ <div class="text-2xl font-semibold text-gray-900 dark:text-white">
40
+ <%= @cache_events.miss_count %>
41
+ </div>
42
+ </dd>
43
+ </div>
44
+ </div>
45
+ </div>
46
+ </div>
47
+
48
+ <div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm rounded-lg">
49
+ <div class="px-4 py-5 sm:p-6">
50
+ <div class="flex items-center">
51
+ <div class="shrink-0 bg-sky-100 dark:bg-sky-700 rounded-md p-3">
52
+ <svg class="h-6 w-6 text-sky-600 dark:text-sky-100" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
53
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
54
+ </svg>
55
+ </div>
56
+ <div class="ml-5 w-0 flex-1">
57
+ <dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
58
+ Hit Ratio
59
+ </dt>
60
+ <dd class="flex items-baseline">
61
+ <div class="text-2xl font-semibold text-gray-900 dark:text-white">
62
+ <%= @cache_events.hit_percentage %>
63
+ </div>
64
+ </dd>
65
+ </div>
66
+ </div>
67
+ </div>
68
+ </div>
69
+ </div>
70
+
71
+ <div class="grid grid-cols-1 gap-6 mb-6">
72
+ <div class="bg-white dark:bg-gray-800 shadow-sm overflow-hidden rounded-lg">
73
+ <div class="px-4 py-5 sm:px-6 flex justify-between items-center">
74
+ <h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-white">Cache Activity</h3>
75
+ <div>
76
+ <div class="inline-flex rounded-md shadow-xs">
77
+ <% ["30m", "1h", "3h", "6h", "12h", "1d"].each do |period| %>
78
+ <%= link_to period.upcase, root_path(chart_period: period), class: "relative inline-flex items-center px-3 py-2 text-sm font-medium #{params[:chart_period] == period ? '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" %>
79
+ <% end %>
80
+ </div>
81
+ </div>
82
+ </div>
83
+ <div class="px-4 py-5 sm:p-6 bg-white dark:bg-gray-800">
84
+ <%= line_chart @charts, height: "300px", colors: @charts.map { |c| c[:color] }, library: { scales: { y: { beginAtZero: true }, x: { ticks: { autoSkip: true, maxTicksLimit: 10 } } } } %>
85
+ </div>
86
+ </div>
87
+ </div>
88
+
89
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
90
+ <div class="bg-white dark:bg-gray-800 shadow-sm overflow-hidden rounded-lg">
91
+ <div class="px-4 py-5 sm:px-6">
92
+ <h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-white">Recent Cache Entries</h3>
93
+ </div>
94
+ <div class="border-t border-gray-200 dark:border-gray-700">
95
+ <div class="overflow-x-auto">
96
+ <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
97
+ <thead class="bg-gray-50 dark:bg-gray-900">
98
+ <tr>
99
+ <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>
100
+ <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>
101
+ <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>
102
+ </tr>
103
+ </thead>
104
+ <tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
105
+ <% @cache_entries.take(5).each do |entry| %>
106
+ <tr>
107
+ <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white truncate max-w-[200px]">
108
+ <%= link_to entry.key, cache_entry_path(entry.key_hash), class: "hover:underline" %>
109
+ </td>
110
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
111
+ <%= entry.human_byte_size %>
112
+ </td>
113
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
114
+ <%= entry.created_at_ago %>
115
+ </td>
116
+ </tr>
117
+ <% end %>
118
+ </tbody>
119
+ </table>
120
+ </div>
121
+ <div class="bg-white dark:bg-gray-800 px-4 py-3 border-t border-gray-200 dark:border-gray-700 text-right sm:px-6">
122
+ <%= link_to "View all entries", cache_entries_path, class: "inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-600 shadow-xs text-sm font-medium rounded-md text-gray-700 dark:text-white bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700" %>
123
+ </div>
124
+ </div>
125
+ </div>
126
+
127
+ <div class="bg-white dark:bg-gray-800 shadow-sm overflow-hidden rounded-lg">
128
+ <div class="px-4 py-5 sm:px-6">
129
+ <h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-white">Recent Cache Events</h3>
130
+ </div>
131
+ <div class="border-t border-gray-200 dark:border-gray-700">
132
+ <div class="overflow-x-auto">
133
+ <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
134
+ <thead class="bg-gray-50 dark:bg-gray-900">
135
+ <tr>
136
+ <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>
137
+ <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>
138
+ <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>
139
+ </tr>
140
+ </thead>
141
+ <tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
142
+ <% @cache_events.take(5).each do |event| %>
143
+ <tr>
144
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
145
+ <%= badge event.event_type.to_s.capitalize, event.color %>
146
+ </td>
147
+ <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white truncate max-w-[200px]">
148
+ <%= event.key_string %>
149
+ </td>
150
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
151
+ <%= event.created_at_ago %>
152
+ </td>
153
+ </tr>
154
+ <% end %>
155
+ </tbody>
156
+ </table>
157
+ </div>
158
+ <div class="bg-white dark:bg-gray-800 px-4 py-3 border-t border-gray-200 dark:border-gray-700 text-right sm:px-6">
159
+ <%= link_to "View all events", cache_events_path, class: "inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-600 shadow-xs text-sm font-medium rounded-md text-gray-700 dark:text-white bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700" %>
160
+ </div>
161
+ </div>
162
+ </div>
163
+ </div>
@@ -0,0 +1,302 @@
1
+ <% content_for :page_title, "Cache Statistics" %>
2
+
3
+ <div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-6">
4
+ <!-- Cache Entries Count -->
5
+ <div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm rounded-lg">
6
+ <div class="px-4 py-5 sm:p-6">
7
+ <div class="flex items-center">
8
+ <div class="shrink-0 bg-blue-100 dark:bg-blue-700 rounded-md p-3">
9
+ <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">
10
+ <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>
11
+ </svg>
12
+ </div>
13
+ <div class="ml-5 w-0 flex-1">
14
+ <dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
15
+ Cache Entries
16
+ </dt>
17
+ <dd class="flex items-baseline">
18
+ <div class="text-2xl font-semibold text-gray-900 dark:text-white">
19
+ <%= @cache_entries_count %>
20
+ </div>
21
+ </dd>
22
+ </div>
23
+ </div>
24
+ </div>
25
+ </div>
26
+
27
+ <!-- Cache Size -->
28
+ <div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm rounded-lg">
29
+ <div class="px-4 py-5 sm:p-6">
30
+ <div class="flex items-center">
31
+ <div class="shrink-0 bg-purple-100 dark:bg-purple-700 rounded-md p-3">
32
+ <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">
33
+ <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>
34
+ </svg>
35
+ </div>
36
+ <div class="ml-5 w-0 flex-1">
37
+ <dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
38
+ Cache Size
39
+ </dt>
40
+ <dd class="flex items-baseline">
41
+ <div class="text-2xl font-semibold text-gray-900 dark:text-white">
42
+ <%= @cache_entries_human_size %>
43
+ </div>
44
+ </dd>
45
+ </div>
46
+ </div>
47
+ </div>
48
+ </div>
49
+
50
+ <!-- Hit Ratio -->
51
+ <div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm rounded-lg">
52
+ <div class="px-4 py-5 sm:p-6">
53
+ <div class="flex items-center">
54
+ <div class="shrink-0 bg-sky-100 dark:bg-sky-700 rounded-md p-3">
55
+ <svg class="h-6 w-6 text-sky-600 dark:text-sky-100" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
56
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
57
+ </svg>
58
+ </div>
59
+ <div class="ml-5 w-0 flex-1">
60
+ <dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
61
+ Hit Ratio
62
+ </dt>
63
+ <dd class="flex items-baseline">
64
+ <div class="text-2xl font-semibold text-gray-900 dark:text-white">
65
+ <%= @hit_percentage %>
66
+ </div>
67
+ </dd>
68
+ </div>
69
+ </div>
70
+ </div>
71
+ </div>
72
+
73
+ <!-- Total Operations -->
74
+ <div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm rounded-lg">
75
+ <div class="px-4 py-5 sm:p-6">
76
+ <div class="flex items-center">
77
+ <div class="shrink-0 bg-indigo-100 dark:bg-indigo-700 rounded-md p-3">
78
+ <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">
79
+ <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"></path>
80
+ </svg>
81
+ </div>
82
+ <div class="ml-5 w-0 flex-1">
83
+ <dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
84
+ Total Operations
85
+ </dt>
86
+ <dd class="flex items-baseline">
87
+ <div class="text-2xl font-semibold text-gray-900 dark:text-white">
88
+ <%= @hit_count + @miss_count + @write_count + @delete_count %>
89
+ </div>
90
+ </dd>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ </div>
96
+
97
+ <!-- Event Type Breakdown -->
98
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
99
+ <!-- Event Types -->
100
+ <div class="bg-white dark:bg-gray-800 shadow-sm overflow-hidden rounded-lg">
101
+ <div class="px-4 py-5 sm:px-6">
102
+ <h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-white">Cache Operation Metrics</h3>
103
+ </div>
104
+ <div class="border-t border-gray-200 dark:border-gray-700">
105
+ <div class="overflow-x-auto">
106
+ <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
107
+ <thead class="bg-gray-50 dark:bg-gray-900">
108
+ <tr>
109
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Operation</th>
110
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Count</th>
111
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Percentage</th>
112
+ </tr>
113
+ </thead>
114
+ <tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
115
+ <% total_events = @hit_count + @miss_count + @write_count + @delete_count %>
116
+ <% total_events = 1 if total_events.zero? %>
117
+ <tr>
118
+ <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white">
119
+ <%= badge "Hit", "green" %>
120
+ </td>
121
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
122
+ <%= @hit_count %>
123
+ </td>
124
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
125
+ <%= "#{((@hit_count.to_f / total_events) * 100).round(2)}%" %>
126
+ </td>
127
+ </tr>
128
+ <tr>
129
+ <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white">
130
+ <%= badge "Miss", "amber" %>
131
+ </td>
132
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
133
+ <%= @miss_count %>
134
+ </td>
135
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
136
+ <%= "#{((@miss_count.to_f / total_events) * 100).round(2)}%" %>
137
+ </td>
138
+ </tr>
139
+ <tr>
140
+ <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white">
141
+ <%= badge "Write", "blue" %>
142
+ </td>
143
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
144
+ <%= @write_count %>
145
+ </td>
146
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
147
+ <%= "#{((@write_count.to_f / total_events) * 100).round(2)}%" %>
148
+ </td>
149
+ </tr>
150
+ <tr>
151
+ <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white">
152
+ <%= badge "Delete", "red" %>
153
+ </td>
154
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
155
+ <%= @delete_count %>
156
+ </td>
157
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
158
+ <%= "#{((@delete_count.to_f / total_events) * 100).round(2)}%" %>
159
+ </td>
160
+ </tr>
161
+ </tbody>
162
+ </table>
163
+ </div>
164
+ </div>
165
+ </div>
166
+
167
+ <!-- Read Performance -->
168
+ <div class="bg-white dark:bg-gray-800 shadow-sm overflow-hidden rounded-lg">
169
+ <div class="px-4 py-5 sm:px-6">
170
+ <h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-white">Read Performance</h3>
171
+ </div>
172
+ <div class="border-t border-gray-200 dark:border-gray-700">
173
+ <div class="px-4 py-5 sm:p-6">
174
+ <div class="space-y-4">
175
+ <div>
176
+ <h4 class="text-sm font-medium text-gray-500 dark:text-gray-400">Hit Rate</h4>
177
+ <div class="mt-2 relative pt-1">
178
+ <div class="overflow-hidden h-6 text-xs flex rounded-full bg-gray-200 dark:bg-gray-700">
179
+ <div style="width:<%= @hit_percentage %>" class="shadow-none flex flex-col text-center whitespace-nowrap text-white justify-center bg-green-500 dark:bg-green-600 rounded-l-full">
180
+ <span class="px-2 font-semibold"><%= @hit_percentage %></span>
181
+ </div>
182
+ <div style="width:<%= 100 - (@hit_ratio * 100).round(2) %>%" class="shadow-none flex flex-col text-center whitespace-nowrap text-white justify-center bg-amber-500 dark:bg-amber-600 rounded-r-full">
183
+ <span class="px-2 font-semibold"><%= "#{(100 - (@hit_ratio * 100).round(2))}%" %></span>
184
+ </div>
185
+ </div>
186
+ <div class="mt-1 flex justify-between text-xs text-gray-500 dark:text-gray-400">
187
+ <span>Hits: <%= @hit_count %></span>
188
+ <span>Misses: <%= @miss_count %></span>
189
+ </div>
190
+ </div>
191
+ </div>
192
+
193
+ <div class="pt-4">
194
+ <h4 class="text-sm font-medium text-gray-500 dark:text-gray-400">Operation Distribution</h4>
195
+ <div class="mt-2 flex flex-col space-y-2">
196
+ <div class="relative pt-1">
197
+ <div class="flex mb-2 items-center justify-between">
198
+ <div>
199
+ <span class="text-xs font-semibold inline-block py-1 px-2 uppercase rounded-full text-green-600 bg-green-200 dark:text-green-100 dark:bg-green-700">
200
+ Reads (Hits + Misses)
201
+ </span>
202
+ </div>
203
+ <div class="text-right">
204
+ <span class="text-xs font-semibold inline-block text-green-600 dark:text-green-100">
205
+ <%= "#{(((@hit_count + @miss_count).to_f / total_events) * 100).round(2)}%" %>
206
+ </span>
207
+ </div>
208
+ </div>
209
+ <div class="overflow-hidden h-2 mb-4 text-xs flex rounded bg-gray-200 dark:bg-gray-700">
210
+ <div style="width:<%= (((@hit_count + @miss_count).to_f / total_events) * 100).round(2) %>%" class="shadow-none flex flex-col text-center whitespace-nowrap text-white justify-center bg-green-500 dark:bg-green-600"></div>
211
+ </div>
212
+ </div>
213
+ <div class="relative pt-1">
214
+ <div class="flex mb-2 items-center justify-between">
215
+ <div>
216
+ <span class="text-xs font-semibold inline-block py-1 px-2 uppercase rounded-full text-blue-600 bg-blue-200 dark:text-blue-100 dark:bg-blue-700">
217
+ Writes
218
+ </span>
219
+ </div>
220
+ <div class="text-right">
221
+ <span class="text-xs font-semibold inline-block text-blue-600 dark:text-blue-100">
222
+ <%= "#{((@write_count.to_f / total_events) * 100).round(2)}%" %>
223
+ </span>
224
+ </div>
225
+ </div>
226
+ <div class="overflow-hidden h-2 mb-4 text-xs flex rounded bg-gray-200 dark:bg-gray-700">
227
+ <div style="width:<%= ((@write_count.to_f / total_events) * 100).round(2) %>%" class="shadow-none flex flex-col text-center whitespace-nowrap text-white justify-center bg-blue-500 dark:bg-blue-600"></div>
228
+ </div>
229
+ </div>
230
+ <div class="relative pt-1">
231
+ <div class="flex mb-2 items-center justify-between">
232
+ <div>
233
+ <span class="text-xs font-semibold inline-block py-1 px-2 uppercase rounded-full text-red-600 bg-red-200 dark:text-red-100 dark:bg-red-700">
234
+ Deletes
235
+ </span>
236
+ </div>
237
+ <div class="text-right">
238
+ <span class="text-xs font-semibold inline-block text-red-600 dark:text-red-100">
239
+ <%= "#{((@delete_count.to_f / total_events) * 100).round(2)}%" %>
240
+ </span>
241
+ </div>
242
+ </div>
243
+ <div class="overflow-hidden h-2 mb-4 text-xs flex rounded bg-gray-200 dark:bg-gray-700">
244
+ <div style="width:<%= ((@delete_count.to_f / total_events) * 100).round(2) %>%" class="shadow-none flex flex-col text-center whitespace-nowrap text-white justify-center bg-red-500 dark:bg-red-600"></div>
245
+ </div>
246
+ </div>
247
+ </div>
248
+ </div>
249
+ </div>
250
+ </div>
251
+ </div>
252
+ </div>
253
+ </div>
254
+
255
+ <!-- Cache Storage Stats -->
256
+ <div class="grid grid-cols-1 gap-6">
257
+ <div class="bg-white dark:bg-gray-800 shadow-sm overflow-hidden rounded-lg">
258
+ <div class="px-4 py-5 sm:px-6">
259
+ <h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-white">Cache Storage Details</h3>
260
+ </div>
261
+ <div class="border-t border-gray-200 dark:border-gray-700">
262
+ <div class="divide-y divide-gray-200 dark:divide-gray-700">
263
+ <div class="py-4 sm:py-5 sm:grid sm:grid-cols-2 sm:gap-4 sm:px-6">
264
+ <dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
265
+ Total Entries
266
+ </dt>
267
+ <dd class="mt-1 text-sm text-gray-900 dark:text-white sm:mt-0">
268
+ <%= @cache_entries_count %>
269
+ </dd>
270
+ </div>
271
+ <div class="py-4 sm:py-5 sm:grid sm:grid-cols-2 sm:gap-4 sm:px-6">
272
+ <dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
273
+ Total Size
274
+ </dt>
275
+ <dd class="mt-1 text-sm text-gray-900 dark:text-white sm:mt-0">
276
+ <%= @cache_entries_human_size %> (<%= @cache_entries_size %> bytes)
277
+ </dd>
278
+ </div>
279
+ <div class="py-4 sm:py-5 sm:grid sm:grid-cols-2 sm:gap-4 sm:px-6">
280
+ <dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
281
+ Average Entry Size
282
+ </dt>
283
+ <dd class="mt-1 text-sm text-gray-900 dark:text-white sm:mt-0">
284
+ <% if @cache_entries_count > 0 %>
285
+ <%= ActiveSupport::NumberHelper.number_to_human_size(@cache_entries_size / @cache_entries_count) %>
286
+ <% else %>
287
+ 0 B
288
+ <% end %>
289
+ </dd>
290
+ </div>
291
+ <div class="py-4 sm:py-5 sm:grid sm:grid-cols-2 sm:gap-4 sm:px-6">
292
+ <dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
293
+ Hit Ratio
294
+ </dt>
295
+ <dd class="mt-1 text-sm text-gray-900 dark:text-white sm:mt-0">
296
+ <%= @hit_percentage %> (<%= @hit_count %> hits / <%= @hit_count + @miss_count %> reads)
297
+ </dd>
298
+ </div>
299
+ </div>
300
+ </div>
301
+ </div>
302
+ </div>
data/config/routes.rb ADDED
@@ -0,0 +1,17 @@
1
+ SolidCacheDashboard::Engine.routes.draw do
2
+ resources :cache_entries, only: [:index, :show] do
3
+ collection do
4
+ delete :clear_all
5
+ end
6
+ member do
7
+ delete :delete
8
+ end
9
+ end
10
+
11
+ resources :cache_events, only: [:index]
12
+
13
+ get "stats", to: "stats#index", as: :stats
14
+ post "appearance/toggle", to: "appearance#toggle", as: :toggle_appearance
15
+
16
+ root "dashboard#index"
17
+ end
@@ -0,0 +1,17 @@
1
+ module SolidCacheDashboard
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ source_root File.expand_path("templates", __dir__)
5
+
6
+ def create_migrations
7
+ template "create_solid_cache_dashboard_events.rb", "db/migrate/#{timestamp}_create_solid_cache_dashboard_events.rb"
8
+ end
9
+
10
+ private
11
+
12
+ def timestamp
13
+ Time.current.strftime("%Y%m%d%H%M%S")
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ class CreateSolidCacheDashboardEvents < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :solid_cache_dashboard_events do |t|
4
+ t.string :event_type, null: false
5
+ t.integer :key_hash, limit: 8, null: false
6
+ t.string :key_string
7
+ t.integer :byte_size, limit: 4
8
+ t.float :duration
9
+ t.datetime :created_at, null: false
10
+
11
+ t.index [:event_type]
12
+ t.index [:key_hash]
13
+ t.index [:created_at]
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ module SolidCacheDashboard
2
+ module CacheEntry
3
+ # Constants
4
+ ACTIVE = :active
5
+ EXPIRED = :expired
6
+
7
+ STATUSES = [ACTIVE, EXPIRED]
8
+
9
+ STATUS_COLORS = {
10
+ ACTIVE => "green",
11
+ EXPIRED => "amber"
12
+ }
13
+
14
+ def self.status_color(status)
15
+ STATUS_COLORS[status] || "zinc"
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,22 @@
1
+ module SolidCacheDashboard
2
+ module CacheEvent
3
+ # Types of cache events
4
+ HIT = :hit
5
+ MISS = :miss
6
+ WRITE = :write
7
+ DELETE = :delete
8
+
9
+ EVENT_TYPES = [HIT, MISS, WRITE, DELETE]
10
+
11
+ EVENT_COLORS = {
12
+ HIT => "green",
13
+ MISS => "amber",
14
+ WRITE => "sky",
15
+ DELETE => "red"
16
+ }
17
+
18
+ def self.event_color(event_type)
19
+ EVENT_COLORS[event_type] || "zinc"
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ module SolidCacheDashboard
2
+ class Configuration
3
+ attr_accessor :title
4
+
5
+ def initialize
6
+ @title = "Solid Cache Dashboard"
7
+ end
8
+ end
9
+
10
+ def self.configuration
11
+ @configuration ||= Configuration.new
12
+ end
13
+
14
+ def self.configure
15
+ yield(configuration)
16
+ end
17
+ end
@@ -0,0 +1,27 @@
1
+ module SolidCacheDashboard
2
+ module Decorators
3
+ class CacheEntriesDecorator
4
+ include Enumerable
5
+
6
+ delegate :current_page, :total_pages, :limit_value, :total_count, :offset_value, to: :@entries
7
+
8
+ def initialize(entries)
9
+ @entries = entries
10
+ end
11
+
12
+ def each(&block)
13
+ @entries.each do |entry|
14
+ block.call(SolidCacheDashboard.decorate(entry))
15
+ end
16
+ end
17
+
18
+ def total_size
19
+ @total_size ||= @entries.sum(:byte_size)
20
+ end
21
+
22
+ def human_total_size
23
+ ActiveSupport::NumberHelper.number_to_human_size(total_size)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,59 @@
1
+ module SolidCacheDashboard
2
+ module Decorators
3
+ class CacheEntryDecorator
4
+ def initialize(entry)
5
+ @entry = entry
6
+ end
7
+
8
+ def key_hash
9
+ @entry.key_hash
10
+ end
11
+
12
+ def key
13
+ readable_key = @entry.key.force_encoding("UTF-8")
14
+ readable_key.match?(/^[-+\/=A-Za-z0-9]+$/) ? readable_key : key_hash
15
+ rescue
16
+ key_hash
17
+ end
18
+
19
+ def value
20
+ @value ||= Marshal.load(@entry.value)
21
+ rescue
22
+ "Binary data"
23
+ end
24
+
25
+ def byte_size
26
+ @entry.byte_size
27
+ end
28
+
29
+ def human_byte_size
30
+ ActiveSupport::NumberHelper.number_to_human_size(byte_size)
31
+ end
32
+
33
+ def created_at
34
+ @entry.created_at
35
+ end
36
+
37
+ def created_at_ago
38
+ time_ago_in_words(created_at)
39
+ end
40
+
41
+ private
42
+
43
+ def time_ago_in_words(time)
44
+ distance_in_seconds = (Time.now - time).round
45
+
46
+ case distance_in_seconds
47
+ when 0..59
48
+ "#{distance_in_seconds} seconds ago"
49
+ when 60..3599
50
+ "#{(distance_in_seconds / 60).round} minutes ago"
51
+ when 3600..86399
52
+ "#{(distance_in_seconds / 3600).round} hours ago"
53
+ else
54
+ "#{(distance_in_seconds / 86400).round} days ago"
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end