solid_queue_web 0.6.0 → 0.7.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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +20 -2
  3. data/Rakefile +2 -2
  4. data/app/assets/stylesheets/solid_queue_web/_01_base.css +41 -0
  5. data/app/assets/stylesheets/solid_queue_web/_02_layout.css +105 -0
  6. data/app/assets/stylesheets/solid_queue_web/_03_stats.css +49 -0
  7. data/app/assets/stylesheets/solid_queue_web/_04_table.css +52 -0
  8. data/app/assets/stylesheets/solid_queue_web/_05_badges.css +27 -0
  9. data/app/assets/stylesheets/solid_queue_web/_06_buttons.css +38 -0
  10. data/app/assets/stylesheets/solid_queue_web/_07_forms.css +103 -0
  11. data/app/assets/stylesheets/solid_queue_web/_08_detail.css +84 -0
  12. data/app/assets/stylesheets/solid_queue_web/_09_pagination.css +27 -0
  13. data/app/assets/stylesheets/solid_queue_web/_10_responsive.css +73 -0
  14. data/app/assets/stylesheets/solid_queue_web/_11_throughput.css +68 -0
  15. data/app/assets/stylesheets/solid_queue_web/application.css +1 -617
  16. data/app/controllers/solid_queue_web/dashboard_controller.rb +12 -0
  17. data/app/controllers/solid_queue_web/failed_jobs_controller.rb +1 -1
  18. data/app/controllers/solid_queue_web/history_controller.rb +16 -0
  19. data/app/controllers/solid_queue_web/jobs_controller.rb +1 -1
  20. data/app/controllers/solid_queue_web/queues/jobs_controller.rb +1 -1
  21. data/app/controllers/solid_queue_web/queues_controller.rb +15 -0
  22. data/app/helpers/solid_queue_web/application_helper.rb +15 -1
  23. data/app/javascript/solid_queue_web/refresh_controller.js +3 -2
  24. data/app/views/layouts/solid_queue_web/application.html.erb +1 -0
  25. data/app/views/solid_queue_web/dashboard/index.html.erb +38 -0
  26. data/app/views/solid_queue_web/failed_jobs/index.html.erb +0 -1
  27. data/app/views/solid_queue_web/history/index.html.erb +67 -0
  28. data/app/views/solid_queue_web/jobs/index.html.erb +0 -1
  29. data/app/views/solid_queue_web/queues/index.html.erb +15 -1
  30. data/config/routes.rb +9 -8
  31. data/lib/solid_queue_web/version.rb +1 -1
  32. metadata +14 -1
@@ -0,0 +1,68 @@
1
+ .sqd-throughput__summary {
2
+ display: flex;
3
+ gap: 1.25rem;
4
+ font-size: 13px;
5
+ color: var(--muted);
6
+ }
7
+
8
+ .sqd-throughput__summary strong {
9
+ color: var(--text);
10
+ font-weight: 600;
11
+ }
12
+
13
+ .sqd-sparkline {
14
+ display: flex;
15
+ align-items: flex-end;
16
+ gap: 3px;
17
+ height: 80px;
18
+ padding: 0.75rem 1.25rem 0;
19
+ }
20
+
21
+ .sqd-sparkline__col {
22
+ flex: 1;
23
+ display: flex;
24
+ flex-direction: column;
25
+ align-items: center;
26
+ gap: 3px;
27
+ height: 100%;
28
+ }
29
+
30
+ .sqd-sparkline__bar-wrap {
31
+ flex: 1;
32
+ width: 100%;
33
+ display: flex;
34
+ align-items: flex-end;
35
+ }
36
+
37
+ .sqd-sparkline__bar {
38
+ width: 100%;
39
+ background: var(--primary);
40
+ border-radius: 2px 2px 0 0;
41
+ min-height: 2px;
42
+ opacity: 0.75;
43
+ transition: opacity 0.15s;
44
+ }
45
+
46
+ .sqd-sparkline__bar:hover {
47
+ opacity: 1;
48
+ }
49
+
50
+ .sqd-sparkline__tick {
51
+ font-size: 10px;
52
+ color: var(--muted);
53
+ white-space: nowrap;
54
+ min-height: 14px;
55
+ line-height: 14px;
56
+ }
57
+
58
+ .sqd-sparkline__empty {
59
+ flex: 1;
60
+ display: flex;
61
+ align-items: center;
62
+ justify-content: center;
63
+ color: var(--muted);
64
+ font-size: 13px;
65
+ padding: 1rem 1.25rem;
66
+ }
67
+
68
+ .sqd-stat--done .sqd-stat__value { color: var(--success); }
@@ -1,619 +1,3 @@
1
1
  /*
2
2
  *= require_self
3
- */
4
-
5
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
6
-
7
- .sqd-sr-only {
8
- position: absolute;
9
- width: 1px;
10
- height: 1px;
11
- padding: 0;
12
- margin: -1px;
13
- overflow: hidden;
14
- clip: rect(0, 0, 0, 0);
15
- white-space: nowrap;
16
- border: 0;
17
- }
18
-
19
- :focus-visible {
20
- outline: 2px solid var(--primary);
21
- outline-offset: 2px;
22
- border-radius: 2px;
23
- }
24
-
25
- :root {
26
- --bg: #f8f9fa;
27
- --surface: #ffffff;
28
- --border: #dee2e6;
29
- --text: #212529;
30
- --muted: #6c757d;
31
- --primary: #0d6efd;
32
- --danger: #dc3545;
33
- --warning: #fd7e14;
34
- --success: #198754;
35
- --info: #0dcaf0;
36
- --purple: #6f42c1;
37
- }
38
-
39
- body {
40
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
41
- font-size: 14px;
42
- background: var(--bg);
43
- color: var(--text);
44
- line-height: 1.5;
45
- }
46
-
47
- /* Layout */
48
- .sqd-header {
49
- background: var(--surface);
50
- border-bottom: 1px solid var(--border);
51
- }
52
-
53
- .sqd-header__inner {
54
- max-width: 1200px;
55
- margin: 0 auto;
56
- padding: 0 1.5rem;
57
- display: flex;
58
- align-items: center;
59
- gap: 2rem;
60
- height: 56px;
61
- }
62
-
63
- .sqd-header__title {
64
- font-size: 16px;
65
- font-weight: 600;
66
- color: var(--text);
67
- text-decoration: none;
68
- }
69
-
70
- .sqd-nav {
71
- display: flex;
72
- gap: 0.25rem;
73
- list-style: none;
74
- }
75
-
76
- .sqd-nav a {
77
- display: block;
78
- padding: 0.35rem 0.75rem;
79
- border-radius: 6px;
80
- color: var(--muted);
81
- text-decoration: none;
82
- font-size: 13px;
83
- font-weight: 500;
84
- transition: background 0.1s, color 0.1s;
85
- }
86
-
87
- .sqd-nav a:hover,
88
- .sqd-nav a.active {
89
- background: var(--bg);
90
- color: var(--text);
91
- }
92
-
93
- .sqd-nav-toggle {
94
- display: none;
95
- flex-direction: column;
96
- justify-content: center;
97
- gap: 5px;
98
- width: 36px;
99
- height: 36px;
100
- padding: 6px;
101
- margin-left: auto;
102
- background: none;
103
- border: 1px solid var(--border);
104
- border-radius: 5px;
105
- cursor: pointer;
106
- }
107
-
108
- .sqd-nav-toggle span {
109
- display: block;
110
- height: 2px;
111
- background: var(--text);
112
- border-radius: 1px;
113
- }
114
-
115
- .sqd-main {
116
- max-width: 1200px;
117
- margin: 0 auto;
118
- padding: 2rem 1.5rem;
119
- }
120
-
121
- .sqd-page-title {
122
- font-size: 20px;
123
- font-weight: 600;
124
- margin-bottom: 0;
125
- }
126
-
127
- .sqd-page-header {
128
- display: flex;
129
- align-items: center;
130
- justify-content: space-between;
131
- margin-bottom: 1.5rem;
132
- }
133
-
134
- .sqd-actions {
135
- display: flex;
136
- gap: 0.5rem;
137
- }
138
-
139
- /* Flash notices */
140
- .sqd-flash {
141
- padding: 0.75rem 1rem;
142
- border-radius: 6px;
143
- margin-bottom: 1rem;
144
- font-size: 13px;
145
- }
146
- .sqd-flash--notice { background: #d1e7dd; color: #0f5132; border: 1px solid #badbcc; }
147
- .sqd-flash--alert { background: #f8d7da; color: #842029; border: 1px solid #f5c2c7; }
148
-
149
- /* Stat cards */
150
- .sqd-stats {
151
- display: grid;
152
- grid-template-columns: repeat(auto-fill, minmax(128px, 1fr));
153
- gap: 1rem;
154
- margin-bottom: 2rem;
155
- }
156
-
157
- .sqd-stat {
158
- background: var(--surface);
159
- border: 1px solid var(--border);
160
- border-radius: 8px;
161
- padding: 1.25rem 1rem;
162
- text-align: center;
163
- }
164
-
165
- .sqd-stat__value {
166
- font-size: 28px;
167
- font-weight: 700;
168
- line-height: 1;
169
- margin-bottom: 0.25rem;
170
- }
171
-
172
- .sqd-stat__label {
173
- font-size: 12px;
174
- color: var(--muted);
175
- text-transform: uppercase;
176
- letter-spacing: 0.05em;
177
- }
178
-
179
- .sqd-stat--ready .sqd-stat__value { color: var(--success); }
180
- .sqd-stat--scheduled .sqd-stat__value { color: var(--info); }
181
- .sqd-stat--claimed .sqd-stat__value { color: var(--primary); }
182
- .sqd-stat--failed .sqd-stat__value { color: var(--danger); }
183
- .sqd-stat--blocked .sqd-stat__value { color: var(--warning); }
184
- .sqd-stat--queues .sqd-stat__value { color: var(--purple); }
185
- .sqd-stat--processes .sqd-stat__value { color: var(--muted); }
186
- .sqd-stat--recurring .sqd-stat__value { color: var(--info); }
187
-
188
- .sqd-stat--link {
189
- display: block;
190
- text-decoration: none;
191
- color: inherit;
192
- transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s;
193
- }
194
- .sqd-stat--link:hover {
195
- border-color: var(--primary);
196
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
197
- transform: translateY(-2px);
198
- }
199
-
200
- /* Tables */
201
- .sqd-card {
202
- background: var(--surface);
203
- border: 1px solid var(--border);
204
- border-radius: 8px;
205
- overflow: hidden;
206
- }
207
-
208
- .sqd-card__header {
209
- padding: 0.875rem 1rem;
210
- border-bottom: 1px solid var(--border);
211
- display: flex;
212
- align-items: center;
213
- justify-content: space-between;
214
- gap: 1rem;
215
- }
216
-
217
- .sqd-card__title {
218
- font-size: 14px;
219
- font-weight: 600;
220
- }
221
-
222
- table {
223
- width: 100%;
224
- border-collapse: collapse;
225
- }
226
-
227
- th {
228
- padding: 0.625rem 1rem;
229
- text-align: left;
230
- font-size: 12px;
231
- font-weight: 600;
232
- color: var(--muted);
233
- text-transform: uppercase;
234
- letter-spacing: 0.05em;
235
- border-bottom: 1px solid var(--border);
236
- white-space: nowrap;
237
- }
238
-
239
- td {
240
- padding: 0.75rem 1rem;
241
- border-bottom: 1px solid var(--border);
242
- vertical-align: middle;
243
- }
244
-
245
- tr:last-child td { border-bottom: none; }
246
- tbody tr:hover { background: var(--bg); }
247
-
248
- .sqd-empty {
249
- text-align: center;
250
- padding: 3rem 1rem;
251
- color: var(--muted);
252
- }
253
-
254
- /* Badges */
255
- .sqd-badge {
256
- display: inline-block;
257
- padding: 0.2em 0.55em;
258
- border-radius: 4px;
259
- font-size: 11px;
260
- font-weight: 600;
261
- line-height: 1;
262
- text-transform: uppercase;
263
- letter-spacing: 0.04em;
264
- }
265
-
266
- .sqd-badge--ready { background: #d1e7dd; color: #0f5132; }
267
- .sqd-badge--scheduled { background: #cff4fc; color: #055160; }
268
- .sqd-badge--claimed { background: #cfe2ff; color: #084298; }
269
- .sqd-badge--failed { background: #f8d7da; color: #842029; }
270
- .sqd-badge--blocked { background: #fff3cd; color: #664d03; }
271
- .sqd-badge--static { background: #d1e7dd; color: #0f5132; }
272
- .sqd-badge--dynamic { background: #e0d7f5; color: #4a2c8a; }
273
- .sqd-badge--paused { background: #e2e3e5; color: #41464b; }
274
- .sqd-badge--running { background: #d1e7dd; color: #0f5132; }
275
- .sqd-badge--supervisor { background: #e0d7f5; color: #4a2c8a; }
276
- .sqd-badge--worker { background: #d1e7dd; color: #0f5132; }
277
- .sqd-badge--dispatcher { background: #cff4fc; color: #055160; }
278
-
279
- .sqd-process-meta { font-size: 12px; color: var(--muted); }
280
- .sqd-process-meta span + span::before { content: " · "; }
281
- .sqd-muted-text { color: var(--muted); font-size: 13px; }
282
-
283
- /* Buttons */
284
- .sqd-btn {
285
- display: inline-flex;
286
- align-items: center;
287
- padding: 0.35rem 0.75rem;
288
- border-radius: 5px;
289
- font-size: 12px;
290
- font-weight: 500;
291
- text-decoration: none;
292
- border: 1px solid transparent;
293
- cursor: pointer;
294
- transition: opacity 0.15s;
295
- }
296
- .sqd-btn:hover { opacity: 0.85; }
297
- .sqd-btn--primary { background: var(--primary); color: #fff; border-color: var(--primary); }
298
- .sqd-btn--danger { background: var(--danger); color: #fff; border-color: var(--danger); }
299
- .sqd-btn--muted { background: var(--surface); color: var(--text); border-color: var(--border); }
300
- .sqd-btn--sm { padding: 0.2rem 0.55rem; font-size: 11px; }
301
-
302
- .sqd-row-actions { white-space: nowrap; text-align: right; width: 1%; }
303
- .sqd-row-actions form { display: inline; margin-left: 0.25rem; }
304
-
305
- /* Selection bar */
306
- .sqd-selection-bar {
307
- display: flex;
308
- align-items: center;
309
- gap: 0.75rem;
310
- padding: 0.5rem 1rem;
311
- background: var(--bg);
312
- border-bottom: 1px solid var(--border);
313
- font-size: 13px;
314
- }
315
-
316
- table th input[type="checkbox"],
317
- table td input[type="checkbox"] {
318
- width: 15px;
319
- height: 15px;
320
- cursor: pointer;
321
- accent-color: var(--primary);
322
- }
323
-
324
- /* Search */
325
- .sqd-search {
326
- display: flex;
327
- gap: 0.5rem;
328
- align-items: center;
329
- margin-bottom: 1rem;
330
- }
331
-
332
- .sqd-search__input {
333
- width: 280px;
334
- padding: 0.35rem 0.75rem;
335
- border: 1px solid var(--border);
336
- border-radius: 5px;
337
- font-size: 13px;
338
- background: var(--surface);
339
- color: var(--text);
340
- line-height: 1.5;
341
- }
342
-
343
- .sqd-search__input:focus {
344
- outline: 2px solid var(--primary);
345
- outline-offset: -1px;
346
- border-color: var(--primary);
347
- }
348
-
349
- @media (max-width: 640px) {
350
- .sqd-search { flex-wrap: wrap; }
351
- .sqd-search__input { width: 100%; }
352
- }
353
-
354
- .sqd-search--global { margin-bottom: 2rem; }
355
-
356
- .sqd-search__input--lg {
357
- width: 420px;
358
- font-size: 15px;
359
- padding: 0.5rem 1rem;
360
- }
361
-
362
- @media (max-width: 640px) {
363
- .sqd-search__input--lg { width: 100%; }
364
- }
365
-
366
- .sqd-search-group {
367
- margin-bottom: 2rem;
368
- }
369
-
370
- .sqd-search-group__header {
371
- display: flex;
372
- align-items: center;
373
- gap: 0.75rem;
374
- margin-bottom: 0.75rem;
375
- }
376
-
377
- /* Filters */
378
- .sqd-filters {
379
- display: flex;
380
- gap: 0.5rem;
381
- flex-wrap: wrap;
382
- margin-bottom: 1rem;
383
- }
384
-
385
- .sqd-filters a {
386
- padding: 0.35rem 0.875rem;
387
- border-radius: 20px;
388
- font-size: 12px;
389
- font-weight: 500;
390
- text-decoration: none;
391
- border: 1px solid var(--border);
392
- color: var(--muted);
393
- background: var(--surface);
394
- transition: all 0.1s;
395
- }
396
-
397
- .sqd-filters a:hover,
398
- .sqd-filters a.active {
399
- background: var(--primary);
400
- border-color: var(--primary);
401
- color: #fff;
402
- }
403
-
404
- /* Period filter */
405
- .sqd-period-filter {
406
- display: flex;
407
- align-items: center;
408
- gap: 0.25rem;
409
- margin-left: auto;
410
- }
411
-
412
- .sqd-period-filter a {
413
- padding: 0.2rem 0.55rem;
414
- border-radius: 4px;
415
- font-size: 11px;
416
- font-weight: 500;
417
- text-decoration: none;
418
- border: 1px solid var(--border);
419
- color: var(--muted);
420
- background: var(--surface);
421
- transition: all 0.1s;
422
- }
423
-
424
- .sqd-period-filter a:hover,
425
- .sqd-period-filter a.active {
426
- background: var(--muted);
427
- border-color: var(--muted);
428
- color: #fff;
429
- }
430
-
431
- /* Code / monospace */
432
- .sqd-mono {
433
- font-family: "SFMono-Regular", Menlo, Monaco, Consolas, monospace;
434
- font-size: 12px;
435
- }
436
-
437
- .sqd-error-msg {
438
- color: var(--danger);
439
- font-size: 12px;
440
- }
441
-
442
- .sqd-truncate {
443
- max-width: 320px;
444
- overflow: hidden;
445
- text-overflow: ellipsis;
446
- white-space: nowrap;
447
- }
448
-
449
- /* Pagination (pagy v43 series-nav) */
450
- nav.pagy {
451
- display: flex;
452
- justify-content: center;
453
- gap: 0.25rem;
454
- padding: 1rem;
455
- list-style: none;
456
- }
457
-
458
- nav.pagy a {
459
- display: inline-flex;
460
- align-items: center;
461
- justify-content: center;
462
- min-width: 32px;
463
- height: 32px;
464
- padding: 0 0.5rem;
465
- border-radius: 5px;
466
- font-size: 13px;
467
- text-decoration: none;
468
- border: 1px solid var(--border);
469
- color: var(--text);
470
- background: var(--surface);
471
- }
472
-
473
- nav.pagy a:hover:not([aria-disabled="true"]) { background: var(--bg); }
474
- nav.pagy a[aria-current="page"] { background: var(--primary); color: #fff; border-color: var(--primary); }
475
- nav.pagy a[role="separator"],
476
- nav.pagy a[aria-disabled="true"] { color: var(--muted); cursor: default; }
477
-
478
- /* Job detail page */
479
- .sqd-breadcrumb {
480
- font-size: 12px;
481
- color: var(--muted);
482
- margin-bottom: 0.25rem;
483
- }
484
-
485
- .sqd-breadcrumb a { color: var(--muted); text-decoration: none; }
486
- .sqd-breadcrumb a:hover { color: var(--text); }
487
-
488
- .sqd-detail-grid {
489
- display: grid;
490
- grid-template-columns: 1fr 1fr;
491
- gap: 1.5rem;
492
- }
493
-
494
- .sqd-grid-2 {
495
- display: grid;
496
- grid-template-columns: 1fr 1fr;
497
- gap: 1rem;
498
- }
499
-
500
- @media (max-width: 768px) {
501
- .sqd-detail-grid { grid-template-columns: 1fr; }
502
- .sqd-grid-2 { grid-template-columns: 1fr; }
503
- }
504
-
505
- @media (max-width: 640px) {
506
- .sqd-main {
507
- padding: 1.5rem 1rem;
508
- }
509
-
510
- .sqd-page-header {
511
- flex-direction: column;
512
- align-items: flex-start;
513
- gap: 0.75rem;
514
- }
515
-
516
- .sqd-card {
517
- overflow-x: auto;
518
- }
519
-
520
- .sqd-card__header {
521
- flex-wrap: wrap;
522
- }
523
-
524
- .sqd-stats {
525
- grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
526
- }
527
-
528
- .sqd-truncate {
529
- max-width: 160px;
530
- }
531
- }
532
-
533
- @media (max-width: 576px) {
534
- .sqd-header {
535
- position: relative;
536
- }
537
-
538
- .sqd-header__inner {
539
- padding: 0 1rem;
540
- }
541
-
542
- .sqd-nav-toggle {
543
- display: flex;
544
- }
545
-
546
- .sqd-nav-wrapper {
547
- display: none;
548
- position: absolute;
549
- top: 100%;
550
- left: 0;
551
- right: 0;
552
- background: var(--surface);
553
- border-bottom: 1px solid var(--border);
554
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
555
- z-index: 50;
556
- padding: 0.5rem;
557
- }
558
-
559
- .sqd-nav-wrapper.sqd-nav--open {
560
- display: block;
561
- }
562
-
563
- .sqd-nav {
564
- flex-direction: column;
565
- gap: 0.25rem;
566
- }
567
-
568
- .sqd-nav a {
569
- padding: 0.5rem 0.75rem;
570
- font-size: 14px;
571
- }
572
- }
573
-
574
- .sqd-detail-section { padding: 1.25rem; }
575
-
576
- .sqd-section-title {
577
- font-size: 13px;
578
- font-weight: 600;
579
- text-transform: uppercase;
580
- letter-spacing: 0.05em;
581
- color: var(--muted);
582
- margin-bottom: 1rem;
583
- }
584
-
585
- .sqd-section-title--danger { color: var(--danger); }
586
-
587
- .sqd-dl {
588
- display: grid;
589
- grid-template-columns: auto 1fr;
590
- gap: 0.5rem 1.5rem;
591
- font-size: 13px;
592
- }
593
-
594
- .sqd-dl dt { color: var(--muted); white-space: nowrap; }
595
- .sqd-dl dd { word-break: break-all; }
596
-
597
- .sqd-pre {
598
- font-family: monospace;
599
- font-size: 12px;
600
- background: var(--bg);
601
- border: 1px solid var(--border);
602
- border-radius: 5px;
603
- padding: 0.75rem;
604
- overflow-x: auto;
605
- white-space: pre-wrap;
606
- word-break: break-word;
607
- max-height: 400px;
608
- overflow-y: auto;
609
- }
610
-
611
- .sqd-pre--muted { color: var(--muted); }
612
-
613
- .sqd-error-header {
614
- font-size: 13px;
615
- padding: 0.5rem 0.75rem;
616
- background: #f8d7da;
617
- color: #842029;
618
- border-radius: 5px;
619
- }
3
+ */
@@ -11,6 +11,18 @@ module SolidQueueWeb
11
11
  processes: SolidQueue::Process.count,
12
12
  recurring: SolidQueue::RecurringTask.count
13
13
  }
14
+
15
+ now = Time.current
16
+ finished_times = SolidQueue::Job.where(finished_at: 24.hours.ago..now).pluck(:finished_at)
17
+ @throughput = {
18
+ completed_1h: finished_times.count { |t| t >= 1.hour.ago },
19
+ completed_24h: finished_times.size
20
+ }
21
+ @sparkline = 12.times.map do |i|
22
+ from = (12 - i).hours.ago
23
+ to = i == 11 ? now : (11 - i).hours.ago
24
+ finished_times.count { |t| t >= from && t < to }
25
+ end
14
26
  end
15
27
  end
16
28
  end
@@ -1,6 +1,6 @@
1
1
  module SolidQueueWeb
2
2
  class FailedJobsController < ApplicationController
3
- before_action :set_filter_params, only: [ :index, :retry_all, :discard_all ]
3
+ before_action :set_filter_params, only: [:index, :retry_all, :discard_all]
4
4
 
5
5
  def index
6
6
  @pagy, @failed_jobs = pagy(filtered_scope.order(created_at: :desc))
@@ -0,0 +1,16 @@
1
+ module SolidQueueWeb
2
+ class HistoryController < ApplicationController
3
+ def index
4
+ @queue = params[:queue].presence
5
+ @search = params[:q].presence
6
+ @period = params[:period].presence_in(PERIOD_DURATIONS.keys)
7
+
8
+ scope = SolidQueue::Job.where.not(finished_at: nil)
9
+ scope = scope.where(queue_name: @queue) if @queue.present?
10
+ scope = scope.where("class_name LIKE ?", "%#{@search}%") if @search.present?
11
+ scope = scope.where("finished_at >= ?", PERIOD_DURATIONS[@period].ago) if @period.present?
12
+
13
+ @pagy, @jobs = pagy(scope.order(finished_at: :desc))
14
+ end
15
+ end
16
+ end