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.
- checksums.yaml +4 -4
- data/README.md +220 -4
- data/app/assets/stylesheets/solid_queue_web/_04_table.css +28 -0
- data/app/controllers/solid_queue_web/application_controller.rb +30 -0
- data/app/controllers/solid_queue_web/audit_controller.rb +43 -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 +6 -4
- data/app/controllers/solid_queue_web/failed_jobs_controller.rb +4 -2
- data/app/controllers/solid_queue_web/jobs/selections_controller.rb +4 -3
- data/app/controllers/solid_queue_web/jobs_controller.rb +7 -3
- data/app/controllers/solid_queue_web/queues/jobs_controller.rb +5 -5
- data/app/controllers/solid_queue_web/queues/pauses_controller.rb +6 -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 +6 -5
- data/app/controllers/solid_queue_web/scheduled_jobs_controller.rb +5 -5
- data/app/models/solid_queue_web/audit_event.rb +17 -0
- data/app/views/layouts/solid_queue_web/application.html.erb +20 -16
- data/app/views/solid_queue_web/audit/index.html.erb +78 -0
- 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/config/routes.rb +1 -0
- data/db/migrate/01_create_solid_queue_web_audit_events.rb +16 -0
- data/lib/generators/solid_queue_web/install/migrations_generator.rb +24 -0
- data/lib/generators/solid_queue_web/install/templates/create_solid_queue_web_audit_events.rb.tt +16 -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 +18 -1
- metadata +8 -1
|
@@ -4,17 +4,19 @@ module SolidQueueWeb
|
|
|
4
4
|
def create
|
|
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: t("solid_queue_web.flash.queue_paused", name: queue.name)
|
|
8
9
|
rescue => e
|
|
9
|
-
redirect_to queues_path, alert: "
|
|
10
|
+
redirect_to queues_path, alert: t("solid_queue_web.flash.cannot_pause_queue", error: e.message)
|
|
10
11
|
end
|
|
11
12
|
|
|
12
13
|
def destroy
|
|
13
14
|
queue = SolidQueue::Queue.find_by_name(params[:queue_name])
|
|
14
15
|
queue.resume
|
|
15
|
-
|
|
16
|
+
record_audit("queue_resumed", queue_name: queue.name)
|
|
17
|
+
redirect_to queues_path, notice: t("solid_queue_web.flash.queue_resumed", name: queue.name)
|
|
16
18
|
rescue => e
|
|
17
|
-
redirect_to queues_path, alert: "
|
|
19
|
+
redirect_to queues_path, alert: t("solid_queue_web.flash.cannot_resume_queue", error: e.message)
|
|
18
20
|
end
|
|
19
21
|
end
|
|
20
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
|
|
@@ -16,22 +16,23 @@ module SolidQueueWeb
|
|
|
16
16
|
else
|
|
17
17
|
SolidQueue::FailedExecution.retry_all(jobs)
|
|
18
18
|
end
|
|
19
|
+
action = params[:id] ? "failed_job_retried" : "failed_jobs_retried"
|
|
20
|
+
record_audit(action, job_class: jobs.first&.class_name, queue_name: jobs.first&.queue_name, item_count: jobs.size)
|
|
19
21
|
redirect_to failed_jobs_path(queue: @queue, q: @search, period: @period),
|
|
20
22
|
notice: retry_notice(jobs.size)
|
|
21
23
|
rescue ArgumentError => e
|
|
22
24
|
redirect_to failed_jobs_path, alert: e.message
|
|
23
25
|
rescue => e
|
|
24
|
-
redirect_to failed_jobs_path, alert: "
|
|
26
|
+
redirect_to failed_jobs_path, alert: t("solid_queue_web.flash.cannot_retry_job", error: e.message)
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
private
|
|
28
30
|
|
|
29
31
|
def retry_notice(count)
|
|
30
|
-
label = "#{count} #{"job".pluralize(count)}"
|
|
31
32
|
if params[:stagger].present? && count > 1
|
|
32
|
-
"
|
|
33
|
+
t("solid_queue_web.flash.jobs_retried_staggered", count: count, stagger: params[:stagger])
|
|
33
34
|
else
|
|
34
|
-
"
|
|
35
|
+
t("solid_queue_web.flash.jobs_retried", count: count)
|
|
35
36
|
end
|
|
36
37
|
end
|
|
37
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
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module SolidQueueWeb
|
|
2
|
+
class AuditEvent < ApplicationRecord
|
|
3
|
+
self.table_name = "solid_queue_web_audit_events"
|
|
4
|
+
|
|
5
|
+
ACTIONS = %w[
|
|
6
|
+
job_discarded jobs_discarded
|
|
7
|
+
failed_job_retried failed_jobs_retried
|
|
8
|
+
failed_job_discarded failed_jobs_discarded
|
|
9
|
+
queue_paused queue_resumed
|
|
10
|
+
].freeze
|
|
11
|
+
|
|
12
|
+
validates :action, presence: true, inclusion: { in: ACTIONS }
|
|
13
|
+
validates :item_count, numericality: { greater_than: 0 }
|
|
14
|
+
|
|
15
|
+
scope :recent, -> { order(created_at: :desc) }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html lang="
|
|
2
|
+
<html lang="<%= I18n.locale %>">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<title
|
|
6
|
+
<title><%= t("solid_queue_web.layout.title") %></title>
|
|
7
7
|
<%= csrf_meta_tags %>
|
|
8
8
|
<%= csp_meta_tag %>
|
|
9
9
|
<%= inline_styles %>
|
|
@@ -13,26 +13,30 @@
|
|
|
13
13
|
|
|
14
14
|
<header class="sqd-header">
|
|
15
15
|
<div class="sqd-header__inner">
|
|
16
|
-
<%= link_to "
|
|
16
|
+
<%= link_to t("solid_queue_web.layout.logo"), root_path, class: "sqd-header__title" %>
|
|
17
17
|
<div class="sqd-nav-wrapper">
|
|
18
|
-
<nav aria-label="
|
|
18
|
+
<nav aria-label="<%= t("solid_queue_web.layout.nav.main_navigation") %>">
|
|
19
19
|
<ul class="sqd-nav">
|
|
20
|
-
<li><%= link_to "
|
|
21
|
-
<li><%= link_to "
|
|
22
|
-
<li><%= link_to "
|
|
23
|
-
<li><%= link_to "
|
|
24
|
-
<li><%= link_to "
|
|
25
|
-
<li><%= link_to "
|
|
26
|
-
<li><%= link_to "
|
|
27
|
-
<li><%= link_to "
|
|
28
|
-
<li><%= link_to "
|
|
20
|
+
<li><%= link_to t("solid_queue_web.layout.nav.dashboard"), root_path, class: current_page?(root_path) ? "active" : "", aria: { current: current_page?(root_path) ? "page" : nil } %></li>
|
|
21
|
+
<li><%= link_to t("solid_queue_web.layout.nav.queues"), queues_path, class: current_page?(queues_path) ? "active" : "", aria: { current: current_page?(queues_path) ? "page" : nil } %></li>
|
|
22
|
+
<li><%= link_to t("solid_queue_web.layout.nav.jobs"), jobs_path, class: current_page?(jobs_path) ? "active" : "", aria: { current: current_page?(jobs_path) ? "page" : nil } %></li>
|
|
23
|
+
<li><%= link_to t("solid_queue_web.layout.nav.history"), history_path, class: current_page?(history_path) ? "active" : "", aria: { current: current_page?(history_path) ? "page" : nil } %></li>
|
|
24
|
+
<li><%= link_to t("solid_queue_web.layout.nav.performance"), performance_path, class: current_page?(performance_path) ? "active" : "", aria: { current: current_page?(performance_path) ? "page" : nil } %></li>
|
|
25
|
+
<li><%= link_to t("solid_queue_web.layout.nav.failed"), failed_jobs_path, class: current_page?(failed_jobs_path) ? "active" : "", aria: { current: current_page?(failed_jobs_path) ? "page" : nil } %></li>
|
|
26
|
+
<li><%= link_to t("solid_queue_web.layout.nav.recurring"), recurring_tasks_path, class: current_page?(recurring_tasks_path) ? "active" : "", aria: { current: current_page?(recurring_tasks_path) ? "page" : nil } %></li>
|
|
27
|
+
<li><%= link_to t("solid_queue_web.layout.nav.processes"), processes_path, class: current_page?(processes_path) ? "active" : "", aria: { current: current_page?(processes_path) ? "page" : nil } %></li>
|
|
28
|
+
<li><%= link_to t("solid_queue_web.layout.nav.search"), search_path, class: current_page?(search_path) ? "active" : "", aria: { current: current_page?(search_path) ? "page" : nil } %></li>
|
|
29
|
+
<li><%= link_to t("solid_queue_web.layout.nav.audit"), audit_path, class: current_page?(audit_path) ? "active" : "", aria: { current: current_page?(audit_path) ? "page" : nil } %></li>
|
|
30
|
+
<% SolidQueueWeb.nav_links.each do |link| %>
|
|
31
|
+
<li><%= link_to link[:label], link[:url] %></li>
|
|
32
|
+
<% end %>
|
|
29
33
|
</ul>
|
|
30
34
|
</nav>
|
|
31
35
|
</div>
|
|
32
36
|
<div class="sqd-header__controls">
|
|
33
|
-
<button class="sqd-theme-toggle" aria-label="
|
|
37
|
+
<button class="sqd-theme-toggle" aria-label="<%= t("solid_queue_web.layout.theme_toggle") %>"
|
|
34
38
|
data-theme-target="toggle" data-action="theme#toggle">☽</button>
|
|
35
|
-
<button class="sqd-nav-toggle" aria-label="
|
|
39
|
+
<button class="sqd-nav-toggle" aria-label="<%= t("solid_queue_web.layout.nav.toggle") %>" aria-expanded="false"
|
|
36
40
|
onclick="var open=document.querySelector('.sqd-nav-wrapper').classList.toggle('sqd-nav--open');this.setAttribute('aria-expanded',open)">
|
|
37
41
|
<span></span>
|
|
38
42
|
<span></span>
|
|
@@ -54,4 +58,4 @@
|
|
|
54
58
|
</main>
|
|
55
59
|
|
|
56
60
|
</body>
|
|
57
|
-
</html>
|
|
61
|
+
</html>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<h1 class="sqd-page-title"><%= t("solid_queue_web.audit.title") %></h1>
|
|
2
|
+
|
|
3
|
+
<div class="sqd-page-header">
|
|
4
|
+
<div class="sqd-filters">
|
|
5
|
+
<form action="<%= audit_path %>" method="get" style="display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap;">
|
|
6
|
+
<select name="action_filter" class="sqd-select" aria-label="<%= t("solid_queue_web.audit.aria_filter_action") %>" onchange="this.form.submit()">
|
|
7
|
+
<option value=""><%= t("solid_queue_web.audit.all_actions") %></option>
|
|
8
|
+
<% SolidQueueWeb::AuditEvent::ACTIONS.each do |a| %>
|
|
9
|
+
<option value="<%= a %>" <%= @action_filter == a ? "selected" : "" %>><%= a.tr("_", " ") %></option>
|
|
10
|
+
<% end %>
|
|
11
|
+
</select>
|
|
12
|
+
<% if @actor_filter.present? %>
|
|
13
|
+
<span class="sqd-badge sqd-badge--muted"><%= t("solid_queue_web.audit.actor_label") %> <%= @actor_filter %></span>
|
|
14
|
+
<%= link_to "×", audit_path(action_filter: @action_filter, queue: @queue_filter), class: "sqd-btn sqd-btn--muted sqd-btn--sm" %>
|
|
15
|
+
<% end %>
|
|
16
|
+
<% if @queue_filter.present? %>
|
|
17
|
+
<span class="sqd-badge sqd-badge--muted"><%= t("solid_queue_web.audit.queue_label") %> <%= @queue_filter %></span>
|
|
18
|
+
<%= link_to "×", audit_path(action_filter: @action_filter, actor: @actor_filter), class: "sqd-btn sqd-btn--muted sqd-btn--sm" %>
|
|
19
|
+
<% end %>
|
|
20
|
+
<% if @action_filter.present? || @actor_filter.present? || @queue_filter.present? %>
|
|
21
|
+
<%= link_to t("solid_queue_web.audit.clear_all"), audit_path, class: "sqd-btn sqd-btn--muted sqd-btn--sm" %>
|
|
22
|
+
<% end %>
|
|
23
|
+
</form>
|
|
24
|
+
</div>
|
|
25
|
+
<% if @audit_events.any? %>
|
|
26
|
+
<div class="sqd-actions">
|
|
27
|
+
<%= link_to t("solid_queue_web.audit.export_csv"), audit_path(format: :csv, action_filter: @action_filter, actor: @actor_filter, queue: @queue_filter),
|
|
28
|
+
class: "sqd-btn sqd-btn--muted", data: { turbo: false } %>
|
|
29
|
+
</div>
|
|
30
|
+
<% end %>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div class="sqd-card">
|
|
34
|
+
<% if @audit_events.empty? %>
|
|
35
|
+
<div class="sqd-empty"><%= t("solid_queue_web.audit.empty") %></div>
|
|
36
|
+
<% else %>
|
|
37
|
+
<table>
|
|
38
|
+
<thead>
|
|
39
|
+
<tr>
|
|
40
|
+
<th scope="col"><%= t("solid_queue_web.audit.col_time") %></th>
|
|
41
|
+
<th scope="col"><%= t("solid_queue_web.audit.col_action") %></th>
|
|
42
|
+
<th scope="col"><%= t("solid_queue_web.audit.col_actor") %></th>
|
|
43
|
+
<th scope="col"><%= t("solid_queue_web.audit.col_job_class") %></th>
|
|
44
|
+
<th scope="col"><%= t("solid_queue_web.audit.col_queue") %></th>
|
|
45
|
+
<th scope="col"><%= t("solid_queue_web.audit.col_count") %></th>
|
|
46
|
+
</tr>
|
|
47
|
+
</thead>
|
|
48
|
+
<tbody>
|
|
49
|
+
<% @audit_events.each do |event| %>
|
|
50
|
+
<tr>
|
|
51
|
+
<td class="sqd-mono"><%= format_timestamp(event.created_at) %></td>
|
|
52
|
+
<td><span class="sqd-badge sqd-badge--<%= event.action.include?("discard") ? "failed" : event.action.include?("paused") || event.action.include?("resumed") ? "paused" : "ready" %>"><%= event.action.tr("_", " ") %></span></td>
|
|
53
|
+
<td class="sqd-mono sqd-muted-text">
|
|
54
|
+
<% if event.actor.present? %>
|
|
55
|
+
<%= link_to event.actor, audit_path(action_filter: @action_filter, queue: @queue_filter, actor: event.actor), style: "color: inherit;" %>
|
|
56
|
+
<% else %>
|
|
57
|
+
<span style="color: var(--muted)">—</span>
|
|
58
|
+
<% end %>
|
|
59
|
+
</td>
|
|
60
|
+
<td class="sqd-mono"><%= event.job_class || "—" %></td>
|
|
61
|
+
<td class="sqd-mono">
|
|
62
|
+
<% if event.queue_name.present? %>
|
|
63
|
+
<%= link_to event.queue_name, audit_path(action_filter: @action_filter, actor: @actor_filter, queue: event.queue_name), style: "color: inherit;" %>
|
|
64
|
+
<% else %>
|
|
65
|
+
<span style="color: var(--muted)">—</span>
|
|
66
|
+
<% end %>
|
|
67
|
+
</td>
|
|
68
|
+
<td><%= event.item_count %></td>
|
|
69
|
+
</tr>
|
|
70
|
+
<% end %>
|
|
71
|
+
</tbody>
|
|
72
|
+
</table>
|
|
73
|
+
<% end %>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<% if @pagy.last > 1 %>
|
|
77
|
+
<%= @pagy.series_nav.html_safe %>
|
|
78
|
+
<% end %>
|
|
@@ -1,60 +1,60 @@
|
|
|
1
1
|
<%= turbo_frame_tag "dashboard", target: "_top", data: { controller: "refresh", refresh_interval_value: SolidQueueWeb.dashboard_refresh_interval } do %>
|
|
2
|
-
<h1 class="sqd-page-title"
|
|
2
|
+
<h1 class="sqd-page-title"><%= t("solid_queue_web.dashboard.title") %></h1>
|
|
3
3
|
|
|
4
4
|
<div class="sqd-stats">
|
|
5
5
|
<%= link_to jobs_path(status: "ready"), class: "sqd-stat sqd-stat--ready sqd-stat--link" do %>
|
|
6
6
|
<div class="sqd-stat__value"><%= @stats.counts[:ready] %></div>
|
|
7
|
-
<div class="sqd-stat__label"
|
|
7
|
+
<div class="sqd-stat__label"><%= t("solid_queue_web.dashboard.ready") %></div>
|
|
8
8
|
<% end %>
|
|
9
9
|
<%= link_to jobs_path(status: "scheduled"), class: "sqd-stat sqd-stat--scheduled sqd-stat--link" do %>
|
|
10
10
|
<div class="sqd-stat__value"><%= @stats.counts[:scheduled] %></div>
|
|
11
|
-
<div class="sqd-stat__label"
|
|
11
|
+
<div class="sqd-stat__label"><%= t("solid_queue_web.dashboard.scheduled") %></div>
|
|
12
12
|
<% end %>
|
|
13
13
|
<%= link_to jobs_path(status: "claimed"), class: "sqd-stat sqd-stat--claimed sqd-stat--link" do %>
|
|
14
14
|
<div class="sqd-stat__value"><%= @stats.counts[:claimed] %></div>
|
|
15
|
-
<div class="sqd-stat__label"
|
|
15
|
+
<div class="sqd-stat__label"><%= t("solid_queue_web.dashboard.running") %></div>
|
|
16
16
|
<% end %>
|
|
17
17
|
<%= link_to jobs_path(status: "blocked"), class: "sqd-stat sqd-stat--blocked sqd-stat--link" do %>
|
|
18
18
|
<div class="sqd-stat__value"><%= @stats.counts[:blocked] %></div>
|
|
19
|
-
<div class="sqd-stat__label"
|
|
19
|
+
<div class="sqd-stat__label"><%= t("solid_queue_web.dashboard.blocked") %></div>
|
|
20
20
|
<% end %>
|
|
21
21
|
<%= link_to failed_jobs_path, class: "sqd-stat sqd-stat--failed sqd-stat--link" do %>
|
|
22
22
|
<div class="sqd-stat__value"><%= @stats.counts[:failed] %></div>
|
|
23
|
-
<div class="sqd-stat__label"
|
|
23
|
+
<div class="sqd-stat__label"><%= t("solid_queue_web.dashboard.failed") %></div>
|
|
24
24
|
<% end %>
|
|
25
25
|
<%= link_to queues_path, class: "sqd-stat sqd-stat--queues sqd-stat--link" do %>
|
|
26
26
|
<div class="sqd-stat__value"><%= @stats.counts[:queues] %></div>
|
|
27
|
-
<div class="sqd-stat__label"
|
|
27
|
+
<div class="sqd-stat__label"><%= t("solid_queue_web.dashboard.queues") %></div>
|
|
28
28
|
<% end %>
|
|
29
29
|
<%= link_to recurring_tasks_path, class: "sqd-stat sqd-stat--recurring sqd-stat--link" do %>
|
|
30
30
|
<div class="sqd-stat__value"><%= @stats.counts[:recurring] %></div>
|
|
31
|
-
<div class="sqd-stat__label"
|
|
31
|
+
<div class="sqd-stat__label"><%= t("solid_queue_web.dashboard.recurring") %></div>
|
|
32
32
|
<% end %>
|
|
33
33
|
<%= link_to processes_path, class: "sqd-stat sqd-stat--processes sqd-stat--link" do %>
|
|
34
34
|
<div class="sqd-stat__value"><%= @stats.counts[:processes] %></div>
|
|
35
|
-
<div class="sqd-stat__label"
|
|
35
|
+
<div class="sqd-stat__label"><%= t("solid_queue_web.dashboard.processes") %></div>
|
|
36
36
|
<% end %>
|
|
37
37
|
<%= link_to history_path(period: "1h"), class: "sqd-stat sqd-stat--done sqd-stat--link" do %>
|
|
38
38
|
<div class="sqd-stat__value"><%= @stats.throughput[:completed_1h] %></div>
|
|
39
|
-
<div class="sqd-stat__label"
|
|
39
|
+
<div class="sqd-stat__label"><%= t("solid_queue_web.dashboard.done_1h") %></div>
|
|
40
40
|
<% end %>
|
|
41
41
|
<%= link_to history_path(period: "24h"), class: "sqd-stat sqd-stat--done sqd-stat--link" do %>
|
|
42
42
|
<div class="sqd-stat__value"><%= @stats.throughput[:completed_24h] %></div>
|
|
43
|
-
<div class="sqd-stat__label"
|
|
43
|
+
<div class="sqd-stat__label"><%= t("solid_queue_web.dashboard.done_24h") %></div>
|
|
44
44
|
<% end %>
|
|
45
45
|
</div>
|
|
46
46
|
|
|
47
47
|
<% max_val = [@stats.sparkline.max, 1].max %>
|
|
48
48
|
<div class="sqd-card" style="margin-bottom: 1rem;">
|
|
49
49
|
<div class="sqd-card__header">
|
|
50
|
-
<span class="sqd-card__title"
|
|
50
|
+
<span class="sqd-card__title"><%= t("solid_queue_web.dashboard.throughput_label") %></span>
|
|
51
51
|
<div class="sqd-throughput__summary">
|
|
52
|
-
<span
|
|
53
|
-
<span
|
|
52
|
+
<span><%= t("solid_queue_web.dashboard.throughput_1h") %> <strong><%= @stats.throughput[:completed_1h] %></strong></span>
|
|
53
|
+
<span><%= t("solid_queue_web.dashboard.throughput_24h") %> <strong><%= @stats.throughput[:completed_24h] %></strong></span>
|
|
54
54
|
</div>
|
|
55
55
|
</div>
|
|
56
56
|
<% if @stats.throughput[:completed_24h] == 0 %>
|
|
57
|
-
<div class="sqd-sparkline__empty"
|
|
57
|
+
<div class="sqd-sparkline__empty"><%= t("solid_queue_web.dashboard.no_completed_jobs") %></div>
|
|
58
58
|
<% else %>
|
|
59
59
|
<div class="sqd-sparkline" aria-label="Jobs completed per hour over the last 12 hours">
|
|
60
60
|
<% @stats.sparkline.each_with_index do |count, i| %>
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
style="height: <%= [pct, 3].max %>%"
|
|
68
68
|
title="<%= hour_start.strftime('%-I%p').downcase %>: <%= count %> <%= "job".pluralize(count) %>"></div>
|
|
69
69
|
</div>
|
|
70
|
-
<div class="sqd-sparkline__tick"><%= show_tick ? (i == 11 ? "
|
|
70
|
+
<div class="sqd-sparkline__tick"><%= show_tick ? (i == 11 ? t("solid_queue_web.dashboard.axis_now") : hour_start.strftime("%-I%p").downcase) : "" %></div>
|
|
71
71
|
</div>
|
|
72
72
|
<% end %>
|
|
73
73
|
</div>
|
|
@@ -78,26 +78,26 @@
|
|
|
78
78
|
<% max_depth = [@stats.depth_sparkline.max, 1].max %>
|
|
79
79
|
<div class="sqd-card" style="margin-bottom: 1rem;">
|
|
80
80
|
<div class="sqd-card__header">
|
|
81
|
-
<span class="sqd-card__title"
|
|
81
|
+
<span class="sqd-card__title"><%= t("solid_queue_web.dashboard.queue_depth_label") %></span>
|
|
82
82
|
<div class="sqd-throughput__summary">
|
|
83
|
-
<span
|
|
83
|
+
<span><%= t("solid_queue_web.dashboard.queue_depth_now") %> <strong><%= current_depth %></strong></span>
|
|
84
84
|
</div>
|
|
85
85
|
</div>
|
|
86
86
|
<% if @stats.depth_sparkline.all?(&:zero?) %>
|
|
87
|
-
<div class="sqd-sparkline__empty"
|
|
87
|
+
<div class="sqd-sparkline__empty"><%= t("solid_queue_web.dashboard.no_active_jobs") %></div>
|
|
88
88
|
<% else %>
|
|
89
89
|
<div class="sqd-sparkline" aria-label="Queue depth over the last 12 hours">
|
|
90
90
|
<% @stats.depth_sparkline.each_with_index do |depth, i| %>
|
|
91
91
|
<% pct = (depth.to_f / max_depth * 100).round %>
|
|
92
|
-
<%
|
|
92
|
+
<% t_val = i == 11 ? Time.current : (12 - i).hours.ago %>
|
|
93
93
|
<% show_tick = [0, 3, 6, 9, 11].include?(i) %>
|
|
94
94
|
<div class="sqd-sparkline__col">
|
|
95
95
|
<div class="sqd-sparkline__bar-wrap">
|
|
96
96
|
<div class="sqd-sparkline__bar sqd-sparkline__bar--depth"
|
|
97
97
|
style="height: <%= [pct, 3].max %>%"
|
|
98
|
-
title="<%= i == 11 ? "
|
|
98
|
+
title="<%= i == 11 ? t("solid_queue_web.dashboard.axis_now") : t_val.strftime("%-I%p").downcase %>: <%= depth %> <%= "job".pluralize(depth) %> in queue"></div>
|
|
99
99
|
</div>
|
|
100
|
-
<div class="sqd-sparkline__tick"><%= show_tick ? (i == 11 ? "
|
|
100
|
+
<div class="sqd-sparkline__tick"><%= show_tick ? (i == 11 ? t("solid_queue_web.dashboard.axis_now") : t_val.strftime("%-I%p").downcase) : "" %></div>
|
|
101
101
|
</div>
|
|
102
102
|
<% end %>
|
|
103
103
|
</div>
|
|
@@ -107,13 +107,13 @@
|
|
|
107
107
|
<% max_failures = [@stats.failure_sparkline.max, 1].max %>
|
|
108
108
|
<div class="sqd-card" style="margin-bottom: 1rem;">
|
|
109
109
|
<div class="sqd-card__header">
|
|
110
|
-
<span class="sqd-card__title"
|
|
110
|
+
<span class="sqd-card__title"><%= t("solid_queue_web.dashboard.failures_label") %></span>
|
|
111
111
|
<div class="sqd-throughput__summary">
|
|
112
|
-
<span
|
|
112
|
+
<span><%= t("solid_queue_web.dashboard.failures_total") %> <strong><%= @stats.failure_sparkline.sum %></strong></span>
|
|
113
113
|
</div>
|
|
114
114
|
</div>
|
|
115
115
|
<% if @stats.failure_sparkline.all?(&:zero?) %>
|
|
116
|
-
<div class="sqd-sparkline__empty"
|
|
116
|
+
<div class="sqd-sparkline__empty"><%= t("solid_queue_web.dashboard.no_failures") %></div>
|
|
117
117
|
<% else %>
|
|
118
118
|
<div class="sqd-sparkline" aria-label="Failed jobs per hour over the last 12 hours">
|
|
119
119
|
<% @stats.failure_sparkline.each_with_index do |count, i| %>
|
|
@@ -126,7 +126,7 @@
|
|
|
126
126
|
style="height: <%= [pct, 3].max %>%"
|
|
127
127
|
title="<%= hour_start.strftime('%-I%p').downcase %>: <%= count %> <%= "failure".pluralize(count) %>"></div>
|
|
128
128
|
</div>
|
|
129
|
-
<div class="sqd-sparkline__tick"><%= show_tick ? (i == 11 ? "
|
|
129
|
+
<div class="sqd-sparkline__tick"><%= show_tick ? (i == 11 ? t("solid_queue_web.dashboard.axis_now") : hour_start.strftime("%-I%p").downcase) : "" %></div>
|
|
130
130
|
</div>
|
|
131
131
|
<% end %>
|
|
132
132
|
</div>
|
|
@@ -136,31 +136,31 @@
|
|
|
136
136
|
<div style="display:grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1rem;">
|
|
137
137
|
<div class="sqd-card">
|
|
138
138
|
<div class="sqd-card__header">
|
|
139
|
-
<span class="sqd-card__title"
|
|
139
|
+
<span class="sqd-card__title"><%= t("solid_queue_web.dashboard.quick_links") %></span>
|
|
140
140
|
</div>
|
|
141
141
|
<div style="padding: 1rem; display: flex; flex-direction: column; gap: 0.5rem;">
|
|
142
|
-
<%= link_to "
|
|
143
|
-
<%= link_to "
|
|
144
|
-
<%= link_to "
|
|
145
|
-
<%= link_to "
|
|
146
|
-
<%= link_to "
|
|
142
|
+
<%= link_to t("solid_queue_web.dashboard.view_ready_jobs"), jobs_path(status: "ready"), class: "sqd-btn sqd-btn--muted" %>
|
|
143
|
+
<%= link_to t("solid_queue_web.dashboard.view_scheduled_jobs"), jobs_path(status: "scheduled"), class: "sqd-btn sqd-btn--muted" %>
|
|
144
|
+
<%= link_to t("solid_queue_web.dashboard.view_failed_jobs"), failed_jobs_path, class: "sqd-btn sqd-btn--muted" %>
|
|
145
|
+
<%= link_to t("solid_queue_web.dashboard.manage_queues"), queues_path, class: "sqd-btn sqd-btn--muted" %>
|
|
146
|
+
<%= link_to t("solid_queue_web.dashboard.view_recurring_tasks"), recurring_tasks_path, class: "sqd-btn sqd-btn--muted" %>
|
|
147
147
|
</div>
|
|
148
148
|
</div>
|
|
149
149
|
|
|
150
150
|
<% if @stats.counts[:failed] > 0 %>
|
|
151
151
|
<div class="sqd-card">
|
|
152
152
|
<div class="sqd-card__header">
|
|
153
|
-
<span class="sqd-card__title"
|
|
153
|
+
<span class="sqd-card__title"><%= t("solid_queue_web.dashboard.failed_jobs_card") %></span>
|
|
154
154
|
</div>
|
|
155
155
|
<div style="padding: 1rem; display: flex; flex-direction: column; gap: 0.5rem;">
|
|
156
156
|
<p style="color: var(--danger); font-size: 13px;">
|
|
157
|
-
<%=
|
|
157
|
+
<%= t("solid_queue_web.dashboard.need_attention", count: @stats.counts[:failed]) %>
|
|
158
158
|
</p>
|
|
159
|
-
<%= button_to "
|
|
159
|
+
<%= button_to t("solid_queue_web.dashboard.retry_all_failed"), retry_all_failed_jobs_path,
|
|
160
160
|
method: :post,
|
|
161
161
|
class: "sqd-btn sqd-btn--primary",
|
|
162
|
-
data: { confirm: "
|
|
163
|
-
<%= link_to "
|
|
162
|
+
data: { confirm: t("solid_queue_web.dashboard.confirm_retry_all_failed", count: @stats.counts[:failed]) } %>
|
|
163
|
+
<%= link_to t("solid_queue_web.dashboard.review"), failed_jobs_path, class: "sqd-btn sqd-btn--muted" %>
|
|
164
164
|
</div>
|
|
165
165
|
</div>
|
|
166
166
|
<% end %>
|
|
@@ -168,13 +168,13 @@
|
|
|
168
168
|
<% if SolidQueueWeb.slow_job_threshold && @stats.slow_jobs_count > 0 %>
|
|
169
169
|
<div class="sqd-card">
|
|
170
170
|
<div class="sqd-card__header">
|
|
171
|
-
<span class="sqd-card__title"
|
|
171
|
+
<span class="sqd-card__title"><%= t("solid_queue_web.dashboard.slow_jobs_card") %></span>
|
|
172
172
|
</div>
|
|
173
173
|
<div style="padding: 1rem; display: flex; flex-direction: column; gap: 0.5rem;">
|
|
174
174
|
<p style="color: var(--warning); font-size: 13px;">
|
|
175
|
-
<%=
|
|
175
|
+
<%= t("solid_queue_web.dashboard.slow_jobs_warning", count: @stats.slow_jobs_count, threshold: distance_of_time_in_words(SolidQueueWeb.slow_job_threshold)) %>
|
|
176
176
|
</p>
|
|
177
|
-
<%= link_to "
|
|
177
|
+
<%= link_to t("solid_queue_web.dashboard.review"), jobs_path(status: "claimed"), class: "sqd-btn sqd-btn--muted" %>
|
|
178
178
|
</div>
|
|
179
179
|
</div>
|
|
180
180
|
<% end %>
|
|
@@ -182,19 +182,40 @@
|
|
|
182
182
|
<% if @stats.counts[:blocked] > 0 %>
|
|
183
183
|
<div class="sqd-card">
|
|
184
184
|
<div class="sqd-card__header">
|
|
185
|
-
<span class="sqd-card__title"
|
|
185
|
+
<span class="sqd-card__title"><%= t("solid_queue_web.dashboard.blocked_jobs_card") %></span>
|
|
186
186
|
</div>
|
|
187
187
|
<div style="padding: 1rem; display: flex; flex-direction: column; gap: 0.5rem;">
|
|
188
188
|
<p style="color: var(--warning); font-size: 13px;">
|
|
189
|
-
<%=
|
|
189
|
+
<%= t("solid_queue_web.dashboard.blocked_jobs_count", count: @stats.counts[:blocked]) %>
|
|
190
190
|
</p>
|
|
191
|
-
<%= button_to "
|
|
191
|
+
<%= button_to t("solid_queue_web.dashboard.discard_all_blocked"), blocked_jobs_path,
|
|
192
192
|
method: :delete,
|
|
193
193
|
class: "sqd-btn sqd-btn--danger",
|
|
194
|
-
data: { confirm: "
|
|
195
|
-
<%= link_to "
|
|
194
|
+
data: { confirm: t("solid_queue_web.dashboard.confirm_discard_all_blocked", count: @stats.counts[:blocked]) } %>
|
|
195
|
+
<%= link_to t("solid_queue_web.dashboard.review"), jobs_path(status: "blocked"), class: "sqd-btn sqd-btn--muted" %>
|
|
196
196
|
</div>
|
|
197
197
|
</div>
|
|
198
198
|
<% end %>
|
|
199
|
+
|
|
200
|
+
<% SolidQueueWeb.dashboard_cards.each do |card| %>
|
|
201
|
+
<div class="sqd-card">
|
|
202
|
+
<div class="sqd-card__header">
|
|
203
|
+
<span class="sqd-card__title"><%= card[:title] %></span>
|
|
204
|
+
<% if card[:link] %>
|
|
205
|
+
<%= link_to card[:link][:label], card[:link][:url], class: "sqd-btn sqd-btn--muted sqd-btn--sm" %>
|
|
206
|
+
<% end %>
|
|
207
|
+
</div>
|
|
208
|
+
<% if card[:stats] %>
|
|
209
|
+
<div class="sqd-card__body">
|
|
210
|
+
<% card[:stats].call.each do |label, value| %>
|
|
211
|
+
<div class="sqd-custom-stat">
|
|
212
|
+
<span class="sqd-custom-stat__label"><%= label %></span>
|
|
213
|
+
<span class="sqd-custom-stat__value"><%= value %></span>
|
|
214
|
+
</div>
|
|
215
|
+
<% end %>
|
|
216
|
+
</div>
|
|
217
|
+
<% end %>
|
|
218
|
+
</div>
|
|
219
|
+
<% end %>
|
|
199
220
|
</div>
|
|
200
|
-
<% end %>
|
|
221
|
+
<% end %>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<div class="sqd-page-header">
|
|
2
|
-
<h1 class="sqd-page-title"
|
|
2
|
+
<h1 class="sqd-page-title"><%= t("solid_queue_web.failed_job_errors.title") %></h1>
|
|
3
3
|
<div class="sqd-actions">
|
|
4
|
-
<%= link_to "
|
|
4
|
+
<%= link_to t("solid_queue_web.failed_job_errors.back"), failed_jobs_path, class: "sqd-btn sqd-btn--muted sqd-btn--sm" %>
|
|
5
5
|
</div>
|
|
6
6
|
</div>
|
|
7
7
|
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
<table>
|
|
11
11
|
<thead>
|
|
12
12
|
<tr>
|
|
13
|
-
<th scope="col"
|
|
14
|
-
<th scope="col"
|
|
15
|
-
<th scope="col" style="text-align: right;"
|
|
13
|
+
<th scope="col"><%= t("solid_queue_web.failed_job_errors.col_error_class") %></th>
|
|
14
|
+
<th scope="col"><%= t("solid_queue_web.failed_job_errors.col_message") %></th>
|
|
15
|
+
<th scope="col" style="text-align: right;"><%= t("solid_queue_web.failed_job_errors.col_count") %></th>
|
|
16
16
|
</tr>
|
|
17
17
|
</thead>
|
|
18
18
|
<tbody>
|
|
@@ -39,6 +39,6 @@
|
|
|
39
39
|
</div>
|
|
40
40
|
<% else %>
|
|
41
41
|
<div class="sqd-card">
|
|
42
|
-
<div class="sqd-empty"
|
|
42
|
+
<div class="sqd-empty"><%= t("solid_queue_web.failed_job_errors.empty") %></div>
|
|
43
43
|
</div>
|
|
44
|
-
<% end %>
|
|
44
|
+
<% end %>
|