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
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
<div class="sqd-page-header">
|
|
2
|
-
<h1 class="sqd-page-title"
|
|
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 "
|
|
6
|
-
<%= link_to "
|
|
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 "
|
|
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: "
|
|
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: "
|
|
20
|
-
data: { confirm: "
|
|
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 "
|
|
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: "
|
|
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="
|
|
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 "
|
|
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="
|
|
46
|
-
<%= link_to "
|
|
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: "
|
|
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>
|
|
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"
|
|
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"
|
|
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="
|
|
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("
|
|
93
|
-
<%= sort_header_th("
|
|
94
|
-
<th scope="col"
|
|
95
|
-
<%= sort_header_th("
|
|
96
|
-
<th scope="col"><span class="sqd-sr-only"
|
|
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="
|
|
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 "
|
|
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 "
|
|
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: "
|
|
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"
|
|
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
|
-
|
|
146
|
-
<%= link_to "
|
|
145
|
+
<%= t("solid_queue_web.shared.filtering_by_queue") %> <strong><%= @queue %></strong> —
|
|
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"
|
|
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 "
|
|
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="
|
|
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 "
|
|
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="
|
|
26
|
-
<%= link_to "
|
|
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
|
-
|
|
36
|
-
<%= link_to "
|
|
35
|
+
<%= t("solid_queue_web.shared.filtering_by_queue") %> <strong><%= @queue %></strong> —
|
|
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("
|
|
47
|
-
<%= sort_header_th("
|
|
48
|
-
<th scope="col"
|
|
49
|
-
<%= sort_header_th("
|
|
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"
|
|
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;"
|
|
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 "
|
|
10
|
-
<%= link_to "
|
|
11
|
-
<%= link_to "
|
|
12
|
-
<%= link_to "
|
|
13
|
-
<%= link_to "
|
|
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 "
|
|
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 "
|
|
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: "
|
|
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 "
|
|
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: "
|
|
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="
|
|
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="
|
|
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" : ""
|
|
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" : ""
|
|
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 "
|
|
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="
|
|
58
|
-
<%= link_to "
|
|
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: "
|
|
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>
|
|
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"
|
|
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="
|
|
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("
|
|
94
|
-
<%= sort_header_th("
|
|
95
|
-
<%= sort_header_th("
|
|
96
|
-
<th scope="col"
|
|
97
|
-
<%= sort_header_th("
|
|
98
|
-
<th scope="col"><span class="sqd-sr-only"
|
|
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="
|
|
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 "
|
|
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: "
|
|
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 "
|
|
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: "
|
|
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"
|
|
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("
|
|
161
|
-
<%= sort_header_th("
|
|
162
|
-
<%= sort_header_th("
|
|
163
|
-
<th scope="col"
|
|
164
|
-
<%= sort_header_th("
|
|
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"
|
|
167
|
-
<th scope="col"
|
|
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 "
|
|
4
|
+
<%= link_to t("solid_queue_web.jobs_show.breadcrumb"), jobs_path(status: @execution_status) %> › <%= 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 "
|
|
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 "
|
|
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: "
|
|
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 "
|
|
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: "
|
|
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"
|
|
31
|
+
<h2 class="sqd-section-title"><%= t("solid_queue_web.jobs_show.section_details") %></h2>
|
|
32
32
|
<dl class="sqd-dl">
|
|
33
|
-
<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
|
|
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
|
|
39
|
+
<dt><%= t("solid_queue_web.jobs_show.field_priority") %></dt>
|
|
40
40
|
<dd><%= @job.priority %></dd>
|
|
41
41
|
|
|
42
|
-
<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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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"
|
|
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": "
|
|
74
|
-
<%= f.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"
|
|
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"
|
|
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="
|
|
7
|
-
<%= link_to "
|
|
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"
|
|
20
|
-
<th scope="col" style="text-align: right;"
|
|
21
|
-
<th scope="col" style="text-align: right;"
|
|
22
|
-
<th scope="col" style="text-align: right;"
|
|
23
|
-
<th scope="col" style="text-align: right;"
|
|
24
|
-
<th scope="col" style="text-align: right;"
|
|
25
|
-
<th scope="col" style="text-align: right;"
|
|
26
|
-
<th scope="col" style="text-align: right;"
|
|
27
|
-
<th scope="col" style="text-align: right;"
|
|
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">
|
|
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 %>
|