solid_queue_monitor 1.2.2 → 2.0.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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +36 -1
  3. data/app/assets/javascripts/solid_queue_monitor/application.js +393 -0
  4. data/app/{services/solid_queue_monitor/stylesheet_generator.rb → assets/stylesheets/solid_queue_monitor/application.css} +80 -12
  5. data/app/controllers/solid_queue_monitor/application_controller.rb +2 -2
  6. data/app/controllers/solid_queue_monitor/assets_controller.rb +52 -0
  7. data/app/controllers/solid_queue_monitor/base_controller.rb +0 -28
  8. data/app/controllers/solid_queue_monitor/failed_jobs_controller.rb +3 -6
  9. data/app/controllers/solid_queue_monitor/in_progress_jobs_controller.rb +3 -6
  10. data/app/controllers/solid_queue_monitor/jobs_controller.rb +3 -6
  11. data/app/controllers/solid_queue_monitor/overview_controller.rb +3 -12
  12. data/app/controllers/solid_queue_monitor/queues_controller.rb +4 -18
  13. data/app/controllers/solid_queue_monitor/ready_jobs_controller.rb +3 -6
  14. data/app/controllers/solid_queue_monitor/recurring_jobs_controller.rb +3 -6
  15. data/app/controllers/solid_queue_monitor/scheduled_jobs_controller.rb +3 -6
  16. data/app/controllers/solid_queue_monitor/search_controller.rb +3 -4
  17. data/app/controllers/solid_queue_monitor/workers_controller.rb +24 -8
  18. data/app/helpers/solid_queue_monitor/application_helper.rb +46 -0
  19. data/app/helpers/solid_queue_monitor/chart_helper.rb +293 -0
  20. data/app/helpers/solid_queue_monitor/job_details_helper.rb +66 -0
  21. data/app/helpers/solid_queue_monitor/jobs_helper.rb +134 -0
  22. data/app/helpers/solid_queue_monitor/pagination_helper.rb +23 -0
  23. data/app/helpers/solid_queue_monitor/sort_helper.rb +30 -0
  24. data/app/helpers/solid_queue_monitor/workers_helper.rb +88 -0
  25. data/app/services/solid_queue_monitor/asset_cache.rb +56 -0
  26. data/app/services/solid_queue_monitor/chart_data_service.rb +2 -2
  27. data/app/views/layouts/solid_queue_monitor/application.html.erb +25 -0
  28. data/app/views/solid_queue_monitor/failed_jobs/_row.html.erb +26 -0
  29. data/app/views/solid_queue_monitor/failed_jobs/index.html.erb +38 -0
  30. data/app/views/solid_queue_monitor/in_progress_jobs/_row.html.erb +13 -0
  31. data/app/views/solid_queue_monitor/in_progress_jobs/index.html.erb +25 -0
  32. data/app/views/solid_queue_monitor/jobs/_arguments.html.erb +9 -0
  33. data/app/views/solid_queue_monitor/jobs/_error.html.erb +26 -0
  34. data/app/views/solid_queue_monitor/jobs/_execution_history.html.erb +25 -0
  35. data/app/views/solid_queue_monitor/jobs/_header.html.erb +37 -0
  36. data/app/views/solid_queue_monitor/jobs/_metadata.html.erb +22 -0
  37. data/app/views/solid_queue_monitor/jobs/_raw_data.html.erb +11 -0
  38. data/app/views/solid_queue_monitor/jobs/_timeline.html.erb +29 -0
  39. data/app/views/solid_queue_monitor/jobs/_timing.html.erb +9 -0
  40. data/app/views/solid_queue_monitor/jobs/_worker.html.erb +12 -0
  41. data/app/views/solid_queue_monitor/jobs/show.html.erb +22 -0
  42. data/app/views/solid_queue_monitor/overview/_chart.html.erb +1 -0
  43. data/app/views/solid_queue_monitor/overview/_recent_job_row.html.erb +26 -0
  44. data/app/views/solid_queue_monitor/overview/_recent_jobs.html.erb +31 -0
  45. data/app/views/solid_queue_monitor/overview/_stat_card.html.erb +4 -0
  46. data/app/views/solid_queue_monitor/overview/_stats.html.erb +11 -0
  47. data/app/views/solid_queue_monitor/overview/index.html.erb +9 -0
  48. data/app/views/solid_queue_monitor/queues/_job_row.html.erb +26 -0
  49. data/app/views/solid_queue_monitor/queues/_row.html.erb +33 -0
  50. data/app/views/solid_queue_monitor/queues/index.html.erb +18 -0
  51. data/app/views/solid_queue_monitor/queues/show.html.erb +63 -0
  52. data/app/views/solid_queue_monitor/ready_jobs/_row.html.erb +7 -0
  53. data/app/views/solid_queue_monitor/ready_jobs/index.html.erb +25 -0
  54. data/app/views/solid_queue_monitor/recurring_jobs/_row.html.erb +8 -0
  55. data/app/views/solid_queue_monitor/recurring_jobs/index.html.erb +26 -0
  56. data/app/views/solid_queue_monitor/scheduled_jobs/_row.html.erb +7 -0
  57. data/app/views/solid_queue_monitor/scheduled_jobs/index.html.erb +36 -0
  58. data/app/views/solid_queue_monitor/search/_completed_row.html.erb +6 -0
  59. data/app/views/solid_queue_monitor/search/_failed_row.html.erb +6 -0
  60. data/app/views/solid_queue_monitor/search/_job_row.html.erb +9 -0
  61. data/app/views/solid_queue_monitor/search/_recurring_row.html.erb +6 -0
  62. data/app/views/solid_queue_monitor/search/_section.html.erb +25 -0
  63. data/app/views/solid_queue_monitor/search/index.html.erb +23 -0
  64. data/app/views/solid_queue_monitor/shared/_filters.html.erb +48 -0
  65. data/app/views/solid_queue_monitor/shared/_flash.html.erb +17 -0
  66. data/app/views/solid_queue_monitor/shared/_footer.html.erb +3 -0
  67. data/app/views/solid_queue_monitor/shared/_header.html.erb +81 -0
  68. data/app/views/solid_queue_monitor/shared/_jobs_table.html.erb +20 -0
  69. data/app/views/solid_queue_monitor/shared/_pagination.html.erb +25 -0
  70. data/app/views/solid_queue_monitor/workers/_row.html.erb +22 -0
  71. data/app/views/solid_queue_monitor/workers/index.html.erb +82 -0
  72. data/config/routes.rb +6 -1
  73. data/lib/solid_queue_monitor/engine.rb +2 -0
  74. data/lib/solid_queue_monitor/version.rb +1 -1
  75. metadata +57 -17
  76. data/app/presenters/solid_queue_monitor/base_presenter.rb +0 -211
  77. data/app/presenters/solid_queue_monitor/failed_jobs_presenter.rb +0 -312
  78. data/app/presenters/solid_queue_monitor/in_progress_jobs_presenter.rb +0 -84
  79. data/app/presenters/solid_queue_monitor/job_details_presenter.rb +0 -696
  80. data/app/presenters/solid_queue_monitor/jobs_presenter.rb +0 -144
  81. data/app/presenters/solid_queue_monitor/queue_details_presenter.rb +0 -195
  82. data/app/presenters/solid_queue_monitor/queues_presenter.rb +0 -89
  83. data/app/presenters/solid_queue_monitor/ready_jobs_presenter.rb +0 -81
  84. data/app/presenters/solid_queue_monitor/recurring_jobs_presenter.rb +0 -81
  85. data/app/presenters/solid_queue_monitor/scheduled_jobs_presenter.rb +0 -173
  86. data/app/presenters/solid_queue_monitor/search_results_presenter.rb +0 -190
  87. data/app/presenters/solid_queue_monitor/stats_presenter.rb +0 -36
  88. data/app/presenters/solid_queue_monitor/workers_presenter.rb +0 -320
  89. data/app/services/solid_queue_monitor/chart_presenter.rb +0 -239
  90. data/app/services/solid_queue_monitor/html_generator.rb +0 -401
@@ -1,401 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SolidQueueMonitor
4
- class HtmlGenerator
5
- include Rails.application.routes.url_helpers
6
- include SolidQueueMonitor::Engine.routes.url_helpers
7
-
8
- def initialize(title:, content:, message: nil, message_type: nil, search_query: nil)
9
- @title = title
10
- @content = content
11
- @message = message
12
- @message_type = message_type
13
- @search_query = search_query
14
- end
15
-
16
- def generate
17
- <<-HTML
18
- <!DOCTYPE html>
19
- <html>
20
- <head>
21
- <title>Solid Queue Monitor - #{@title}</title>
22
- #{generate_head}
23
- </head>
24
- <body class="solid_queue_monitor">
25
- #{generate_body}
26
- </body>
27
- </html>
28
- HTML
29
- end
30
-
31
- private
32
-
33
- def generate_head
34
- <<-HTML
35
- <meta charset="UTF-8">
36
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
37
- <style>
38
- #{SolidQueueMonitor::StylesheetGenerator.new.generate}
39
- </style>
40
- HTML
41
- end
42
-
43
- def generate_body
44
- <<-HTML
45
- #{render_message}
46
- <div class="container">
47
- #{generate_header}
48
- <div class="section">
49
- <h2>#{@title}</h2>
50
- #{@content}
51
- </div>
52
- #{generate_footer}
53
- </div>
54
- #{generate_auto_refresh_script}
55
- #{generate_chart_script}
56
- HTML
57
- end
58
-
59
- def render_message
60
- return '' unless @message
61
-
62
- class_name = @message_type == 'success' ? 'message-success' : 'message-error'
63
- <<-HTML
64
- <div id="flash-message" class="message #{class_name}">#{@message}</div>
65
- <script>
66
- // Automatically hide the flash message after 5 seconds
67
- document.addEventListener('DOMContentLoaded', function() {
68
- var flashMessage = document.getElementById('flash-message');
69
- if (flashMessage) {
70
- setTimeout(function() {
71
- flashMessage.style.opacity = '1';
72
- // Fade out animation
73
- var fadeEffect = setInterval(function() {
74
- if (!flashMessage.style.opacity) {
75
- flashMessage.style.opacity = 1;
76
- }
77
- if (flashMessage.style.opacity > 0) {
78
- flashMessage.style.opacity -= 0.1;
79
- } else {
80
- clearInterval(fadeEffect);
81
- flashMessage.style.display = 'none';
82
- }
83
- }, 50);
84
- }, 5000); // 5 seconds
85
- }
86
- });
87
- </script>
88
- HTML
89
- end
90
-
91
- def generate_header
92
- nav_items = [
93
- { path: root_path, label: 'Overview', match: 'Overview' },
94
- { path: ready_jobs_path, label: 'Ready Jobs', match: 'Ready Jobs' },
95
- { path: in_progress_jobs_path, label: 'In Progress Jobs', match: 'In Progress' },
96
- { path: scheduled_jobs_path, label: 'Scheduled Jobs', match: 'Scheduled Jobs' },
97
- { path: recurring_jobs_path, label: 'Recurring Jobs', match: 'Recurring Jobs' },
98
- { path: failed_jobs_path, label: 'Failed Jobs', match: 'Failed Jobs' },
99
- { path: queues_path, label: 'Queues', match: 'Queues' },
100
- { path: workers_path, label: 'Workers', match: 'Workers' }
101
- ]
102
-
103
- nav_links = nav_items.map do |item|
104
- active_class = @title&.include?(item[:match]) ? 'active' : ''
105
- "<a href=\"#{item[:path]}\" class=\"nav-link #{active_class}\">#{item[:label]}</a>"
106
- end.join("\n ")
107
-
108
- <<-HTML
109
- <header>
110
- <div class="header-top">
111
- <h1><a href="#{root_path}" class="header-title-link">Solid Queue Monitor</a></h1>
112
- #{generate_search_box}
113
- <div class="header-controls">
114
- #{generate_auto_refresh_controls}
115
- #{generate_theme_toggle}
116
- </div>
117
- </div>
118
- <nav class="navigation">
119
- #{nav_links}
120
- </nav>
121
- </header>
122
- HTML
123
- end
124
-
125
- def generate_footer
126
- <<-HTML
127
- <footer>
128
- <p>Powered by Solid Queue Monitor</p>
129
- </footer>
130
- HTML
131
- end
132
-
133
- def generate_search_box
134
- search_value = @search_query ? escape_html(@search_query) : ''
135
- <<-HTML
136
- <form method="get" action="#{search_path}" class="header-search-form">
137
- <input type="text" name="q" value="#{search_value}" placeholder="Search by class, queue, job ID, or error..." class="header-search-input">
138
- <button type="submit" class="header-search-button" title="Search">
139
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
140
- <circle cx="11" cy="11" r="8"></circle>
141
- <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
142
- </svg>
143
- </button>
144
- </form>
145
- HTML
146
- end
147
-
148
- def escape_html(text)
149
- text.to_s.gsub('&', '&amp;').gsub('<', '&lt;').gsub('>', '&gt;').gsub('"', '&quot;')
150
- end
151
-
152
- def generate_auto_refresh_controls
153
- return '' unless SolidQueueMonitor.auto_refresh_enabled
154
-
155
- interval = SolidQueueMonitor.auto_refresh_interval
156
- <<-HTML
157
- <div class="auto-refresh-container" title="Auto-refresh every #{interval}s" data-tooltip="Auto-refresh: Dashboard updates automatically every #{interval} seconds. Toggle to enable/disable.">
158
- <span class="auto-refresh-indicator" id="auto-refresh-indicator"></span>
159
- <span class="auto-refresh-countdown" id="auto-refresh-countdown">#{interval}s</span>
160
- <label class="auto-refresh-switch" title="Toggle auto-refresh">
161
- <input type="checkbox" id="auto-refresh-toggle" checked>
162
- <span class="switch-slider"></span>
163
- </label>
164
- <button class="refresh-now-btn" id="refresh-now-btn" title="Refresh now">
165
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
166
- <path d="M21 2v6h-6M3 12a9 9 0 0 1 15-6.7L21 8M3 22v-6h6M21 12a9 9 0 0 1-15 6.7L3 16"/>
167
- </svg>
168
- </button>
169
- </div>
170
- HTML
171
- end
172
-
173
- def generate_theme_toggle
174
- <<-HTML
175
- <button class="theme-toggle-btn" id="theme-toggle-btn" title="Toggle dark mode">
176
- <svg class="theme-icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
177
- <circle cx="12" cy="12" r="5"></circle>
178
- <line x1="12" y1="1" x2="12" y2="3"></line>
179
- <line x1="12" y1="21" x2="12" y2="23"></line>
180
- <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
181
- <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
182
- <line x1="1" y1="12" x2="3" y2="12"></line>
183
- <line x1="21" y1="12" x2="23" y2="12"></line>
184
- <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
185
- <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
186
- </svg>
187
- <svg class="theme-icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
188
- <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
189
- </svg>
190
- </button>
191
- HTML
192
- end
193
-
194
- def generate_auto_refresh_script
195
- return '' unless SolidQueueMonitor.auto_refresh_enabled
196
-
197
- "<script>#{auto_refresh_javascript}</script>"
198
- end
199
-
200
- def auto_refresh_javascript
201
- interval = SolidQueueMonitor.auto_refresh_interval
202
- <<-JS
203
- (function() {
204
- var REFRESH_INTERVAL = #{interval};
205
- var countdown = REFRESH_INTERVAL;
206
- var timerId = null;
207
- var isEnabled = localStorage.getItem('sqm_auto_refresh') !== 'false';
208
- #{auto_refresh_dom_elements}
209
- #{auto_refresh_functions}
210
- #{auto_refresh_event_listeners}
211
- #{auto_refresh_init}
212
- })();
213
- JS
214
- end
215
-
216
- def auto_refresh_dom_elements
217
- <<-JS
218
- var toggle = document.getElementById('auto-refresh-toggle');
219
- var indicator = document.getElementById('auto-refresh-indicator');
220
- var countdownEl = document.getElementById('auto-refresh-countdown');
221
- var refreshBtn = document.getElementById('refresh-now-btn');
222
- JS
223
- end
224
-
225
- def auto_refresh_functions
226
- <<-JS
227
- function updateUI() {
228
- if (toggle) toggle.checked = isEnabled;
229
- if (indicator) indicator.classList.toggle('active', isEnabled);
230
- if (countdownEl) {
231
- countdownEl.textContent = countdown + 's';
232
- countdownEl.style.opacity = isEnabled ? '1' : '0.4';
233
- }
234
- }
235
- function tick() {
236
- countdown--;
237
- if (countdown <= 0) { refresh(); } else { updateUI(); }
238
- }
239
- function startTimer() {
240
- stopTimer();
241
- countdown = REFRESH_INTERVAL;
242
- updateUI();
243
- timerId = setInterval(tick, 1000);
244
- }
245
- function stopTimer() {
246
- if (timerId) { clearInterval(timerId); timerId = null; }
247
- }
248
- function refresh() { window.location.reload(); }
249
- function setEnabled(enabled) {
250
- isEnabled = enabled;
251
- localStorage.setItem('sqm_auto_refresh', enabled ? 'true' : 'false');
252
- if (enabled) { startTimer(); } else { stopTimer(); countdown = REFRESH_INTERVAL; updateUI(); }
253
- }
254
- JS
255
- end
256
-
257
- def auto_refresh_event_listeners
258
- <<-JS
259
- if (toggle) { toggle.addEventListener('change', function() { setEnabled(this.checked); }); }
260
- if (refreshBtn) { refreshBtn.addEventListener('click', function() { refresh(); }); }
261
- JS
262
- end
263
-
264
- def auto_refresh_init
265
- <<-JS
266
- updateUI();
267
- if (isEnabled) { startTimer(); }
268
- JS
269
- end
270
-
271
- def generate_chart_script
272
- <<-HTML
273
- <script>
274
- #{theme_toggle_javascript}
275
- #{chart_tooltip_javascript}
276
- </script>
277
- HTML
278
- end
279
-
280
- def theme_toggle_javascript
281
- <<-JS
282
- (function() {
283
- var body = document.body;
284
- var themeBtn = document.getElementById('theme-toggle-btn');
285
- var storageKey = 'sqm_dark_theme';
286
-
287
- // Check for saved preference or system preference
288
- function getPreferredTheme() {
289
- var saved = localStorage.getItem(storageKey);
290
- if (saved !== null) {
291
- return saved === 'true';
292
- }
293
- // Check system preference
294
- return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
295
- }
296
-
297
- function setTheme(isDark) {
298
- if (isDark) {
299
- body.classList.add('dark-theme');
300
- } else {
301
- body.classList.remove('dark-theme');
302
- }
303
- localStorage.setItem(storageKey, isDark ? 'true' : 'false');
304
- }
305
-
306
- // Initialize theme
307
- setTheme(getPreferredTheme());
308
-
309
- // Toggle on button click
310
- if (themeBtn) {
311
- themeBtn.addEventListener('click', function() {
312
- var isDark = body.classList.contains('dark-theme');
313
- setTheme(!isDark);
314
- });
315
- }
316
-
317
- // Listen for system preference changes
318
- if (window.matchMedia) {
319
- window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) {
320
- // Only auto-switch if user hasn't manually set a preference
321
- if (localStorage.getItem(storageKey) === null) {
322
- setTheme(e.matches);
323
- }
324
- });
325
- }
326
- })();
327
- JS
328
- end
329
-
330
- def chart_tooltip_javascript
331
- <<-JS
332
- (function() {
333
- // Chart collapse/expand functionality
334
- var chartSection = document.getElementById('chart-section');
335
- var toggleBtn = document.getElementById('chart-toggle-btn');
336
-
337
- if (chartSection && toggleBtn) {
338
- var isCollapsed = localStorage.getItem('sqm_chart_collapsed') === 'true';
339
-
340
- if (isCollapsed) {
341
- chartSection.classList.add('collapsed');
342
- }
343
-
344
- toggleBtn.addEventListener('click', function() {
345
- chartSection.classList.toggle('collapsed');
346
- var collapsed = chartSection.classList.contains('collapsed');
347
- localStorage.setItem('sqm_chart_collapsed', collapsed ? 'true' : 'false');
348
- });
349
- }
350
-
351
- // Chart tooltip functionality
352
- var tooltip = document.getElementById('chart-tooltip');
353
- if (!tooltip) return;
354
-
355
- var dataPoints = document.querySelectorAll('.data-point');
356
- var seriesNames = { created: 'Created', completed: 'Completed', failed: 'Failed' };
357
-
358
- dataPoints.forEach(function(point) {
359
- point.addEventListener('mouseenter', function(e) {
360
- var series = this.getAttribute('data-series');
361
- var label = this.getAttribute('data-label');
362
- var value = this.getAttribute('data-value');
363
-
364
- tooltip.querySelector('.tooltip-label').textContent = label;
365
- tooltip.querySelector('.tooltip-value').textContent = seriesNames[series] + ': ' + value;
366
- tooltip.style.display = 'block';
367
- positionTooltip(e);
368
- });
369
-
370
- point.addEventListener('mousemove', function(e) {
371
- positionTooltip(e);
372
- });
373
-
374
- point.addEventListener('mouseleave', function() {
375
- tooltip.style.display = 'none';
376
- });
377
- });
378
-
379
- function positionTooltip(e) {
380
- var x = e.clientX + 10;
381
- var y = e.clientY - 30;
382
-
383
- if (x + tooltip.offsetWidth > window.innerWidth) {
384
- x = e.clientX - tooltip.offsetWidth - 10;
385
- }
386
- if (y < 0) {
387
- y = e.clientY + 10;
388
- }
389
-
390
- tooltip.style.left = x + 'px';
391
- tooltip.style.top = y + 'px';
392
- }
393
- })();
394
- JS
395
- end
396
-
397
- def default_url_options
398
- { only_path: true }
399
- end
400
- end
401
- end