solid_queue_web 1.5.0 → 1.6.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.
- checksums.yaml +4 -4
- data/README.md +179 -5
- data/app/assets/stylesheets/solid_queue_web/_04_table.css +28 -0
- data/app/controllers/solid_queue_web/application_controller.rb +10 -0
- data/app/controllers/solid_queue_web/blocked_jobs_controller.rb +2 -2
- data/app/controllers/solid_queue_web/failed_jobs/arguments_controller.rb +3 -3
- data/app/controllers/solid_queue_web/failed_jobs/selections_controller.rb +4 -4
- data/app/controllers/solid_queue_web/failed_jobs_controller.rb +2 -2
- data/app/controllers/solid_queue_web/jobs/selections_controller.rb +3 -3
- data/app/controllers/solid_queue_web/jobs_controller.rb +4 -3
- data/app/controllers/solid_queue_web/queues/jobs_controller.rb +5 -5
- data/app/controllers/solid_queue_web/queues/pauses_controller.rb +4 -4
- data/app/controllers/solid_queue_web/recurring_tasks/runs_controller.rb +4 -4
- data/app/controllers/solid_queue_web/retry_failed_jobs_controller.rb +4 -5
- data/app/controllers/solid_queue_web/scheduled_jobs_controller.rb +5 -5
- data/app/views/layouts/solid_queue_web/application.html.erb +20 -17
- data/app/views/solid_queue_web/audit/index.html.erb +15 -15
- data/app/views/solid_queue_web/dashboard/index.html.erb +67 -46
- data/app/views/solid_queue_web/failed_jobs/errors/index.html.erb +7 -7
- data/app/views/solid_queue_web/failed_jobs/index.html.erb +31 -31
- data/app/views/solid_queue_web/history/index.html.erb +14 -14
- data/app/views/solid_queue_web/jobs/index.html.erb +42 -42
- data/app/views/solid_queue_web/jobs/show.html.erb +20 -20
- data/app/views/solid_queue_web/performance/index.html.erb +16 -14
- data/app/views/solid_queue_web/processes/index.html.erb +16 -16
- data/app/views/solid_queue_web/queues/index.html.erb +16 -16
- data/app/views/solid_queue_web/queues/jobs/index.html.erb +21 -21
- data/app/views/solid_queue_web/recurring_tasks/index.html.erb +15 -15
- data/app/views/solid_queue_web/search/index.html.erb +13 -13
- data/config/locales/en.yml +330 -0
- data/lib/solid_queue_web/engine.rb +1 -0
- data/lib/solid_queue_web/version.rb +1 -1
- data/lib/solid_queue_web.rb +13 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cb357c2f74fd24c1a394d54fe2aa53c0fd62a965c3b08e4e359b086243f66e51
|
|
4
|
+
data.tar.gz: c00154198de848c521c53d02124dcc194fdd54c31e67860a6f70554ad5e055da
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cb41d51eafd160f58fd7e54f8b0be6c95e9f488d43d8c6c32720c9a21e108643df38d6957d3aed50cc4f675d456425a3907a7aee37ab39d423a3a9705b050a5e
|
|
7
|
+
data.tar.gz: b272217dd76d4fd7f383815b3604acaae7cfdf9ed9bd8c2132539b3c65036398bd9a99431b73b1804fe84d0b47bbe7f0abcc6ad84fb4a6ff770e2630e97c7989
|
data/README.md
CHANGED
|
@@ -12,10 +12,46 @@ A monitoring and management dashboard for [Solid Queue](https://github.com/rails
|
|
|
12
12
|
|
|
13
13
|

|
|
14
14
|
|
|
15
|
+
## Table of Contents
|
|
16
|
+
|
|
17
|
+
- [The problem](#the-problem)
|
|
18
|
+
- [Why SolidQueueWeb?](#why-solidqueueweb)
|
|
19
|
+
- [Real-world use case](#real-world-use-case)
|
|
20
|
+
- [Features](#features)
|
|
21
|
+
- [Compatibility](#compatibility)
|
|
22
|
+
- [Installation](#installation)
|
|
23
|
+
- [Mounting the engine](#mounting-the-engine)
|
|
24
|
+
- [Configuration](#configuration)
|
|
25
|
+
- [Webhook alerts](#webhook-alerts)
|
|
26
|
+
- [Failure threshold alerts](#failure-threshold-alerts)
|
|
27
|
+
- [Queue depth alerts](#queue-depth-alerts)
|
|
28
|
+
- [Slow job alerts](#slow-job-alerts)
|
|
29
|
+
- [Stale process alerts](#stale-process-alerts)
|
|
30
|
+
- [Admin audit log](#admin-audit-log)
|
|
31
|
+
- [Setup](#setup)
|
|
32
|
+
- [Identity](#identity)
|
|
33
|
+
- [Audited actions](#audited-actions)
|
|
34
|
+
- [Metrics endpoint](#metrics-endpoint)
|
|
35
|
+
- [Read replica support](#read-replica-support)
|
|
36
|
+
- [i18n](#i18n)
|
|
37
|
+
- [Adding a custom locale](#adding-a-custom-locale)
|
|
38
|
+
- [Extensibility](#extensibility)
|
|
39
|
+
- [Custom dashboard cards](#custom-dashboard-cards)
|
|
40
|
+
- [Custom nav links](#custom-nav-links)
|
|
41
|
+
- [Roadmap](#roadmap)
|
|
42
|
+
- [Contributing](#contributing)
|
|
43
|
+
- [License](#license)
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
15
47
|
## The problem
|
|
16
48
|
|
|
17
49
|
Solid Queue ships without a web interface. When jobs fail, queues back up, or workers go silent in production, the only options are `rails console` or raw SQL queries. SolidQueueWeb gives your team a real-time dashboard to inspect, retry, and discard jobs without leaving the browser — and without standing up any additional infrastructure.
|
|
18
50
|
|
|
51
|
+
[↑ Back to top](#table-of-contents)
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
19
55
|
## Why SolidQueueWeb?
|
|
20
56
|
|
|
21
57
|
- Purpose-built for Solid Queue — uses its native models directly, no adapters
|
|
@@ -24,6 +60,10 @@ Solid Queue ships without a web interface. When jobs fail, queues back up, or wo
|
|
|
24
60
|
- Built for Rails 8 — Turbo Frames for in-place updates, Stimulus for dynamic search and auto-refresh, Pagy for efficient pagination
|
|
25
61
|
- Inspired by Sidekiq Web UI and the GoodJob dashboard, adapted for the Solid Queue ecosystem
|
|
26
62
|
|
|
63
|
+
[↑ Back to top](#table-of-contents)
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
27
67
|
## Real-world use case
|
|
28
68
|
|
|
29
69
|
A Rails app processes order confirmations, email notifications, and report generation through Solid Queue. An operations team needs to:
|
|
@@ -35,6 +75,10 @@ A Rails app processes order confirmations, email notifications, and report gener
|
|
|
35
75
|
|
|
36
76
|
SolidQueueWeb surfaces all of this in a browser UI available at any route you choose.
|
|
37
77
|
|
|
78
|
+
[↑ Back to top](#table-of-contents)
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
38
82
|
## Features
|
|
39
83
|
|
|
40
84
|
- **Dashboard** — stat cards showing counts for ready, scheduled, running, blocked, and failed jobs, plus queues, recurring tasks, and processes; "Done (1h)" and "Done (24h)" throughput cards; a "Throughput — Last 12 Hours" bar chart (blue) and a "Queue Depth — Last 12 Hours" bar chart (purple) showing hourly snapshots of active job count; pure CSS, no charting library; auto-refreshes every 5 seconds
|
|
@@ -60,6 +104,13 @@ SolidQueueWeb surfaces all of this in a browser UI available at any route you ch
|
|
|
60
104
|
- **Failed job trend chart** — a "Failures — Last 12 Hours" bar chart on the dashboard shows failures per hour over the last 12 hours; bars are red, making failure spikes visible before clicking into the failed jobs list
|
|
61
105
|
- **Error frequency report** — `GET /jobs/failed_jobs/errors` groups all failed jobs by error class and message prefix, shows a count per group, and surfaces a sample backtrace in an expandable row; sorted by count descending so the most common errors appear first; accessible via the "Error Summary" button on the Failed Jobs page
|
|
62
106
|
- **Metrics / health endpoint** — `GET /jobs/metrics.json` returns a machine-readable JSON document with job counts, throughput, per-queue depth and pause state, and process health summary; suitable for Prometheus scraping, uptime monitors, or external dashboards; `slow_jobs` count included when `slow_job_threshold` is configured
|
|
107
|
+
- **i18n** — all UI strings (page titles, table headers, buttons, empty states, flash messages) are backed by `config/locales/en.yml`; locale switching via `?locale=` param or session; add a custom locale by supplying a YAML file in your host app and registering it with `config.available_locales`
|
|
108
|
+
- **Custom dashboard cards** — `config.dashboard_cards` accepts an array of `{ title:, stats:, link: }` hashes rendered after the built-in queue stat cards; `stats:` is a lambda returning a `{ label => value }` hash evaluated at render time; `link:` is an optional header link
|
|
109
|
+
- **Custom nav links** — `config.nav_links` accepts an array of `{ label:, url: }` hashes appended to the main navigation bar after the built-in links
|
|
110
|
+
|
|
111
|
+
[↑ Back to top](#table-of-contents)
|
|
112
|
+
|
|
113
|
+
---
|
|
63
114
|
|
|
64
115
|
## Compatibility
|
|
65
116
|
|
|
@@ -71,6 +122,10 @@ SolidQueueWeb surfaces all of this in a browser UI available at any route you ch
|
|
|
71
122
|
|
|
72
123
|
Tested on Ruby 3.3, 3.4, and 4.0.
|
|
73
124
|
|
|
125
|
+
[↑ Back to top](#table-of-contents)
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
74
129
|
## Installation
|
|
75
130
|
|
|
76
131
|
Add to your application's Gemfile:
|
|
@@ -85,6 +140,10 @@ Then run:
|
|
|
85
140
|
bundle install
|
|
86
141
|
```
|
|
87
142
|
|
|
143
|
+
[↑ Back to top](#table-of-contents)
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
88
147
|
## Mounting the engine
|
|
89
148
|
|
|
90
149
|
Add to your `config/routes.rb`:
|
|
@@ -95,6 +154,10 @@ mount SolidQueueWeb::Engine, at: "/jobs"
|
|
|
95
154
|
|
|
96
155
|
The dashboard will be available at `/jobs`.
|
|
97
156
|
|
|
157
|
+
[↑ Back to top](#table-of-contents)
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
98
161
|
## Configuration
|
|
99
162
|
|
|
100
163
|
All settings are optional — the dashboard works with zero configuration. Create `config/initializers/solid_queue_web.rb` to customize behavior:
|
|
@@ -115,6 +178,9 @@ SolidQueueWeb.configure do |config|
|
|
|
115
178
|
config.current_actor = -> { current_user&.email } # identity for audit log (default: nil)
|
|
116
179
|
config.connects_to = { reading: :reading, writing: :writing } # read replica (default: nil)
|
|
117
180
|
config.time_zone = "America/New_York" # display timezone for all timestamps (default: nil = UTC)
|
|
181
|
+
config.available_locales = [:en, :fr] # locales available for switching (default: [:en])
|
|
182
|
+
config.nav_links = [{ label: "Admin", url: "/admin" }] # extra nav links (default: [])
|
|
183
|
+
config.dashboard_cards = [{ title: "My App", stats: -> { { "Users" => User.count } } }] # custom stat cards (default: [])
|
|
118
184
|
end
|
|
119
185
|
|
|
120
186
|
SolidQueueWeb.authenticate do
|
|
@@ -126,8 +192,16 @@ end
|
|
|
126
192
|
|
|
127
193
|
No authentication is enforced by default. When the `authenticate` block returns falsy, HTTP Basic auth is used as a fallback.
|
|
128
194
|
|
|
195
|
+
[↑ Back to top](#table-of-contents)
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
129
199
|
## Webhook alerts
|
|
130
200
|
|
|
201
|
+
The engine supports four webhook alert types, each firing asynchronously with a configurable cooldown to prevent repeated alerts.
|
|
202
|
+
|
|
203
|
+
### Failure threshold alerts
|
|
204
|
+
|
|
131
205
|
Set `alert_webhook_url` and `alert_failure_threshold` to receive a POST request whenever the failed job count meets or exceeds the threshold. This is useful for paging an on-call team or triggering a Slack notification via an incoming webhook.
|
|
132
206
|
|
|
133
207
|
```ruby
|
|
@@ -162,7 +236,7 @@ The request body is JSON:
|
|
|
162
236
|
|
|
163
237
|
The webhook fires asynchronously in a background thread so dashboard page loads are never delayed. HTTP errors are logged to `Rails.logger` and swallowed. The cooldown window prevents repeated alerts while the count stays elevated — the clock resets on each app restart.
|
|
164
238
|
|
|
165
|
-
|
|
239
|
+
### Queue depth alerts
|
|
166
240
|
|
|
167
241
|
Set `alert_queue_thresholds` to fire a webhook when any queue's ready job count meets or exceeds a per-queue limit:
|
|
168
242
|
|
|
@@ -187,7 +261,7 @@ The same `alert_webhook_url` endpoint(s) receive the payload, with a distinct ev
|
|
|
187
261
|
|
|
188
262
|
Cooldown is tracked independently per queue, so a persistently deep "critical" queue does not suppress alerts for "default". The shared `alert_webhook_cooldown` setting applies to each queue separately.
|
|
189
263
|
|
|
190
|
-
|
|
264
|
+
### Slow job alerts
|
|
191
265
|
|
|
192
266
|
Set `alert_slow_job_count_threshold` to fire a webhook when the number of currently-running slow jobs meets or exceeds a count. This requires `slow_job_threshold` to also be configured — it defines what "slow" means.
|
|
193
267
|
|
|
@@ -213,7 +287,7 @@ The same `alert_webhook_url` endpoint(s) receive the payload with a distinct eve
|
|
|
213
287
|
|
|
214
288
|
The alert fires on every dashboard page load while the condition persists, subject to the cooldown window.
|
|
215
289
|
|
|
216
|
-
|
|
290
|
+
### Stale process alerts
|
|
217
291
|
|
|
218
292
|
Set `alert_stale_process_threshold` to fire a webhook when the number of stale workers meets or exceeds a count. A process is considered stale when its `last_heartbeat_at` has not been updated within `SolidQueue.process_alive_threshold` (default 5 minutes). A stale worker means jobs in its queues have silently stopped processing.
|
|
219
293
|
|
|
@@ -238,11 +312,15 @@ The same `alert_webhook_url` endpoint(s) receive the payload with a distinct eve
|
|
|
238
312
|
|
|
239
313
|
The alert fires on every dashboard page load while the condition persists, subject to the cooldown window.
|
|
240
314
|
|
|
315
|
+
[↑ Back to top](#table-of-contents)
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
241
319
|
## Admin audit log
|
|
242
320
|
|
|
243
321
|
Every discard, retry, queue pause, and resume action is recorded to a `solid_queue_web_audit_events` table and viewable at `/jobs/audit`.
|
|
244
322
|
|
|
245
|
-
###
|
|
323
|
+
### Setup
|
|
246
324
|
|
|
247
325
|
The audit log requires an opt-in migration. Run the install generator to copy it to your application:
|
|
248
326
|
|
|
@@ -278,6 +356,10 @@ If not configured, the actor column is left `nil`.
|
|
|
278
356
|
|
|
279
357
|
The audit log page at `/jobs/audit` supports filtering by action, actor, and queue name. All records can be exported as CSV.
|
|
280
358
|
|
|
359
|
+
[↑ Back to top](#table-of-contents)
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
281
363
|
## Metrics endpoint
|
|
282
364
|
|
|
283
365
|
`GET /jobs/metrics.json` returns a machine-readable JSON document suitable for Prometheus scraping, uptime monitors, or external dashboards. No configuration is required — the endpoint is available as soon as the engine is mounted.
|
|
@@ -320,6 +402,10 @@ When `slow_job_threshold` is configured, a `slow_jobs` integer is also included
|
|
|
320
402
|
|
|
321
403
|
The endpoint respects the same authentication and `connects_to` settings as the rest of the dashboard. A process is counted as **stale** when its `last_heartbeat_at` is older than `SolidQueue.process_alive_threshold` (default: 5 minutes).
|
|
322
404
|
|
|
405
|
+
[↑ Back to top](#table-of-contents)
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
323
409
|
## Read replica support
|
|
324
410
|
|
|
325
411
|
Set `connects_to` with both `reading:` and `writing:` keys to enable automatic role switching. GET requests are routed to the reading role; POST/DELETE/PATCH requests use the writing role.
|
|
@@ -339,16 +425,104 @@ config.connects_to = { role: :writing }
|
|
|
339
425
|
|
|
340
426
|
When `connects_to` is `nil` (the default), no connection switching occurs and single-database apps are unaffected.
|
|
341
427
|
|
|
428
|
+
[↑ Back to top](#table-of-contents)
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
## i18n
|
|
433
|
+
|
|
434
|
+
All dashboard UI strings — page titles, table headers, button labels, empty states, and flash messages — are backed by `config/locales/en.yml` in the gem. The engine ships with **English (`en`)** only.
|
|
435
|
+
|
|
436
|
+
The selected locale is stored in the session and applied via `I18n.with_locale`, so it persists across requests without touching the host application's locale. The `?locale=` query param takes precedence over the session value, making it easy to deep-link to a specific language.
|
|
437
|
+
|
|
438
|
+
```ruby
|
|
439
|
+
SolidQueueWeb.configure do |config|
|
|
440
|
+
# Locales available for switching (default: [:en]).
|
|
441
|
+
config.available_locales = [:en, :fr]
|
|
442
|
+
end
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
### Adding a custom locale
|
|
446
|
+
|
|
447
|
+
1. Create a locale file in your host application under `config/locales/`, e.g. `config/locales/solid_queue_web.fr.yml`.
|
|
448
|
+
2. Nest all keys under `fr > solid_queue_web:` — use [`config/locales/en.yml`](config/locales/en.yml) in the gem as a reference for the full key list.
|
|
449
|
+
3. Register the locale:
|
|
450
|
+
|
|
451
|
+
```ruby
|
|
452
|
+
config.available_locales = [:en, :fr]
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Rails will pick up the file automatically via its standard `config.i18n.load_path`; no additional configuration is needed.
|
|
456
|
+
|
|
457
|
+
[↑ Back to top](#table-of-contents)
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
## Extensibility
|
|
462
|
+
|
|
463
|
+
### Custom dashboard cards
|
|
464
|
+
|
|
465
|
+
`config.dashboard_cards` adds custom stat cards to the dashboard after the built-in queue cards. Each card accepts three keys:
|
|
466
|
+
|
|
467
|
+
| Key | Type | Description |
|
|
468
|
+
|-----|------|-------------|
|
|
469
|
+
| `title` | String | Card heading (required) |
|
|
470
|
+
| `link` | `{ label:, url: }` | Optional header link rendered top-right |
|
|
471
|
+
| `stats` | Lambda | Optional — called at render time; must return a `{ label => value }` hash |
|
|
472
|
+
|
|
473
|
+
```ruby
|
|
474
|
+
SolidQueueWeb.configure do |config|
|
|
475
|
+
config.dashboard_cards = [
|
|
476
|
+
{
|
|
477
|
+
title: "My App",
|
|
478
|
+
link: { label: "View Admin", url: "/admin" },
|
|
479
|
+
stats: -> { { "Users" => User.count, "Premium" => User.premium.count } }
|
|
480
|
+
}
|
|
481
|
+
]
|
|
482
|
+
end
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
The `stats` lambda runs on every dashboard render, so keep it fast. Defaults to `[]` — no custom cards appear when unconfigured.
|
|
486
|
+
|
|
487
|
+
### Custom nav links
|
|
488
|
+
|
|
489
|
+
`config.nav_links` appends extra links to the main navigation bar after the built-in links. Use it to link back to your host application's admin pages or related tools.
|
|
490
|
+
|
|
491
|
+
```ruby
|
|
492
|
+
SolidQueueWeb.configure do |config|
|
|
493
|
+
config.nav_links = [
|
|
494
|
+
{ label: "Back to App", url: "/" },
|
|
495
|
+
{ label: "Admin", url: "/admin" }
|
|
496
|
+
]
|
|
497
|
+
end
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
Defaults to `[]` — no extra links appear when unconfigured.
|
|
501
|
+
|
|
502
|
+
[↑ Back to top](#table-of-contents)
|
|
503
|
+
|
|
504
|
+
---
|
|
505
|
+
|
|
342
506
|
## Roadmap
|
|
343
507
|
|
|
344
508
|
See [ROADMAP.md](ROADMAP.md) for the full post-1.0 feature plan, organized by release milestone.
|
|
345
509
|
|
|
346
510
|
Pull requests for any of these are welcome. See [Contributing](#contributing) below.
|
|
347
511
|
|
|
512
|
+
[↑ Back to top](#table-of-contents)
|
|
513
|
+
|
|
514
|
+
---
|
|
515
|
+
|
|
348
516
|
## Contributing
|
|
349
517
|
|
|
350
518
|
Bug reports and pull requests are welcome on [GitHub](https://github.com/eclectic-coding/solid_queue_web).
|
|
351
519
|
|
|
520
|
+
[↑ Back to top](#table-of-contents)
|
|
521
|
+
|
|
522
|
+
---
|
|
523
|
+
|
|
352
524
|
## License
|
|
353
525
|
|
|
354
|
-
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
526
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
527
|
+
|
|
528
|
+
[↑ Back to top](#table-of-contents)
|
|
@@ -19,6 +19,34 @@
|
|
|
19
19
|
font-weight: 600;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
.sqd-card__body {
|
|
23
|
+
padding: 0.75rem 1rem;
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.sqd-custom-stat {
|
|
29
|
+
display: flex;
|
|
30
|
+
justify-content: space-between;
|
|
31
|
+
align-items: baseline;
|
|
32
|
+
font-size: 13px;
|
|
33
|
+
padding: 0.375rem 0;
|
|
34
|
+
border-bottom: 1px solid var(--border);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.sqd-custom-stat:last-child {
|
|
38
|
+
border-bottom: none;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.sqd-custom-stat__label {
|
|
42
|
+
color: var(--muted);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.sqd-custom-stat__value {
|
|
46
|
+
font-weight: 600;
|
|
47
|
+
font-variant-numeric: tabular-nums;
|
|
48
|
+
}
|
|
49
|
+
|
|
22
50
|
table {
|
|
23
51
|
width: 100%;
|
|
24
52
|
border-collapse: collapse;
|
|
@@ -8,10 +8,20 @@ module SolidQueueWeb
|
|
|
8
8
|
STAGGER_INTERVALS = { "5s" => 5.seconds, "10s" => 10.seconds, "30s" => 30.seconds, "1m" => 1.minute }.freeze
|
|
9
9
|
|
|
10
10
|
before_action :authenticate!
|
|
11
|
+
around_action :with_locale
|
|
11
12
|
around_action :with_database_connection
|
|
12
13
|
|
|
13
14
|
private
|
|
14
15
|
|
|
16
|
+
def with_locale
|
|
17
|
+
available = SolidQueueWeb.available_locales.map(&:to_s)
|
|
18
|
+
locale = params[:locale].presence_in(available) ||
|
|
19
|
+
session[:solid_queue_web_locale].presence_in(available) ||
|
|
20
|
+
I18n.default_locale.to_s
|
|
21
|
+
session[:solid_queue_web_locale] = locale
|
|
22
|
+
I18n.with_locale(locale) { yield }
|
|
23
|
+
end
|
|
24
|
+
|
|
15
25
|
def with_database_connection
|
|
16
26
|
config = SolidQueueWeb.connects_to
|
|
17
27
|
return yield unless config
|
|
@@ -3,9 +3,9 @@ module SolidQueueWeb
|
|
|
3
3
|
def destroy
|
|
4
4
|
jobs = SolidQueue::BlockedExecution.includes(:job).map(&:job)
|
|
5
5
|
SolidQueue::BlockedExecution.discard_all_from_jobs(jobs)
|
|
6
|
-
redirect_to root_path, notice: "
|
|
6
|
+
redirect_to root_path, notice: t("solid_queue_web.flash.blocked_jobs_discarded", count: jobs.size)
|
|
7
7
|
rescue => e
|
|
8
|
-
redirect_to root_path, alert: "
|
|
8
|
+
redirect_to root_path, alert: t("solid_queue_web.flash.cannot_discard_blocked_jobs", error: e.message)
|
|
9
9
|
end
|
|
10
10
|
end
|
|
11
11
|
end
|
|
@@ -5,11 +5,11 @@ module SolidQueueWeb
|
|
|
5
5
|
new_arguments = JSON.parse(params[:arguments])
|
|
6
6
|
execution.job.update!(arguments: new_arguments)
|
|
7
7
|
execution.retry
|
|
8
|
-
redirect_to failed_jobs_path, notice: "
|
|
8
|
+
redirect_to failed_jobs_path, notice: t("solid_queue_web.flash.arguments_updated")
|
|
9
9
|
rescue JSON::ParserError
|
|
10
|
-
redirect_to job_path(execution.job), alert: "
|
|
10
|
+
redirect_to job_path(execution.job), alert: t("solid_queue_web.flash.invalid_json")
|
|
11
11
|
rescue => e
|
|
12
|
-
redirect_to failed_jobs_path, alert: "
|
|
12
|
+
redirect_to failed_jobs_path, alert: t("solid_queue_web.flash.cannot_update_job", error: e.message)
|
|
13
13
|
end
|
|
14
14
|
end
|
|
15
15
|
end
|
|
@@ -8,9 +8,9 @@ module SolidQueueWeb
|
|
|
8
8
|
SolidQueue::FailedExecution.retry_all(jobs)
|
|
9
9
|
record_audit("failed_jobs_retried", item_count: jobs.size)
|
|
10
10
|
redirect_to failed_jobs_path,
|
|
11
|
-
notice: "
|
|
11
|
+
notice: t("solid_queue_web.flash.jobs_retried", count: jobs.size)
|
|
12
12
|
rescue => e
|
|
13
|
-
redirect_to failed_jobs_path, alert: "
|
|
13
|
+
redirect_to failed_jobs_path, alert: t("solid_queue_web.flash.cannot_retry_jobs", error: e.message)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def destroy
|
|
@@ -20,9 +20,9 @@ module SolidQueueWeb
|
|
|
20
20
|
SolidQueue::FailedExecution.discard_all_from_jobs(jobs)
|
|
21
21
|
record_audit("failed_jobs_discarded", item_count: jobs.size)
|
|
22
22
|
redirect_to failed_jobs_path,
|
|
23
|
-
notice: "
|
|
23
|
+
notice: t("solid_queue_web.flash.jobs_discarded", count: jobs.size)
|
|
24
24
|
rescue => e
|
|
25
|
-
redirect_to failed_jobs_path, alert: "
|
|
25
|
+
redirect_to failed_jobs_path, alert: t("solid_queue_web.flash.cannot_discard_jobs", error: e.message)
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
28
|
end
|
|
@@ -17,7 +17,7 @@ module SolidQueueWeb
|
|
|
17
17
|
executions = params[:id] ? [SolidQueue::FailedExecution.find(params[:id])] : filtered_scope.to_a
|
|
18
18
|
perform_discard(executions)
|
|
19
19
|
rescue => e
|
|
20
|
-
redirect_to failed_jobs_path, alert: "
|
|
20
|
+
redirect_to failed_jobs_path, alert: t("solid_queue_web.flash.cannot_discard_job", error: e.message)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
private
|
|
@@ -41,7 +41,7 @@ module SolidQueueWeb
|
|
|
41
41
|
SolidQueue::FailedExecution.discard_all_from_jobs(jobs)
|
|
42
42
|
record_audit(action, job_class: jobs.first&.class_name, queue_name: jobs.first&.queue_name, item_count: jobs.size)
|
|
43
43
|
redirect_to failed_jobs_path(queue: @queue, q: @search, period: @period),
|
|
44
|
-
notice: "
|
|
44
|
+
notice: t("solid_queue_web.flash.jobs_discarded", count: jobs.size)
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def sortable_columns
|
|
@@ -4,18 +4,18 @@ module SolidQueueWeb
|
|
|
4
4
|
def destroy
|
|
5
5
|
status = params[:status]
|
|
6
6
|
period = params[:period].presence_in(PERIOD_DURATIONS.keys)
|
|
7
|
-
raise ArgumentError, "
|
|
7
|
+
raise ArgumentError, t("solid_queue_web.flash.cannot_discard", status: status) unless Job::DISCARDABLE.include?(status)
|
|
8
8
|
model = Job::EXECUTION_MODELS[status]
|
|
9
9
|
ids = Array(params[:ids]).map(&:to_i).reject(&:zero?)
|
|
10
10
|
jobs = model.where(id: ids).includes(:job).map(&:job)
|
|
11
11
|
model.discard_all_from_jobs(jobs)
|
|
12
12
|
record_audit("jobs_discarded", item_count: jobs.size)
|
|
13
13
|
redirect_to jobs_path(status: status, period: period),
|
|
14
|
-
notice: "
|
|
14
|
+
notice: t("solid_queue_web.flash.jobs_discarded", count: jobs.size)
|
|
15
15
|
rescue ArgumentError => e
|
|
16
16
|
redirect_to jobs_path(status: status), alert: e.message
|
|
17
17
|
rescue => e
|
|
18
|
-
redirect_to jobs_path(status: status), alert: "
|
|
18
|
+
redirect_to jobs_path(status: status), alert: t("solid_queue_web.flash.cannot_discard_jobs", error: e.message)
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
end
|
|
@@ -36,18 +36,19 @@ module SolidQueueWeb
|
|
|
36
36
|
@remaining_count = filtered_scope(model).count
|
|
37
37
|
respond_to do |format|
|
|
38
38
|
format.turbo_stream
|
|
39
|
-
format.html { redirect_to jobs_return_path, notice: "
|
|
39
|
+
format.html { redirect_to jobs_return_path, notice: t("solid_queue_web.flash.job_discarded") }
|
|
40
40
|
end
|
|
41
41
|
else
|
|
42
42
|
jobs = filtered_scope(model).map(&:job)
|
|
43
43
|
model.discard_all_from_jobs(jobs)
|
|
44
44
|
record_audit("jobs_discarded", item_count: jobs.size)
|
|
45
|
-
redirect_to jobs_return_path, notice: "
|
|
45
|
+
redirect_to jobs_return_path, notice: t("solid_queue_web.flash.jobs_discarded", count: jobs.size)
|
|
46
46
|
end
|
|
47
47
|
rescue ArgumentError => e
|
|
48
48
|
redirect_to jobs_return_path, alert: e.message
|
|
49
49
|
rescue => e
|
|
50
|
-
|
|
50
|
+
msg = params[:id] ? t("solid_queue_web.flash.cannot_discard_job", error: e.message) : t("solid_queue_web.flash.cannot_discard_jobs", error: e.message)
|
|
51
|
+
redirect_to jobs_return_path, alert: msg
|
|
51
52
|
end
|
|
52
53
|
|
|
53
54
|
private
|
|
@@ -21,19 +21,19 @@ module SolidQueueWeb
|
|
|
21
21
|
@remaining_count = filtered_scope(model).count
|
|
22
22
|
respond_to do |format|
|
|
23
23
|
format.turbo_stream
|
|
24
|
-
format.html { redirect_to queue_jobs_path(queue_name: @queue, status: @status), notice: "
|
|
24
|
+
format.html { redirect_to queue_jobs_path(queue_name: @queue, status: @status), notice: t("solid_queue_web.flash.job_discarded") }
|
|
25
25
|
end
|
|
26
26
|
else
|
|
27
27
|
jobs = filtered_scope(model).map(&:job)
|
|
28
28
|
model.discard_all_from_jobs(jobs)
|
|
29
29
|
redirect_to queue_jobs_path(queue_name: @queue, status: @status),
|
|
30
|
-
notice: "
|
|
30
|
+
notice: t("solid_queue_web.flash.jobs_discarded", count: jobs.size)
|
|
31
31
|
end
|
|
32
32
|
rescue ArgumentError => e
|
|
33
33
|
redirect_to queue_jobs_path(queue_name: @queue, status: @status), alert: e.message
|
|
34
34
|
rescue => e
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
msg = params[:id] ? t("solid_queue_web.flash.cannot_discard_job", error: e.message) : t("solid_queue_web.flash.cannot_discard_jobs", error: e.message)
|
|
36
|
+
redirect_to queue_jobs_path(queue_name: @queue, status: @status), alert: msg
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
private
|
|
@@ -51,7 +51,7 @@ module SolidQueueWeb
|
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
def execution_model_for!(status)
|
|
54
|
-
raise ArgumentError, "
|
|
54
|
+
raise ArgumentError, t("solid_queue_web.flash.cannot_discard_from_queue", status: status) unless Job::DISCARDABLE.include?(status)
|
|
55
55
|
Job::EXECUTION_MODELS[status]
|
|
56
56
|
end
|
|
57
57
|
end
|
|
@@ -5,18 +5,18 @@ module SolidQueueWeb
|
|
|
5
5
|
queue = SolidQueue::Queue.find_by_name(params[:queue_name])
|
|
6
6
|
queue.pause
|
|
7
7
|
record_audit("queue_paused", queue_name: queue.name)
|
|
8
|
-
redirect_to queues_path, notice: "
|
|
8
|
+
redirect_to queues_path, notice: t("solid_queue_web.flash.queue_paused", name: queue.name)
|
|
9
9
|
rescue => e
|
|
10
|
-
redirect_to queues_path, alert: "
|
|
10
|
+
redirect_to queues_path, alert: t("solid_queue_web.flash.cannot_pause_queue", error: e.message)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def destroy
|
|
14
14
|
queue = SolidQueue::Queue.find_by_name(params[:queue_name])
|
|
15
15
|
queue.resume
|
|
16
16
|
record_audit("queue_resumed", queue_name: queue.name)
|
|
17
|
-
redirect_to queues_path, notice: "
|
|
17
|
+
redirect_to queues_path, notice: t("solid_queue_web.flash.queue_resumed", name: queue.name)
|
|
18
18
|
rescue => e
|
|
19
|
-
redirect_to queues_path, alert: "
|
|
19
|
+
redirect_to queues_path, alert: t("solid_queue_web.flash.cannot_resume_queue", error: e.message)
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
end
|
|
@@ -5,14 +5,14 @@ module SolidQueueWeb
|
|
|
5
5
|
result = task.enqueue(at: Time.current)
|
|
6
6
|
|
|
7
7
|
if result
|
|
8
|
-
redirect_to recurring_tasks_path, notice: "
|
|
8
|
+
redirect_to recurring_tasks_path, notice: t("solid_queue_web.flash.task_queued", key: task.key)
|
|
9
9
|
else
|
|
10
|
-
redirect_to recurring_tasks_path, alert: "
|
|
10
|
+
redirect_to recurring_tasks_path, alert: t("solid_queue_web.flash.cannot_enqueue_task", key: task.key)
|
|
11
11
|
end
|
|
12
12
|
rescue ActiveRecord::RecordNotFound
|
|
13
|
-
redirect_to recurring_tasks_path, alert: "
|
|
13
|
+
redirect_to recurring_tasks_path, alert: t("solid_queue_web.flash.task_not_found")
|
|
14
14
|
rescue => e
|
|
15
|
-
redirect_to recurring_tasks_path, alert: "
|
|
15
|
+
redirect_to recurring_tasks_path, alert: t("solid_queue_web.flash.cannot_run_task", error: e.message)
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
end
|
|
@@ -8,7 +8,7 @@ module SolidQueueWeb
|
|
|
8
8
|
|
|
9
9
|
if params[:stagger].present? && executions.size > 1
|
|
10
10
|
interval = STAGGER_INTERVALS[params[:stagger]]
|
|
11
|
-
raise ArgumentError, "
|
|
11
|
+
raise ArgumentError, t("solid_queue_web.flash.invalid_stagger") unless interval
|
|
12
12
|
executions.each_with_index do |execution, i|
|
|
13
13
|
execution.job.update!(scheduled_at: i.zero? ? nil : Time.current + (i * interval))
|
|
14
14
|
execution.retry
|
|
@@ -23,17 +23,16 @@ module SolidQueueWeb
|
|
|
23
23
|
rescue ArgumentError => e
|
|
24
24
|
redirect_to failed_jobs_path, alert: e.message
|
|
25
25
|
rescue => e
|
|
26
|
-
redirect_to failed_jobs_path, alert: "
|
|
26
|
+
redirect_to failed_jobs_path, alert: t("solid_queue_web.flash.cannot_retry_job", error: e.message)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
private
|
|
30
30
|
|
|
31
31
|
def retry_notice(count)
|
|
32
|
-
label = "#{count} #{"job".pluralize(count)}"
|
|
33
32
|
if params[:stagger].present? && count > 1
|
|
34
|
-
"
|
|
33
|
+
t("solid_queue_web.flash.jobs_retried_staggered", count: count, stagger: params[:stagger])
|
|
35
34
|
else
|
|
36
|
-
"
|
|
35
|
+
t("solid_queue_web.flash.jobs_retried", count: count)
|
|
37
36
|
end
|
|
38
37
|
end
|
|
39
38
|
|
|
@@ -8,10 +8,10 @@ module SolidQueueWeb
|
|
|
8
8
|
SolidQueue::Job.where(id: job_ids).update_all(scheduled_at: 1.second.ago)
|
|
9
9
|
|
|
10
10
|
redirect_to jobs_path(status: "scheduled", period: @period),
|
|
11
|
-
notice: "
|
|
11
|
+
notice: t("solid_queue_web.flash.jobs_run_immediately", count: job_ids.size)
|
|
12
12
|
rescue => e
|
|
13
13
|
redirect_to jobs_path(status: "scheduled", period: @period),
|
|
14
|
-
alert: "
|
|
14
|
+
alert: t("solid_queue_web.flash.cannot_run_jobs", error: e.message)
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def update
|
|
@@ -26,14 +26,14 @@ module SolidQueueWeb
|
|
|
26
26
|
respond_to do |format|
|
|
27
27
|
format.turbo_stream
|
|
28
28
|
format.html do
|
|
29
|
-
notice = @run_now ? "
|
|
29
|
+
notice = @run_now ? t("solid_queue_web.flash.job_run_immediately") : t("solid_queue_web.flash.job_rescheduled", offset: params[:offset])
|
|
30
30
|
redirect_to jobs_path(status: "scheduled", period: @period), notice: notice
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
rescue ArgumentError => e
|
|
34
34
|
redirect_to jobs_path(status: "scheduled"), alert: e.message
|
|
35
35
|
rescue => e
|
|
36
|
-
redirect_to jobs_path(status: "scheduled"), alert: "
|
|
36
|
+
redirect_to jobs_path(status: "scheduled"), alert: t("solid_queue_web.flash.cannot_reschedule_job", error: e.message)
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
private
|
|
@@ -46,7 +46,7 @@ module SolidQueueWeb
|
|
|
46
46
|
|
|
47
47
|
def resolve_new_time(execution, offset)
|
|
48
48
|
return 1.second.ago if offset == "now"
|
|
49
|
-
raise ArgumentError, "
|
|
49
|
+
raise ArgumentError, t("solid_queue_web.flash.invalid_offset") unless PERIOD_DURATIONS.key?(offset)
|
|
50
50
|
|
|
51
51
|
execution.scheduled_at + PERIOD_DURATIONS[offset]
|
|
52
52
|
end
|