solid_queue_web 1.4.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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +220 -4
  3. data/app/assets/stylesheets/solid_queue_web/_04_table.css +28 -0
  4. data/app/controllers/solid_queue_web/application_controller.rb +30 -0
  5. data/app/controllers/solid_queue_web/audit_controller.rb +43 -0
  6. data/app/controllers/solid_queue_web/blocked_jobs_controller.rb +2 -2
  7. data/app/controllers/solid_queue_web/failed_jobs/arguments_controller.rb +3 -3
  8. data/app/controllers/solid_queue_web/failed_jobs/selections_controller.rb +6 -4
  9. data/app/controllers/solid_queue_web/failed_jobs_controller.rb +4 -2
  10. data/app/controllers/solid_queue_web/jobs/selections_controller.rb +4 -3
  11. data/app/controllers/solid_queue_web/jobs_controller.rb +7 -3
  12. data/app/controllers/solid_queue_web/queues/jobs_controller.rb +5 -5
  13. data/app/controllers/solid_queue_web/queues/pauses_controller.rb +6 -4
  14. data/app/controllers/solid_queue_web/recurring_tasks/runs_controller.rb +4 -4
  15. data/app/controllers/solid_queue_web/retry_failed_jobs_controller.rb +6 -5
  16. data/app/controllers/solid_queue_web/scheduled_jobs_controller.rb +5 -5
  17. data/app/models/solid_queue_web/audit_event.rb +17 -0
  18. data/app/views/layouts/solid_queue_web/application.html.erb +20 -16
  19. data/app/views/solid_queue_web/audit/index.html.erb +78 -0
  20. data/app/views/solid_queue_web/dashboard/index.html.erb +67 -46
  21. data/app/views/solid_queue_web/failed_jobs/errors/index.html.erb +7 -7
  22. data/app/views/solid_queue_web/failed_jobs/index.html.erb +31 -31
  23. data/app/views/solid_queue_web/history/index.html.erb +14 -14
  24. data/app/views/solid_queue_web/jobs/index.html.erb +42 -42
  25. data/app/views/solid_queue_web/jobs/show.html.erb +20 -20
  26. data/app/views/solid_queue_web/performance/index.html.erb +16 -14
  27. data/app/views/solid_queue_web/processes/index.html.erb +16 -16
  28. data/app/views/solid_queue_web/queues/index.html.erb +16 -16
  29. data/app/views/solid_queue_web/queues/jobs/index.html.erb +21 -21
  30. data/app/views/solid_queue_web/recurring_tasks/index.html.erb +15 -15
  31. data/app/views/solid_queue_web/search/index.html.erb +13 -13
  32. data/config/locales/en.yml +330 -0
  33. data/config/routes.rb +1 -0
  34. data/db/migrate/01_create_solid_queue_web_audit_events.rb +16 -0
  35. data/lib/generators/solid_queue_web/install/migrations_generator.rb +24 -0
  36. data/lib/generators/solid_queue_web/install/templates/create_solid_queue_web_audit_events.rb.tt +16 -0
  37. data/lib/solid_queue_web/engine.rb +1 -0
  38. data/lib/solid_queue_web/version.rb +1 -1
  39. data/lib/solid_queue_web.rb +18 -1
  40. metadata +8 -1
@@ -1,30 +1,30 @@
1
1
  <div class="sqd-page-header">
2
- <h1 class="sqd-page-title">Failed Jobs</h1>
2
+ <h1 class="sqd-page-title"><%= t("solid_queue_web.failed_jobs.title") %></h1>
3
3
  <% if @failed_jobs.any? %>
4
4
  <div class="sqd-actions">
5
- <%= link_to "Error Summary", failed_job_errors_path, class: "sqd-btn sqd-btn--muted sqd-btn--sm" %>
6
- <%= link_to "Export CSV", failed_jobs_path(format: :csv, queue: @queue, q: @search, period: @period),
5
+ <%= link_to t("solid_queue_web.failed_jobs.error_summary"), failed_job_errors_path, class: "sqd-btn sqd-btn--muted sqd-btn--sm" %>
6
+ <%= link_to t("solid_queue_web.failed_jobs.export_csv"), failed_jobs_path(format: :csv, queue: @queue, q: @search, period: @period),
7
7
  class: "sqd-btn sqd-btn--muted", data: { turbo: false } %>
8
- <%= button_to "Retry All", retry_all_failed_jobs_path,
8
+ <%= button_to t("solid_queue_web.failed_jobs.retry_all"), retry_all_failed_jobs_path,
9
9
  method: :post,
10
10
  params: { queue: @queue, q: @search, period: @period },
11
11
  class: "sqd-btn sqd-btn--primary",
12
- data: { confirm: "Retry all #{@failed_jobs.size} failed jobs?" } %>
12
+ data: { confirm: t("solid_queue_web.failed_jobs.confirm_retry_all", count: @failed_jobs.size) } %>
13
13
  <% if @failed_jobs.size > 1 %>
14
14
  <% %w[5s 10s 30s 1m].each do |interval| %>
15
15
  <%= button_to "+#{interval}", retry_all_failed_jobs_path,
16
16
  method: :post,
17
17
  params: { stagger: interval, queue: @queue, q: @search, period: @period },
18
18
  class: "sqd-btn sqd-btn--muted sqd-btn--sm",
19
- title: "Retry all, staggered #{interval} apart",
20
- data: { confirm: "Retry #{@failed_jobs.size} failed jobs staggered #{interval} apart?" } %>
19
+ title: t("solid_queue_web.failed_jobs.stagger_title", interval: interval),
20
+ data: { confirm: t("solid_queue_web.failed_jobs.confirm_stagger", count: @failed_jobs.size, interval: interval) } %>
21
21
  <% end %>
22
22
  <% end %>
23
- <%= button_to "Discard All", discard_all_failed_jobs_path,
23
+ <%= button_to t("solid_queue_web.failed_jobs.discard_all"), discard_all_failed_jobs_path,
24
24
  method: :post,
25
25
  params: { queue: @queue, q: @search, period: @period },
26
26
  class: "sqd-btn sqd-btn--danger",
27
- data: { confirm: "Discard all #{@failed_jobs.size} failed jobs? This cannot be undone." } %>
27
+ data: { confirm: t("solid_queue_web.failed_jobs.confirm_discard_all", count: @failed_jobs.size) } %>
28
28
  </div>
29
29
  <% end %>
30
30
  </div>
@@ -37,13 +37,13 @@
37
37
  <input type="hidden" name="sort" value="<%= @sort %>">
38
38
  <input type="hidden" name="direction" value="<%= @direction %>">
39
39
  <input class="sqd-search__input" type="search" name="q" value="<%= @search %>"
40
- placeholder="Filter by job class…" autocomplete="off" aria-label="Filter by job class"
40
+ placeholder="<%= t("solid_queue_web.jobs.placeholder_job_class") %>" autocomplete="off" aria-label="<%= t("solid_queue_web.jobs.aria_filter_job_class") %>"
41
41
  data-action="input->search#filter">
42
42
  <% if @search.present? %>
43
- <%= link_to "Clear", failed_jobs_path(queue: @queue, period: @period), class: "sqd-btn sqd-btn--muted" %>
43
+ <%= link_to t("solid_queue_web.shared.clear"), failed_jobs_path(queue: @queue, period: @period), class: "sqd-btn sqd-btn--muted" %>
44
44
  <% end %>
45
- <div class="sqd-period-filter" role="group" aria-label="Time period" data-controller="filters" data-filters-page-value="failed">
46
- <%= link_to "All", failed_jobs_path(queue: @queue, q: @search), class: @period.nil? ? "active" : "", aria: { current: @period.nil? ? "true" : nil }, aria_label: "All time", data: { action: "click->filters#savePeriod", filters_period_param: "" } %>
45
+ <div class="sqd-period-filter" role="group" aria-label="<%= t("solid_queue_web.shared.period_filter.label") %>" data-controller="filters" data-filters-page-value="failed">
46
+ <%= link_to t("solid_queue_web.shared.period_filter.all"), failed_jobs_path(queue: @queue, q: @search), class: @period.nil? ? "active" : "", aria: { current: @period.nil? ? "true" : nil }, aria_label: "All time", data: { action: "click->filters#savePeriod", filters_period_param: "" } %>
47
47
  <%= link_to "1h", failed_jobs_path(queue: @queue, q: @search, period: "1h"), class: @period == "1h" ? "active" : "", aria: { current: @period == "1h" ? "true" : nil }, aria_label: "Last 1 hour", data: { action: "click->filters#savePeriod", filters_period_param: "1h" } %>
48
48
  <%= link_to "24h", failed_jobs_path(queue: @queue, q: @search, period: "24h"), class: @period == "24h" ? "active" : "", aria: { current: @period == "24h" ? "true" : nil }, aria_label: "Last 24 hours", data: { action: "click->filters#savePeriod", filters_period_param: "24h" } %>
49
49
  <%= link_to "7d", failed_jobs_path(queue: @queue, q: @search, period: "7d"), class: @period == "7d" ? "active" : "", aria: { current: @period == "7d" ? "true" : nil }, aria_label: "Last 7 days", data: { action: "click->filters#savePeriod", filters_period_param: "7d" } %>
@@ -63,20 +63,20 @@
63
63
  <% end %>
64
64
 
65
65
  <%= form_tag failed_job_selection_path, method: :delete, id: "discard-selection-form",
66
- data: { turbo_confirm: "Discard selected jobs? This cannot be undone." } do %>
66
+ data: { turbo_confirm: t("solid_queue_web.failed_jobs.confirm_discard_selected") } do %>
67
67
  <%= hidden_field_tag :queue, @queue %>
68
68
  <%= hidden_field_tag :q, @search %>
69
69
  <%= hidden_field_tag :period, @period %>
70
70
  <% end %>
71
71
 
72
72
  <div class="sqd-selection-bar" data-selection-target="bar" style="display: none;">
73
- <span class="sqd-muted-text"><span data-selection-target="count">0</span> selected</span>
73
+ <span class="sqd-muted-text"><span data-selection-target="count">0</span> <%= t("solid_queue_web.jobs.selected_label") %></span>
74
74
  <button type="button" class="sqd-btn sqd-btn--primary sqd-btn--sm"
75
75
  data-action="click->selection#submit"
76
- data-selection-form-id-param="retry-selection-form">Retry Selected</button>
76
+ data-selection-form-id-param="retry-selection-form"><%= t("solid_queue_web.failed_jobs.retry_selected") %></button>
77
77
  <button type="button" class="sqd-btn sqd-btn--danger sqd-btn--sm"
78
78
  data-action="click->selection#submit"
79
- data-selection-form-id-param="discard-selection-form">Discard Selected</button>
79
+ data-selection-form-id-param="discard-selection-form"><%= t("solid_queue_web.failed_jobs.discard_selected") %></button>
80
80
  </div>
81
81
 
82
82
  <div class="sqd-card">
@@ -86,14 +86,14 @@
86
86
  <th scope="col">
87
87
  <input type="checkbox" data-selection-target="selectAll"
88
88
  data-action="change->selection#selectAll"
89
- aria-label="Select all failed jobs">
89
+ aria-label="<%= t("solid_queue_web.failed_jobs.select_all") %>">
90
90
  </th>
91
91
  <% sort_url = ->(p) { failed_jobs_path(queue: @queue, q: @search, period: @period, **p) } %>
92
- <%= sort_header_th("Job Class", "class_name", sort_url, current_sort: @sort, current_dir: @direction) %>
93
- <%= sort_header_th("Queue", "queue_name", sort_url, current_sort: @sort, current_dir: @direction) %>
94
- <th scope="col">Error</th>
95
- <%= sort_header_th("Failed At", "created_at", sort_url, current_sort: @sort, current_dir: @direction) %>
96
- <th scope="col"><span class="sqd-sr-only">Actions</span></th>
92
+ <%= sort_header_th(t("solid_queue_web.failed_jobs.col_job_class"), "class_name", sort_url, current_sort: @sort, current_dir: @direction) %>
93
+ <%= sort_header_th(t("solid_queue_web.failed_jobs.col_queue"), "queue_name", sort_url, current_sort: @sort, current_dir: @direction) %>
94
+ <th scope="col"><%= t("solid_queue_web.failed_jobs.col_error") %></th>
95
+ <%= sort_header_th(t("solid_queue_web.failed_jobs.col_failed_at"), "created_at", sort_url, current_sort: @sort, current_dir: @direction) %>
96
+ <th scope="col"><span class="sqd-sr-only"><%= t("solid_queue_web.shared.actions") %></span></th>
97
97
  </tr>
98
98
  </thead>
99
99
  <tbody>
@@ -104,7 +104,7 @@
104
104
  <input type="checkbox" value="<%= execution.id %>"
105
105
  data-selection-target="checkbox"
106
106
  data-action="change->selection#toggle"
107
- aria-label="Select job <%= job.class_name %>">
107
+ aria-label="<%= t("solid_queue_web.failed_jobs.select_job", class_name: job.class_name) %>">
108
108
  </td>
109
109
  <td><%= link_to job.class_name, job_path(job), class: "sqd-table-link" %></td>
110
110
  <td>
@@ -122,11 +122,11 @@
122
122
  </td>
123
123
  <td class="sqd-mono"><%= format_timestamp(execution.created_at) %></td>
124
124
  <td class="sqd-row-actions">
125
- <%= button_to "Retry", retry_failed_job_path(execution), method: :post,
125
+ <%= button_to t("solid_queue_web.failed_jobs.retry"), retry_failed_job_path(execution), method: :post,
126
126
  class: "sqd-btn sqd-btn--primary sqd-btn--sm" %>
127
- <%= button_to "Discard", failed_job_path(execution), method: :delete,
127
+ <%= button_to t("solid_queue_web.failed_jobs.discard"), failed_job_path(execution), method: :delete,
128
128
  class: "sqd-btn sqd-btn--danger sqd-btn--sm",
129
- data: { confirm: "Discard this job?" } %>
129
+ data: { confirm: t("solid_queue_web.failed_jobs.confirm_discard") } %>
130
130
  </td>
131
131
  </tr>
132
132
  <% end %>
@@ -136,13 +136,13 @@
136
136
  </div>
137
137
  <% else %>
138
138
  <div class="sqd-card">
139
- <div class="sqd-empty">No failed jobs. All clear!</div>
139
+ <div class="sqd-empty"><%= t("solid_queue_web.failed_jobs.empty") %></div>
140
140
  </div>
141
141
  <% end %>
142
142
 
143
143
  <% if @queue.present? %>
144
144
  <p style="margin-top: 0.75rem; font-size: 13px; color: var(--muted);">
145
- Filtering by queue: <strong><%= @queue %></strong> &mdash;
146
- <%= link_to "Clear filter", failed_jobs_path(q: @search, period: @period) %>
145
+ <%= t("solid_queue_web.shared.filtering_by_queue") %> <strong><%= @queue %></strong> &mdash;
146
+ <%= link_to t("solid_queue_web.shared.clear_filter"), failed_jobs_path(q: @search, period: @period) %>
147
147
  </p>
148
- <% end %>
148
+ <% end %>
@@ -1,9 +1,9 @@
1
1
  <%= turbo_frame_tag "history-table", data: { controller: "refresh", refresh_interval_value: SolidQueueWeb.default_refresh_interval } do %>
2
2
  <div class="sqd-page-header">
3
- <h1 class="sqd-page-title">Job History</h1>
3
+ <h1 class="sqd-page-title"><%= t("solid_queue_web.history.title") %></h1>
4
4
  <% if @jobs.any? %>
5
5
  <div class="sqd-actions">
6
- <%= link_to "Export CSV", history_path(format: :csv, queue: @queue, q: @search, period: @period),
6
+ <%= link_to t("solid_queue_web.history.export_csv"), history_path(format: :csv, queue: @queue, q: @search, period: @period),
7
7
  class: "sqd-btn sqd-btn--muted", data: { turbo: false } %>
8
8
  </div>
9
9
  <% end %>
@@ -17,13 +17,13 @@
17
17
  <input type="hidden" name="sort" value="<%= @sort %>">
18
18
  <input type="hidden" name="direction" value="<%= @direction %>">
19
19
  <input class="sqd-search__input" type="search" name="q" value="<%= @search %>"
20
- placeholder="Filter by job class…" autocomplete="off" aria-label="Filter by job class"
20
+ placeholder="<%= t("solid_queue_web.history.placeholder_filter") %>" autocomplete="off" aria-label="<%= t("solid_queue_web.history.aria_filter") %>"
21
21
  data-action="input->search#filter">
22
22
  <% if @search.present? %>
23
- <%= link_to "Clear", history_path(queue: @queue, period: @period), class: "sqd-btn sqd-btn--muted" %>
23
+ <%= link_to t("solid_queue_web.history.clear"), history_path(queue: @queue, period: @period), class: "sqd-btn sqd-btn--muted" %>
24
24
  <% end %>
25
- <div class="sqd-period-filter" role="group" aria-label="Time period" data-controller="filters" data-filters-page-value="history">
26
- <%= link_to "All", history_path(queue: @queue, q: @search), class: @period.nil? ? "active" : "", aria: { current: @period.nil? ? "true" : nil }, data: { action: "click->filters#savePeriod", filters_period_param: "" } %>
25
+ <div class="sqd-period-filter" role="group" aria-label="<%= t("solid_queue_web.shared.period_filter.label") %>" data-controller="filters" data-filters-page-value="history">
26
+ <%= link_to t("solid_queue_web.shared.period_filter.all"), history_path(queue: @queue, q: @search), class: @period.nil? ? "active" : "", aria: { current: @period.nil? ? "true" : nil }, data: { action: "click->filters#savePeriod", filters_period_param: "" } %>
27
27
  <%= link_to "1h", history_path(queue: @queue, q: @search, period: "1h"), class: @period == "1h" ? "active" : "", aria: { current: @period == "1h" ? "true" : nil }, data: { action: "click->filters#savePeriod", filters_period_param: "1h" } %>
28
28
  <%= link_to "24h", history_path(queue: @queue, q: @search, period: "24h"), class: @period == "24h" ? "active" : "", aria: { current: @period == "24h" ? "true" : nil }, data: { action: "click->filters#savePeriod", filters_period_param: "24h" } %>
29
29
  <%= link_to "7d", history_path(queue: @queue, q: @search, period: "7d"), class: @period == "7d" ? "active" : "", aria: { current: @period == "7d" ? "true" : nil }, data: { action: "click->filters#savePeriod", filters_period_param: "7d" } %>
@@ -32,8 +32,8 @@
32
32
 
33
33
  <% if @queue.present? %>
34
34
  <p style="margin-top: 0.5rem; font-size: 13px; color: var(--muted);">
35
- Filtering by queue: <strong><%= @queue %></strong> &mdash;
36
- <%= link_to "Clear filter", history_path(q: @search, period: @period) %>
35
+ <%= t("solid_queue_web.shared.filtering_by_queue") %> <strong><%= @queue %></strong> &mdash;
36
+ <%= link_to t("solid_queue_web.shared.clear_filter"), history_path(q: @search, period: @period) %>
37
37
  </p>
38
38
  <% end %>
39
39
 
@@ -43,10 +43,10 @@
43
43
  <thead>
44
44
  <% sort_url = ->(p) { history_path(queue: @queue, q: @search, period: @period, **p) } %>
45
45
  <tr>
46
- <%= sort_header_th("Job Class", "class_name", sort_url, current_sort: @sort, current_dir: @direction) %>
47
- <%= sort_header_th("Queue", "queue_name", sort_url, current_sort: @sort, current_dir: @direction) %>
48
- <th scope="col">Duration</th>
49
- <%= sort_header_th("Finished At", "finished_at", sort_url, current_sort: @sort, current_dir: @direction) %>
46
+ <%= sort_header_th(t("solid_queue_web.history.col_job_class"), "class_name", sort_url, current_sort: @sort, current_dir: @direction) %>
47
+ <%= sort_header_th(t("solid_queue_web.history.col_queue"), "queue_name", sort_url, current_sort: @sort, current_dir: @direction) %>
48
+ <th scope="col"><%= t("solid_queue_web.history.col_duration") %></th>
49
+ <%= sort_header_th(t("solid_queue_web.history.col_finished_at"), "finished_at", sort_url, current_sort: @sort, current_dir: @direction) %>
50
50
  </tr>
51
51
  </thead>
52
52
  <tbody>
@@ -66,11 +66,11 @@
66
66
  </div>
67
67
  <% else %>
68
68
  <div class="sqd-card">
69
- <div class="sqd-empty">No finished jobs found.</div>
69
+ <div class="sqd-empty"><%= t("solid_queue_web.history.empty") %></div>
70
70
  </div>
71
71
  <% end %>
72
72
 
73
73
  <% if @pagy.last > 1 %>
74
74
  <%= @pagy.series_nav.html_safe %>
75
75
  <% end %>
76
- <% end %>
76
+ <% end %>
@@ -1,4 +1,4 @@
1
- <h1 class="sqd-page-title" style="margin-bottom: 1.5rem;">Jobs</h1>
1
+ <h1 class="sqd-page-title" style="margin-bottom: 1.5rem;"><%= t("solid_queue_web.jobs.title") %></h1>
2
2
 
3
3
  <%= turbo_frame_tag "jobs-table", data: { turbo_action: "advance", controller: "refresh", refresh_interval_value: SolidQueueWeb.default_refresh_interval } do %>
4
4
  <% discardable = SolidQueueWeb::Job::DISCARDABLE.include?(@status) %>
@@ -6,29 +6,29 @@
6
6
  <div data-controller="filters" data-filters-page-value="jobs">
7
7
  <div class="sqd-page-header">
8
8
  <div class="sqd-filters">
9
- <%= link_to "Ready", jobs_path(status: "ready", q: @search, period: @period, priority: @priority, sort: @sort, direction: @direction), class: @status == "ready" ? "active" : "", data: { action: "click->filters#saveStatus", filters_status_param: "ready" } %>
10
- <%= link_to "Scheduled", jobs_path(status: "scheduled", q: @search, period: @period, priority: @priority, sort: @sort, direction: @direction), class: @status == "scheduled" ? "active" : "", data: { action: "click->filters#saveStatus", filters_status_param: "scheduled" } %>
11
- <%= link_to "Running", jobs_path(status: "claimed", q: @search, period: @period, priority: @priority, sort: @sort, direction: @direction), class: @status == "claimed" ? "active" : "", data: { action: "click->filters#saveStatus", filters_status_param: "claimed" } %>
12
- <%= link_to "Blocked", jobs_path(status: "blocked", q: @search, period: @period, priority: @priority, sort: @sort, direction: @direction), class: @status == "blocked" ? "active" : "", data: { action: "click->filters#saveStatus", filters_status_param: "blocked" } %>
13
- <%= link_to "Failed", jobs_path(status: "failed", q: @search, period: @period, priority: @priority, sort: @sort, direction: @direction), class: @status == "failed" ? "active" : "", data: { action: "click->filters#saveStatus", filters_status_param: "failed" } %>
9
+ <%= link_to t("solid_queue_web.jobs.tab_ready"), jobs_path(status: "ready", q: @search, period: @period, priority: @priority, sort: @sort, direction: @direction), class: @status == "ready" ? "active" : "", data: { action: "click->filters#saveStatus", filters_status_param: "ready" } %>
10
+ <%= link_to t("solid_queue_web.jobs.tab_scheduled"), jobs_path(status: "scheduled", q: @search, period: @period, priority: @priority, sort: @sort, direction: @direction), class: @status == "scheduled" ? "active" : "", data: { action: "click->filters#saveStatus", filters_status_param: "scheduled" } %>
11
+ <%= link_to t("solid_queue_web.jobs.tab_running"), jobs_path(status: "claimed", q: @search, period: @period, priority: @priority, sort: @sort, direction: @direction), class: @status == "claimed" ? "active" : "", data: { action: "click->filters#saveStatus", filters_status_param: "claimed" } %>
12
+ <%= link_to t("solid_queue_web.jobs.tab_blocked"), jobs_path(status: "blocked", q: @search, period: @period, priority: @priority, sort: @sort, direction: @direction), class: @status == "blocked" ? "active" : "", data: { action: "click->filters#saveStatus", filters_status_param: "blocked" } %>
13
+ <%= link_to t("solid_queue_web.jobs.tab_failed"), jobs_path(status: "failed", q: @search, period: @period, priority: @priority, sort: @sort, direction: @direction), class: @status == "failed" ? "active" : "", data: { action: "click->filters#saveStatus", filters_status_param: "failed" } %>
14
14
  </div>
15
15
  <% if @jobs.any? %>
16
16
  <div class="sqd-actions">
17
- <%= link_to "Export CSV", jobs_path(format: :csv, status: @status, q: @search, period: @period, sort: @sort, direction: @direction),
17
+ <%= link_to t("solid_queue_web.jobs.export_csv"), jobs_path(format: :csv, status: @status, q: @search, period: @period, sort: @sort, direction: @direction),
18
18
  class: "sqd-btn sqd-btn--muted", data: { turbo: false } %>
19
19
  <% if @status == "scheduled" %>
20
- <%= button_to "Run All Now", run_all_now_scheduled_jobs_path,
20
+ <%= button_to t("solid_queue_web.jobs.run_all_now"), run_all_now_scheduled_jobs_path,
21
21
  method: :post,
22
22
  params: { period: @period },
23
23
  class: "sqd-btn sqd-btn--primary",
24
- data: { confirm: "Run all #{@pagy.count} scheduled jobs immediately?" } %>
24
+ data: { confirm: t("solid_queue_web.jobs.confirm_run_all_now", count: @pagy.count) } %>
25
25
  <% end %>
26
26
  <% if discardable %>
27
- <%= button_to "Discard All", discard_all_jobs_path,
27
+ <%= button_to t("solid_queue_web.jobs.discard_all"), discard_all_jobs_path,
28
28
  method: :post,
29
29
  params: { status: @status, period: @period },
30
30
  class: "sqd-btn sqd-btn--danger",
31
- data: { confirm: "Discard all #{@pagy.count} #{@status} jobs? This cannot be undone." } %>
31
+ data: { confirm: t("solid_queue_web.jobs.confirm_discard_all", count: @pagy.count, status: @status) } %>
32
32
  <% end %>
33
33
  </div>
34
34
  <% end %>
@@ -40,22 +40,22 @@
40
40
  <input type="hidden" name="sort" value="<%= @sort %>">
41
41
  <input type="hidden" name="direction" value="<%= @direction %>">
42
42
  <input class="sqd-search__input" type="search" name="q" value="<%= @search %>"
43
- placeholder="Filter by job class…" autocomplete="off" aria-label="Filter by job class"
43
+ placeholder="<%= t("solid_queue_web.jobs.placeholder_job_class") %>" autocomplete="off" aria-label="<%= t("solid_queue_web.jobs.aria_filter_job_class") %>"
44
44
  data-action="input->search#filter">
45
45
  <% if @priority_options.size > 1 %>
46
- <select name="priority" class="sqd-select" aria-label="Filter by priority"
46
+ <select name="priority" class="sqd-select" aria-label="<%= t("solid_queue_web.jobs.aria_filter_priority") %>"
47
47
  onchange="this.form.submit()">
48
- <option value="" <%= @priority.nil? ? "selected" : "" %>>All priorities</option>
48
+ <option value="" <%= @priority.nil? ? "selected" : "" %>><%= t("solid_queue_web.jobs.all_priorities") %></option>
49
49
  <% @priority_options.each do |p| %>
50
- <option value="<%= p %>" <%= @priority.to_s == p.to_s ? "selected" : "" %>>Priority <%= p %></option>
50
+ <option value="<%= p %>" <%= @priority.to_s == p.to_s ? "selected" : "" %>><%= t("solid_queue_web.jobs.priority_option", n: p) %></option>
51
51
  <% end %>
52
52
  </select>
53
53
  <% end %>
54
54
  <% if @search.present? || @priority.present? %>
55
- <%= link_to "Clear", jobs_path(status: @status, period: @period, sort: @sort, direction: @direction), class: "sqd-btn sqd-btn--muted" %>
55
+ <%= link_to t("solid_queue_web.jobs.clear_search"), jobs_path(status: @status, period: @period, sort: @sort, direction: @direction), class: "sqd-btn sqd-btn--muted" %>
56
56
  <% end %>
57
- <div class="sqd-period-filter" role="group" aria-label="Time period">
58
- <%= link_to "All", jobs_path(status: @status, q: @search, priority: @priority, sort: @sort, direction: @direction), class: @period.nil? ? "active" : "", aria: { current: @period.nil? ? "true" : nil }, aria_label: "All time", data: { action: "click->filters#savePeriod", filters_period_param: "" } %>
57
+ <div class="sqd-period-filter" role="group" aria-label="<%= t("solid_queue_web.shared.period_filter.label") %>">
58
+ <%= link_to t("solid_queue_web.shared.period_filter.all"), jobs_path(status: @status, q: @search, priority: @priority, sort: @sort, direction: @direction), class: @period.nil? ? "active" : "", aria: { current: @period.nil? ? "true" : nil }, aria_label: "All time", data: { action: "click->filters#savePeriod", filters_period_param: "" } %>
59
59
  <%= link_to "1h", jobs_path(status: @status, q: @search, priority: @priority, period: "1h", sort: @sort, direction: @direction), class: @period == "1h" ? "active" : "", aria: { current: @period == "1h" ? "true" : nil }, aria_label: "Last 1 hour", data: { action: "click->filters#savePeriod", filters_period_param: "1h" } %>
60
60
  <%= link_to "24h", jobs_path(status: @status, q: @search, priority: @priority, period: "24h", sort: @sort, direction: @direction), class: @period == "24h" ? "active" : "", aria: { current: @period == "24h" ? "true" : nil }, aria_label: "Last 24 hours", data: { action: "click->filters#savePeriod", filters_period_param: "24h" } %>
61
61
  <%= link_to "7d", jobs_path(status: @status, q: @search, priority: @priority, period: "7d", sort: @sort, direction: @direction), class: @period == "7d" ? "active" : "", aria: { current: @period == "7d" ? "true" : nil }, aria_label: "Last 7 days", data: { action: "click->filters#savePeriod", filters_period_param: "7d" } %>
@@ -66,7 +66,7 @@
66
66
  <% if discardable && @jobs.any? %>
67
67
  <div data-controller="selection">
68
68
  <%= form_tag job_selection_path, method: :delete, id: "job-selection-form",
69
- data: { turbo_confirm: "Discard selected jobs? This cannot be undone." } do %>
69
+ data: { turbo_confirm: t("solid_queue_web.jobs.confirm_discard_selected") } do %>
70
70
  <%= hidden_field_tag :status, @status %>
71
71
  <%= hidden_field_tag :period, @period %>
72
72
  <%= hidden_field_tag :sort, @sort %>
@@ -74,10 +74,10 @@
74
74
  <% end %>
75
75
 
76
76
  <div class="sqd-selection-bar" data-selection-target="bar" style="display: none;">
77
- <span class="sqd-muted-text"><span data-selection-target="count">0</span> selected</span>
77
+ <span class="sqd-muted-text"><span data-selection-target="count">0</span> <%= t("solid_queue_web.jobs.selected_label") %></span>
78
78
  <button type="button" class="sqd-btn sqd-btn--danger sqd-btn--sm"
79
79
  data-action="click->selection#submit"
80
- data-selection-form-id-param="job-selection-form">Discard Selected</button>
80
+ data-selection-form-id-param="job-selection-form"><%= t("solid_queue_web.jobs.discard_selected") %></button>
81
81
  </div>
82
82
 
83
83
  <div class="sqd-card" id="jobs-list">
@@ -87,15 +87,15 @@
87
87
  <th scope="col">
88
88
  <input type="checkbox" data-selection-target="selectAll"
89
89
  data-action="change->selection#selectAll"
90
- aria-label="Select all jobs">
90
+ aria-label="<%= t("solid_queue_web.jobs.select_all_jobs") %>">
91
91
  </th>
92
92
  <% sort_url = ->(p) { jobs_path(status: @status, q: @search, period: @period, priority: @priority, **p) } %>
93
- <%= sort_header_th("Job Class", "class_name", sort_url, current_sort: @sort, current_dir: @direction) %>
94
- <%= sort_header_th("Queue", "queue_name", sort_url, current_sort: @sort, current_dir: @direction) %>
95
- <%= sort_header_th("Priority", "priority", sort_url, current_sort: @sort, current_dir: @direction) %>
96
- <th scope="col">Scheduled At</th>
97
- <%= sort_header_th("Enqueued At", "created_at", sort_url, current_sort: @sort, current_dir: @direction) %>
98
- <th scope="col"><span class="sqd-sr-only">Actions</span></th>
93
+ <%= sort_header_th(t("solid_queue_web.jobs.col_job_class"), "class_name", sort_url, current_sort: @sort, current_dir: @direction) %>
94
+ <%= sort_header_th(t("solid_queue_web.jobs.col_queue"), "queue_name", sort_url, current_sort: @sort, current_dir: @direction) %>
95
+ <%= sort_header_th(t("solid_queue_web.jobs.col_priority"), "priority", sort_url, current_sort: @sort, current_dir: @direction) %>
96
+ <th scope="col"><%= t("solid_queue_web.jobs.col_scheduled_at") %></th>
97
+ <%= sort_header_th(t("solid_queue_web.jobs.col_enqueued_at"), "created_at", sort_url, current_sort: @sort, current_dir: @direction) %>
98
+ <th scope="col"><span class="sqd-sr-only"><%= t("solid_queue_web.jobs.actions") %></span></th>
99
99
  </tr>
100
100
  </thead>
101
101
  <tbody>
@@ -106,7 +106,7 @@
106
106
  <input type="checkbox" value="<%= execution.id %>"
107
107
  data-selection-target="checkbox"
108
108
  data-action="change->selection#toggle"
109
- aria-label="Select job <%= job.class_name %>">
109
+ aria-label="<%= t("solid_queue_web.jobs.select_job", class_name: job.class_name) %>">
110
110
  </td>
111
111
  <td>
112
112
  <span class="sqd-badge sqd-badge--<%= @status %>"><%= @status %></span>
@@ -123,11 +123,11 @@
123
123
  <td class="sqd-mono"><%= format_timestamp(job.created_at) %></td>
124
124
  <td class="sqd-row-actions">
125
125
  <% if @status == "scheduled" %>
126
- <%= button_to "Run Now", scheduled_job_path(execution),
126
+ <%= button_to t("solid_queue_web.jobs.run_now"), scheduled_job_path(execution),
127
127
  method: :patch,
128
128
  params: { offset: "now", period: @period },
129
129
  class: "sqd-btn sqd-btn--primary sqd-btn--sm",
130
- data: { confirm: "Run this job immediately?" } %>
130
+ data: { confirm: t("solid_queue_web.jobs.confirm_run_now") } %>
131
131
  <% %w[1h 24h 7d].each do |offset| %>
132
132
  <%= button_to "+#{offset}", scheduled_job_path(execution),
133
133
  method: :patch,
@@ -135,11 +135,11 @@
135
135
  class: "sqd-btn sqd-btn--muted sqd-btn--sm" %>
136
136
  <% end %>
137
137
  <% end %>
138
- <%= button_to "Discard", job_path(execution),
138
+ <%= button_to t("solid_queue_web.jobs.discard"), job_path(execution),
139
139
  method: :delete,
140
140
  params: { status: @status, period: @period, sort: @sort, direction: @direction },
141
141
  class: "sqd-btn sqd-btn--danger sqd-btn--sm",
142
- data: { confirm: "Discard this job?" } %>
142
+ data: { confirm: t("solid_queue_web.jobs.confirm_discard") } %>
143
143
  </td>
144
144
  </tr>
145
145
  <% end %>
@@ -150,21 +150,21 @@
150
150
  <% else %>
151
151
  <div class="sqd-card" id="jobs-list">
152
152
  <% if @jobs.empty? %>
153
- <div class="sqd-empty">No <%= @status %> jobs.</div>
153
+ <div class="sqd-empty"><%= t("solid_queue_web.jobs.no_jobs", status: @status) %></div>
154
154
  <% else %>
155
155
  <% slow_threshold = @status == "claimed" ? SolidQueueWeb.slow_job_threshold : nil %>
156
156
  <table>
157
157
  <thead>
158
158
  <tr>
159
159
  <% sort_url = ->(p) { jobs_path(status: @status, q: @search, period: @period, priority: @priority, **p) } %>
160
- <%= sort_header_th("Job Class", "class_name", sort_url, current_sort: @sort, current_dir: @direction) %>
161
- <%= sort_header_th("Queue", "queue_name", sort_url, current_sort: @sort, current_dir: @direction) %>
162
- <%= sort_header_th("Priority", "priority", sort_url, current_sort: @sort, current_dir: @direction) %>
163
- <th scope="col">Scheduled At</th>
164
- <%= sort_header_th("Enqueued At", "created_at", sort_url, current_sort: @sort, current_dir: @direction) %>
160
+ <%= sort_header_th(t("solid_queue_web.jobs.col_job_class"), "class_name", sort_url, current_sort: @sort, current_dir: @direction) %>
161
+ <%= sort_header_th(t("solid_queue_web.jobs.col_queue"), "queue_name", sort_url, current_sort: @sort, current_dir: @direction) %>
162
+ <%= sort_header_th(t("solid_queue_web.jobs.col_priority"), "priority", sort_url, current_sort: @sort, current_dir: @direction) %>
163
+ <th scope="col"><%= t("solid_queue_web.jobs.col_scheduled_at") %></th>
164
+ <%= sort_header_th(t("solid_queue_web.jobs.col_enqueued_at"), "created_at", sort_url, current_sort: @sort, current_dir: @direction) %>
165
165
  <% if @status == "claimed" %>
166
- <th scope="col">Running For</th>
167
- <th scope="col">Wait Time</th>
166
+ <th scope="col"><%= t("solid_queue_web.jobs.col_running_for") %></th>
167
+ <th scope="col"><%= t("solid_queue_web.jobs.col_wait_time") %></th>
168
168
  <% end %>
169
169
  </tr>
170
170
  </thead>
@@ -208,4 +208,4 @@
208
208
  <% if @pagy.last > 1 %>
209
209
  <%= @pagy.series_nav.html_safe %>
210
210
  <% end %>
211
- <% end %>
211
+ <% end %>
@@ -1,26 +1,26 @@
1
1
  <div class="sqd-page-header">
2
2
  <div>
3
3
  <div class="sqd-breadcrumb">
4
- <%= link_to "Jobs", jobs_path(status: @execution_status) %> &rsaquo; Detail
4
+ <%= link_to t("solid_queue_web.jobs_show.breadcrumb"), jobs_path(status: @execution_status) %> &rsaquo; <%= t("solid_queue_web.jobs_show.detail") %>
5
5
  </div>
6
6
  <h1 class="sqd-page-title"><%= @job.class_name %></h1>
7
7
  </div>
8
8
 
9
9
  <div class="sqd-actions">
10
10
  <% if @execution_status == "failed" && @job.failed_execution %>
11
- <%= button_to "Retry", retry_failed_job_path(@job.failed_execution), method: :post,
11
+ <%= button_to t("solid_queue_web.jobs_show.retry"), retry_failed_job_path(@job.failed_execution), method: :post,
12
12
  class: "sqd-btn sqd-btn--primary" %>
13
- <%= button_to "Discard", failed_job_path(@job.failed_execution), method: :delete,
13
+ <%= button_to t("solid_queue_web.jobs_show.discard"), failed_job_path(@job.failed_execution), method: :delete,
14
14
  class: "sqd-btn sqd-btn--danger",
15
- data: { confirm: "Discard this job?" } %>
15
+ data: { confirm: t("solid_queue_web.jobs_show.confirm_discard") } %>
16
16
  <% elsif SolidQueueWeb::Job::DISCARDABLE.include?(@execution_status) %>
17
17
  <% execution = @job.public_send("#{@execution_status}_execution") %>
18
18
  <% if execution %>
19
- <%= button_to "Discard", job_path(execution),
19
+ <%= button_to t("solid_queue_web.jobs_show.discard"), job_path(execution),
20
20
  method: :delete,
21
21
  params: { status: @execution_status },
22
22
  class: "sqd-btn sqd-btn--danger",
23
- data: { confirm: "Discard this job?" } %>
23
+ data: { confirm: t("solid_queue_web.jobs_show.confirm_discard") } %>
24
24
  <% end %>
25
25
  <% end %>
26
26
  </div>
@@ -28,41 +28,41 @@
28
28
 
29
29
  <div class="sqd-detail-grid">
30
30
  <div class="sqd-card sqd-detail-section">
31
- <h2 class="sqd-section-title">Details</h2>
31
+ <h2 class="sqd-section-title"><%= t("solid_queue_web.jobs_show.section_details") %></h2>
32
32
  <dl class="sqd-dl">
33
- <dt>Status</dt>
33
+ <dt><%= t("solid_queue_web.jobs_show.field_status") %></dt>
34
34
  <dd><span class="sqd-badge sqd-badge--<%= @execution_status %>"><%= @execution_status %></span></dd>
35
35
 
36
- <dt>Queue</dt>
36
+ <dt><%= t("solid_queue_web.jobs_show.field_queue") %></dt>
37
37
  <dd class="sqd-mono"><%= @job.queue_name %></dd>
38
38
 
39
- <dt>Priority</dt>
39
+ <dt><%= t("solid_queue_web.jobs_show.field_priority") %></dt>
40
40
  <dd><%= @job.priority %></dd>
41
41
 
42
- <dt>Active Job ID</dt>
42
+ <dt><%= t("solid_queue_web.jobs_show.field_active_job_id") %></dt>
43
43
  <dd class="sqd-mono sqd-truncate" title="<%= @job.active_job_id %>"><%= @job.active_job_id %></dd>
44
44
 
45
- <dt>Concurrency Key</dt>
45
+ <dt><%= t("solid_queue_web.jobs_show.field_concurrency_key") %></dt>
46
46
  <dd class="sqd-mono"><%= @job.concurrency_key.presence || "—" %></dd>
47
47
 
48
48
  <% if @job.blocked_execution %>
49
- <dt>Blocked Until</dt>
49
+ <dt><%= t("solid_queue_web.jobs_show.field_blocked_until") %></dt>
50
50
  <dd class="sqd-mono"><%= @job.blocked_execution.expires_at ? @job.blocked_execution.expires_at.strftime("%Y-%m-%d %H:%M:%S %Z") : "—" %></dd>
51
51
  <% end %>
52
52
 
53
- <dt>Enqueued At</dt>
53
+ <dt><%= t("solid_queue_web.jobs_show.field_enqueued_at") %></dt>
54
54
  <dd class="sqd-mono"><%= @job.created_at.strftime("%Y-%m-%d %H:%M:%S %Z") %></dd>
55
55
 
56
- <dt>Scheduled At</dt>
56
+ <dt><%= t("solid_queue_web.jobs_show.field_scheduled_at") %></dt>
57
57
  <dd class="sqd-mono"><%= @job.scheduled_at ? @job.scheduled_at.strftime("%Y-%m-%d %H:%M:%S %Z") : "—" %></dd>
58
58
 
59
- <dt>Finished At</dt>
59
+ <dt><%= t("solid_queue_web.jobs_show.field_finished_at") %></dt>
60
60
  <dd class="sqd-mono"><%= @job.finished_at ? @job.finished_at.strftime("%Y-%m-%d %H:%M:%S %Z") : "—" %></dd>
61
61
  </dl>
62
62
  </div>
63
63
 
64
64
  <div class="sqd-card sqd-detail-section">
65
- <h2 class="sqd-section-title">Arguments</h2>
65
+ <h2 class="sqd-section-title"><%= t("solid_queue_web.jobs_show.section_arguments") %></h2>
66
66
  <% if @execution_status == "failed" && @job.failed_execution %>
67
67
  <% args_json = begin; JSON.pretty_generate(@job.arguments); rescue; @job.arguments.inspect; end %>
68
68
  <%= form_with url: failed_job_arguments_path(@job.failed_execution), method: :patch do |f| %>
@@ -70,8 +70,8 @@
70
70
  value: args_json,
71
71
  class: "sqd-textarea sqd-mono",
72
72
  rows: [args_json.lines.count + 1, 6].max,
73
- "aria-label": "Job arguments JSON" %>
74
- <%= f.submit "Retry with these arguments", class: "sqd-btn sqd-btn--primary sqd-args-form__submit" %>
73
+ "aria-label": t("solid_queue_web.jobs_show.arguments_aria_label") %>
74
+ <%= f.submit t("solid_queue_web.jobs_show.retry_with_args"), class: "sqd-btn sqd-btn--primary sqd-args-form__submit" %>
75
75
  <% end %>
76
76
  <% else %>
77
77
  <pre class="sqd-pre"><%= JSON.pretty_generate(@job.arguments) rescue @job.arguments.inspect %></pre>
@@ -81,7 +81,7 @@
81
81
 
82
82
  <% if @job.failed_execution %>
83
83
  <div class="sqd-card sqd-detail-section" style="margin-top: 1.5rem;">
84
- <h2 class="sqd-section-title sqd-section-title--danger">Error</h2>
84
+ <h2 class="sqd-section-title sqd-section-title--danger"><%= t("solid_queue_web.jobs_show.section_error") %></h2>
85
85
  <p class="sqd-error-header">
86
86
  <strong><%= @job.failed_execution.exception_class %></strong>: <%= @job.failed_execution.message %>
87
87
  </p>
@@ -1,10 +1,10 @@
1
1
  <div class="sqd-page-header">
2
- <h1 class="sqd-page-title">Performance</h1>
2
+ <h1 class="sqd-page-title"><%= t("solid_queue_web.performance.title") %></h1>
3
3
  </div>
4
4
 
5
5
  <form class="sqd-search" action="<%= performance_path %>" method="get">
6
- <div class="sqd-period-filter" role="group" aria-label="Time period">
7
- <%= link_to "All", performance_path, class: @period.nil? ? "active" : "", aria: { current: @period.nil? ? "true" : nil } %>
6
+ <div class="sqd-period-filter" role="group" aria-label="<%= t("solid_queue_web.shared.period_filter.label") %>">
7
+ <%= link_to t("solid_queue_web.shared.period_filter.all"), performance_path, class: @period.nil? ? "active" : "", aria: { current: @period.nil? ? "true" : nil } %>
8
8
  <%= link_to "1h", performance_path(period: "1h"), class: @period == "1h" ? "active" : "", aria: { current: @period == "1h" ? "true" : nil } %>
9
9
  <%= link_to "24h", performance_path(period: "24h"), class: @period == "24h" ? "active" : "", aria: { current: @period == "24h" ? "true" : nil } %>
10
10
  <%= link_to "7d", performance_path(period: "7d"), class: @period == "7d" ? "active" : "", aria: { current: @period == "7d" ? "true" : nil } %>
@@ -16,15 +16,15 @@
16
16
  <table>
17
17
  <thead>
18
18
  <tr>
19
- <th scope="col">Job Class</th>
20
- <th scope="col" style="text-align: right;">Runs</th>
21
- <th scope="col" style="text-align: right;">Avg</th>
22
- <th scope="col" style="text-align: right;">p50</th>
23
- <th scope="col" style="text-align: right;">p95</th>
24
- <th scope="col" style="text-align: right;">p99</th>
25
- <th scope="col" style="text-align: right;">Std Dev</th>
26
- <th scope="col" style="text-align: right;">Min</th>
27
- <th scope="col" style="text-align: right;">Max</th>
19
+ <th scope="col"><%= t("solid_queue_web.performance.col_job_class") %></th>
20
+ <th scope="col" style="text-align: right;"><%= t("solid_queue_web.performance.col_runs") %></th>
21
+ <th scope="col" style="text-align: right;"><%= t("solid_queue_web.performance.col_avg") %></th>
22
+ <th scope="col" style="text-align: right;"><%= t("solid_queue_web.performance.col_p50") %></th>
23
+ <th scope="col" style="text-align: right;"><%= t("solid_queue_web.performance.col_p95") %></th>
24
+ <th scope="col" style="text-align: right;"><%= t("solid_queue_web.performance.col_p99") %></th>
25
+ <th scope="col" style="text-align: right;"><%= t("solid_queue_web.performance.col_std_dev") %></th>
26
+ <th scope="col" style="text-align: right;"><%= t("solid_queue_web.performance.col_min") %></th>
27
+ <th scope="col" style="text-align: right;"><%= t("solid_queue_web.performance.col_max") %></th>
28
28
  </tr>
29
29
  </thead>
30
30
  <tbody>
@@ -49,6 +49,8 @@
49
49
  </div>
50
50
  <% else %>
51
51
  <div class="sqd-card" style="margin-top: 1rem;">
52
- <div class="sqd-empty">No finished jobs found<%= " in the last #{@period}" if @period %>.</div>
52
+ <div class="sqd-empty">
53
+ <%= @period ? t("solid_queue_web.performance.empty_with_period", period: @period) : t("solid_queue_web.performance.empty") %>
54
+ </div>
53
55
  </div>
54
- <% end %>
56
+ <% end %>