solid_queue_monitor 1.2.2 → 2.0.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 +36 -1
- data/app/assets/javascripts/solid_queue_monitor/application.js +393 -0
- data/app/{services/solid_queue_monitor/stylesheet_generator.rb → assets/stylesheets/solid_queue_monitor/application.css} +80 -12
- data/app/controllers/solid_queue_monitor/application_controller.rb +2 -2
- data/app/controllers/solid_queue_monitor/assets_controller.rb +52 -0
- data/app/controllers/solid_queue_monitor/base_controller.rb +0 -28
- data/app/controllers/solid_queue_monitor/failed_jobs_controller.rb +3 -6
- data/app/controllers/solid_queue_monitor/in_progress_jobs_controller.rb +3 -6
- data/app/controllers/solid_queue_monitor/jobs_controller.rb +3 -6
- data/app/controllers/solid_queue_monitor/overview_controller.rb +3 -12
- data/app/controllers/solid_queue_monitor/queues_controller.rb +4 -18
- data/app/controllers/solid_queue_monitor/ready_jobs_controller.rb +3 -6
- data/app/controllers/solid_queue_monitor/recurring_jobs_controller.rb +3 -6
- data/app/controllers/solid_queue_monitor/scheduled_jobs_controller.rb +3 -6
- data/app/controllers/solid_queue_monitor/search_controller.rb +3 -4
- data/app/controllers/solid_queue_monitor/workers_controller.rb +24 -8
- data/app/helpers/solid_queue_monitor/application_helper.rb +46 -0
- data/app/helpers/solid_queue_monitor/chart_helper.rb +293 -0
- data/app/helpers/solid_queue_monitor/job_details_helper.rb +66 -0
- data/app/helpers/solid_queue_monitor/jobs_helper.rb +134 -0
- data/app/helpers/solid_queue_monitor/pagination_helper.rb +23 -0
- data/app/helpers/solid_queue_monitor/sort_helper.rb +30 -0
- data/app/helpers/solid_queue_monitor/workers_helper.rb +88 -0
- data/app/services/solid_queue_monitor/asset_cache.rb +56 -0
- data/app/services/solid_queue_monitor/chart_data_service.rb +2 -2
- data/app/views/layouts/solid_queue_monitor/application.html.erb +25 -0
- data/app/views/solid_queue_monitor/failed_jobs/_row.html.erb +26 -0
- data/app/views/solid_queue_monitor/failed_jobs/index.html.erb +38 -0
- data/app/views/solid_queue_monitor/in_progress_jobs/_row.html.erb +13 -0
- data/app/views/solid_queue_monitor/in_progress_jobs/index.html.erb +25 -0
- data/app/views/solid_queue_monitor/jobs/_arguments.html.erb +9 -0
- data/app/views/solid_queue_monitor/jobs/_error.html.erb +26 -0
- data/app/views/solid_queue_monitor/jobs/_execution_history.html.erb +25 -0
- data/app/views/solid_queue_monitor/jobs/_header.html.erb +37 -0
- data/app/views/solid_queue_monitor/jobs/_metadata.html.erb +22 -0
- data/app/views/solid_queue_monitor/jobs/_raw_data.html.erb +11 -0
- data/app/views/solid_queue_monitor/jobs/_timeline.html.erb +29 -0
- data/app/views/solid_queue_monitor/jobs/_timing.html.erb +9 -0
- data/app/views/solid_queue_monitor/jobs/_worker.html.erb +12 -0
- data/app/views/solid_queue_monitor/jobs/show.html.erb +22 -0
- data/app/views/solid_queue_monitor/overview/_chart.html.erb +1 -0
- data/app/views/solid_queue_monitor/overview/_recent_job_row.html.erb +26 -0
- data/app/views/solid_queue_monitor/overview/_recent_jobs.html.erb +31 -0
- data/app/views/solid_queue_monitor/overview/_stat_card.html.erb +4 -0
- data/app/views/solid_queue_monitor/overview/_stats.html.erb +11 -0
- data/app/views/solid_queue_monitor/overview/index.html.erb +9 -0
- data/app/views/solid_queue_monitor/queues/_job_row.html.erb +26 -0
- data/app/views/solid_queue_monitor/queues/_row.html.erb +33 -0
- data/app/views/solid_queue_monitor/queues/index.html.erb +18 -0
- data/app/views/solid_queue_monitor/queues/show.html.erb +63 -0
- data/app/views/solid_queue_monitor/ready_jobs/_row.html.erb +7 -0
- data/app/views/solid_queue_monitor/ready_jobs/index.html.erb +25 -0
- data/app/views/solid_queue_monitor/recurring_jobs/_row.html.erb +8 -0
- data/app/views/solid_queue_monitor/recurring_jobs/index.html.erb +26 -0
- data/app/views/solid_queue_monitor/scheduled_jobs/_row.html.erb +7 -0
- data/app/views/solid_queue_monitor/scheduled_jobs/index.html.erb +36 -0
- data/app/views/solid_queue_monitor/search/_completed_row.html.erb +6 -0
- data/app/views/solid_queue_monitor/search/_failed_row.html.erb +6 -0
- data/app/views/solid_queue_monitor/search/_job_row.html.erb +9 -0
- data/app/views/solid_queue_monitor/search/_recurring_row.html.erb +6 -0
- data/app/views/solid_queue_monitor/search/_section.html.erb +25 -0
- data/app/views/solid_queue_monitor/search/index.html.erb +23 -0
- data/app/views/solid_queue_monitor/shared/_filters.html.erb +48 -0
- data/app/views/solid_queue_monitor/shared/_flash.html.erb +17 -0
- data/app/views/solid_queue_monitor/shared/_footer.html.erb +3 -0
- data/app/views/solid_queue_monitor/shared/_header.html.erb +81 -0
- data/app/views/solid_queue_monitor/shared/_jobs_table.html.erb +20 -0
- data/app/views/solid_queue_monitor/shared/_pagination.html.erb +25 -0
- data/app/views/solid_queue_monitor/workers/_row.html.erb +22 -0
- data/app/views/solid_queue_monitor/workers/index.html.erb +82 -0
- data/config/routes.rb +6 -1
- data/lib/solid_queue_monitor/engine.rb +2 -0
- data/lib/solid_queue_monitor/version.rb +1 -1
- metadata +57 -17
- data/app/presenters/solid_queue_monitor/base_presenter.rb +0 -211
- data/app/presenters/solid_queue_monitor/failed_jobs_presenter.rb +0 -312
- data/app/presenters/solid_queue_monitor/in_progress_jobs_presenter.rb +0 -84
- data/app/presenters/solid_queue_monitor/job_details_presenter.rb +0 -696
- data/app/presenters/solid_queue_monitor/jobs_presenter.rb +0 -144
- data/app/presenters/solid_queue_monitor/queue_details_presenter.rb +0 -195
- data/app/presenters/solid_queue_monitor/queues_presenter.rb +0 -89
- data/app/presenters/solid_queue_monitor/ready_jobs_presenter.rb +0 -81
- data/app/presenters/solid_queue_monitor/recurring_jobs_presenter.rb +0 -81
- data/app/presenters/solid_queue_monitor/scheduled_jobs_presenter.rb +0 -173
- data/app/presenters/solid_queue_monitor/search_results_presenter.rb +0 -190
- data/app/presenters/solid_queue_monitor/stats_presenter.rb +0 -36
- data/app/presenters/solid_queue_monitor/workers_presenter.rb +0 -320
- data/app/services/solid_queue_monitor/chart_presenter.rb +0 -239
- data/app/services/solid_queue_monitor/html_generator.rb +0 -401
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<% process = @claimed_execution.instance_variable_get(:@process) %>
|
|
2
|
+
<% if process %>
|
|
3
|
+
<div class="job-section">
|
|
4
|
+
<h3 class="section-title">Worker</h3>
|
|
5
|
+
<div class="details-grid">
|
|
6
|
+
<div class="detail-row"><span class="detail-label">Hostname</span><span class="detail-value"><%= process.hostname %></span></div>
|
|
7
|
+
<div class="detail-row"><span class="detail-label">PID</span><span class="detail-value"><%= process.pid %></span></div>
|
|
8
|
+
<div class="detail-row"><span class="detail-label">Process Type</span><span class="detail-value"><%= process.kind %></span></div>
|
|
9
|
+
<div class="detail-row"><span class="detail-label">Started At</span><span class="detail-value"><%= format_datetime(@claimed_execution.created_at) %></span></div>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
<% end %>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<% content_for :title, "Job ##{@job.id}" %>
|
|
2
|
+
|
|
3
|
+
<div class="job-details-page">
|
|
4
|
+
<div class="job-back-link">
|
|
5
|
+
<%= link_to @back_path, class: 'back-link' do %>
|
|
6
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
7
|
+
<path d="M19 12H5M12 19l-7-7 7-7"/>
|
|
8
|
+
</svg>
|
|
9
|
+
Back
|
|
10
|
+
<% end %>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<%= render 'header' %>
|
|
14
|
+
<%= render 'timeline' %>
|
|
15
|
+
<%= render 'timing' %>
|
|
16
|
+
<%= render 'error' if @failed_execution %>
|
|
17
|
+
<%= render 'arguments' %>
|
|
18
|
+
<%= render 'metadata' %>
|
|
19
|
+
<%= render 'worker' if @claimed_execution %>
|
|
20
|
+
<%= render 'execution_history' %>
|
|
21
|
+
<%= render 'raw_data' %>
|
|
22
|
+
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= render_chart(data: @chart_data, time_range: @time_range) %>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<% failed_execution = SolidQueue::FailedExecution.find_by(job_id: job.id) if job_status(job) == 'failed' %>
|
|
2
|
+
<tr>
|
|
3
|
+
<td><%= link_to job.id, job_path(job), class: 'job-class-link' %></td>
|
|
4
|
+
<td><%= link_to job.class_name, job_path(job), class: 'job-class-link' %></td>
|
|
5
|
+
<td><%= queue_link(job.queue_name) %></td>
|
|
6
|
+
<td><%= format_arguments(job.arguments) %></td>
|
|
7
|
+
<td><%= job_status_badge(job) %></td>
|
|
8
|
+
<td><%= format_datetime(job.created_at) %></td>
|
|
9
|
+
<td class="actions-cell">
|
|
10
|
+
<% if failed_execution %>
|
|
11
|
+
<div class="job-actions">
|
|
12
|
+
<form method="post" action="<%= retry_failed_job_path(id: failed_execution.id) %>" class="inline-form">
|
|
13
|
+
<input type="hidden" name="redirect_to" value="<%= root_path %>">
|
|
14
|
+
<button type="submit" class="action-button retry-button">Retry</button>
|
|
15
|
+
</form>
|
|
16
|
+
<form method="post"
|
|
17
|
+
action="<%= discard_failed_job_path(id: failed_execution.id) %>"
|
|
18
|
+
class="inline-form"
|
|
19
|
+
data-confirm="Are you sure you want to discard this job?">
|
|
20
|
+
<input type="hidden" name="redirect_to" value="<%= root_path %>">
|
|
21
|
+
<button type="submit" class="action-button discard-button">Discard</button>
|
|
22
|
+
</form>
|
|
23
|
+
</div>
|
|
24
|
+
<% end %>
|
|
25
|
+
</td>
|
|
26
|
+
</tr>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<div class="section-wrapper">
|
|
2
|
+
<div class="section">
|
|
3
|
+
<h3>Recent Jobs</h3>
|
|
4
|
+
|
|
5
|
+
<%= render 'solid_queue_monitor/shared/filters',
|
|
6
|
+
action_path: root_path,
|
|
7
|
+
filters: @filters,
|
|
8
|
+
show_status: true %>
|
|
9
|
+
|
|
10
|
+
<% columns = [
|
|
11
|
+
{ sort_key: nil, label: 'ID' },
|
|
12
|
+
{ sort_key: :class_name, label: 'Job' },
|
|
13
|
+
{ sort_key: :queue_name, label: 'Queue' },
|
|
14
|
+
{ sort_key: nil, label: 'Arguments' },
|
|
15
|
+
{ sort_key: nil, label: 'Status' },
|
|
16
|
+
{ sort_key: :created_at, label: 'Created At' },
|
|
17
|
+
{ sort_key: nil, label: 'Actions' }
|
|
18
|
+
] %>
|
|
19
|
+
|
|
20
|
+
<%= render 'solid_queue_monitor/shared/jobs_table',
|
|
21
|
+
jobs: @recent_jobs[:records],
|
|
22
|
+
columns: columns,
|
|
23
|
+
sort: @sort,
|
|
24
|
+
filters: @filters,
|
|
25
|
+
row_partial: 'solid_queue_monitor/overview/recent_job_row' %>
|
|
26
|
+
|
|
27
|
+
<%= render 'solid_queue_monitor/shared/pagination',
|
|
28
|
+
current_page: @recent_jobs[:current_page],
|
|
29
|
+
total_pages: @recent_jobs[:total_pages] %>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<div class="stats-container">
|
|
2
|
+
<h3>Queue Statistics</h3>
|
|
3
|
+
<div class="stats">
|
|
4
|
+
<%= render 'stat_card', title: 'Active Jobs', value: @stats[:active_jobs] %>
|
|
5
|
+
<%= render 'stat_card', title: 'Ready', value: @stats[:ready] %>
|
|
6
|
+
<%= render 'stat_card', title: 'In Progress', value: @stats[:in_progress] %>
|
|
7
|
+
<%= render 'stat_card', title: 'Scheduled', value: @stats[:scheduled] %>
|
|
8
|
+
<%= render 'stat_card', title: 'Recurring', value: @stats[:recurring] %>
|
|
9
|
+
<%= render 'stat_card', title: 'Failed', value: @stats[:failed] %>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<% status = job_status(job) %>
|
|
2
|
+
<% failed_execution = SolidQueue::FailedExecution.find_by(job_id: job.id) if status == 'failed' %>
|
|
3
|
+
<tr>
|
|
4
|
+
<td><%= link_to job.id, job_path(job), class: 'job-class-link' %></td>
|
|
5
|
+
<td><%= link_to job.class_name, job_path(job), class: 'job-class-link' %></td>
|
|
6
|
+
<td><%= format_arguments(job.arguments) %></td>
|
|
7
|
+
<td><%= job_status_badge(job) %></td>
|
|
8
|
+
<td><%= format_datetime(job.created_at) %></td>
|
|
9
|
+
<td class="actions-cell">
|
|
10
|
+
<% if failed_execution %>
|
|
11
|
+
<div class="job-actions">
|
|
12
|
+
<form method="post" action="<%= retry_failed_job_path(id: failed_execution.id) %>" class="inline-form">
|
|
13
|
+
<input type="hidden" name="redirect_to" value="<%= queue_details_path(queue_name: @queue_name) %>">
|
|
14
|
+
<button type="submit" class="action-button retry-button">Retry</button>
|
|
15
|
+
</form>
|
|
16
|
+
<form method="post"
|
|
17
|
+
action="<%= discard_failed_job_path(id: failed_execution.id) %>"
|
|
18
|
+
class="inline-form"
|
|
19
|
+
data-confirm="Are you sure you want to discard this job?">
|
|
20
|
+
<input type="hidden" name="redirect_to" value="<%= queue_details_path(queue_name: @queue_name) %>">
|
|
21
|
+
<button type="submit" class="action-button discard-button">Discard</button>
|
|
22
|
+
</form>
|
|
23
|
+
</div>
|
|
24
|
+
<% end %>
|
|
25
|
+
</td>
|
|
26
|
+
</tr>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<% queue_name = job.queue_name || 'default' %>
|
|
2
|
+
<% paused = @paused_queues.include?(queue_name) %>
|
|
3
|
+
|
|
4
|
+
<tr class="<%= 'queue-paused' if paused %>">
|
|
5
|
+
<td><%= queue_link(queue_name) %></td>
|
|
6
|
+
<td>
|
|
7
|
+
<% if paused %>
|
|
8
|
+
<span class="status-badge status-paused">Paused</span>
|
|
9
|
+
<% else %>
|
|
10
|
+
<span class="status-badge status-active">Active</span>
|
|
11
|
+
<% end %>
|
|
12
|
+
</td>
|
|
13
|
+
<td><%= job.job_count %></td>
|
|
14
|
+
<td><%= @queue_stats.dig(:ready, queue_name) || 0 %></td>
|
|
15
|
+
<td><%= @queue_stats.dig(:scheduled, queue_name) || 0 %></td>
|
|
16
|
+
<td><%= @queue_stats.dig(:failed, queue_name) || 0 %></td>
|
|
17
|
+
<td class="actions-cell">
|
|
18
|
+
<% if paused %>
|
|
19
|
+
<form action="<%= resume_queue_path %>" method="post" class="inline-form">
|
|
20
|
+
<input type="hidden" name="queue_name" value="<%= queue_name %>">
|
|
21
|
+
<button type="submit" class="action-button resume-button" title="Resume queue processing">Resume</button>
|
|
22
|
+
</form>
|
|
23
|
+
<% else %>
|
|
24
|
+
<form action="<%= pause_queue_path %>"
|
|
25
|
+
method="post"
|
|
26
|
+
class="inline-form"
|
|
27
|
+
data-confirm="Are you sure you want to pause the <%= queue_name %> queue? Workers will stop processing jobs from this queue.">
|
|
28
|
+
<input type="hidden" name="queue_name" value="<%= queue_name %>">
|
|
29
|
+
<button type="submit" class="action-button pause-button" title="Pause queue processing">Pause</button>
|
|
30
|
+
</form>
|
|
31
|
+
<% end %>
|
|
32
|
+
</td>
|
|
33
|
+
</tr>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<% content_for :title, 'Queues' %>
|
|
2
|
+
|
|
3
|
+
<% columns = [
|
|
4
|
+
{ sort_key: :queue_name, label: 'Queue Name' },
|
|
5
|
+
{ sort_key: nil, label: 'Status' },
|
|
6
|
+
{ sort_key: :job_count, label: 'Total Jobs' },
|
|
7
|
+
{ sort_key: nil, label: 'Ready Jobs' },
|
|
8
|
+
{ sort_key: nil, label: 'Scheduled Jobs' },
|
|
9
|
+
{ sort_key: nil, label: 'Failed Jobs' },
|
|
10
|
+
{ sort_key: nil, label: 'Actions' }
|
|
11
|
+
] %>
|
|
12
|
+
|
|
13
|
+
<%= render 'solid_queue_monitor/shared/jobs_table',
|
|
14
|
+
jobs: @queues,
|
|
15
|
+
columns: columns,
|
|
16
|
+
sort: @sort,
|
|
17
|
+
filters: {},
|
|
18
|
+
row_partial: 'solid_queue_monitor/queues/row' %>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<% content_for :title, "Queue: #{@queue_name}" %>
|
|
2
|
+
|
|
3
|
+
<div class="section-header-row">
|
|
4
|
+
<div class="section-header-left">
|
|
5
|
+
<% if @paused %>
|
|
6
|
+
<span class="status-badge status-paused">Paused</span>
|
|
7
|
+
<% else %>
|
|
8
|
+
<span class="status-badge status-active">Active</span>
|
|
9
|
+
<% end %>
|
|
10
|
+
</div>
|
|
11
|
+
<div class="section-header-right">
|
|
12
|
+
<% if @paused %>
|
|
13
|
+
<form action="<%= resume_queue_path %>" method="post" class="inline-form">
|
|
14
|
+
<input type="hidden" name="queue_name" value="<%= @queue_name %>">
|
|
15
|
+
<input type="hidden" name="redirect_to" value="<%= queue_details_path(queue_name: @queue_name) %>">
|
|
16
|
+
<button type="submit" class="action-button resume-button">Resume Queue</button>
|
|
17
|
+
</form>
|
|
18
|
+
<% else %>
|
|
19
|
+
<form action="<%= pause_queue_path %>" method="post" class="inline-form" data-confirm="Are you sure you want to pause this queue?">
|
|
20
|
+
<input type="hidden" name="queue_name" value="<%= @queue_name %>">
|
|
21
|
+
<input type="hidden" name="redirect_to" value="<%= queue_details_path(queue_name: @queue_name) %>">
|
|
22
|
+
<button type="submit" class="action-button pause-button">Pause Queue</button>
|
|
23
|
+
</form>
|
|
24
|
+
<% end %>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<div class="stats-container">
|
|
29
|
+
<div class="stats">
|
|
30
|
+
<% @counts.each do |label, value| %>
|
|
31
|
+
<%= render 'solid_queue_monitor/overview/stat_card', title: label.to_s.titleize, value: value %>
|
|
32
|
+
<% end %>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<%= render 'solid_queue_monitor/shared/filters',
|
|
37
|
+
action_path: @action_path,
|
|
38
|
+
filters: @filters.merge(queue_name: nil),
|
|
39
|
+
show_status: true %>
|
|
40
|
+
|
|
41
|
+
<% if @jobs[:records].empty? %>
|
|
42
|
+
<p class="empty-message">No jobs in this queue</p>
|
|
43
|
+
<% else %>
|
|
44
|
+
<% columns = [
|
|
45
|
+
{ sort_key: nil, label: 'ID' },
|
|
46
|
+
{ sort_key: :class_name, label: 'Job' },
|
|
47
|
+
{ sort_key: nil, label: 'Arguments' },
|
|
48
|
+
{ sort_key: nil, label: 'Status' },
|
|
49
|
+
{ sort_key: :created_at, label: 'Created At' },
|
|
50
|
+
{ sort_key: nil, label: 'Actions' }
|
|
51
|
+
] %>
|
|
52
|
+
|
|
53
|
+
<%= render 'solid_queue_monitor/shared/jobs_table',
|
|
54
|
+
jobs: @jobs[:records],
|
|
55
|
+
columns: columns,
|
|
56
|
+
sort: @sort,
|
|
57
|
+
filters: @filters,
|
|
58
|
+
row_partial: 'solid_queue_monitor/queues/job_row' %>
|
|
59
|
+
<% end %>
|
|
60
|
+
|
|
61
|
+
<%= render 'solid_queue_monitor/shared/pagination',
|
|
62
|
+
current_page: @jobs[:current_page],
|
|
63
|
+
total_pages: @jobs[:total_pages] %>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<tr>
|
|
2
|
+
<td><%= link_to job.job.class_name, job_path(job.job), class: 'job-class-link' %></td>
|
|
3
|
+
<td><%= queue_link(job.queue_name) %></td>
|
|
4
|
+
<td><%= job.priority %></td>
|
|
5
|
+
<td><%= format_arguments(job.job.arguments) %></td>
|
|
6
|
+
<td><%= format_datetime(job.created_at) %></td>
|
|
7
|
+
</tr>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<% content_for :title, 'Ready Jobs' %>
|
|
2
|
+
|
|
3
|
+
<%= render 'solid_queue_monitor/shared/filters',
|
|
4
|
+
action_path: @action_path,
|
|
5
|
+
filters: @filters,
|
|
6
|
+
show_status: false %>
|
|
7
|
+
|
|
8
|
+
<% columns = [
|
|
9
|
+
{ sort_key: :class_name, label: 'Job' },
|
|
10
|
+
{ sort_key: :queue_name, label: 'Queue' },
|
|
11
|
+
{ sort_key: :priority, label: 'Priority' },
|
|
12
|
+
{ sort_key: nil, label: 'Arguments' },
|
|
13
|
+
{ sort_key: :created_at, label: 'Created At' }
|
|
14
|
+
] %>
|
|
15
|
+
|
|
16
|
+
<%= render 'solid_queue_monitor/shared/jobs_table',
|
|
17
|
+
jobs: @ready_jobs[:records],
|
|
18
|
+
columns: columns,
|
|
19
|
+
sort: @sort,
|
|
20
|
+
filters: @filters,
|
|
21
|
+
row_partial: 'solid_queue_monitor/ready_jobs/row' %>
|
|
22
|
+
|
|
23
|
+
<%= render 'solid_queue_monitor/shared/pagination',
|
|
24
|
+
current_page: @ready_jobs[:current_page],
|
|
25
|
+
total_pages: @ready_jobs[:total_pages] %>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<% content_for :title, 'Recurring Jobs' %>
|
|
2
|
+
|
|
3
|
+
<%= render 'solid_queue_monitor/shared/filters',
|
|
4
|
+
action_path: @action_path,
|
|
5
|
+
filters: @filters,
|
|
6
|
+
show_status: false %>
|
|
7
|
+
|
|
8
|
+
<% columns = [
|
|
9
|
+
{ sort_key: :key, label: 'Key' },
|
|
10
|
+
{ sort_key: :class_name, label: 'Job' },
|
|
11
|
+
{ sort_key: nil, label: 'Schedule' },
|
|
12
|
+
{ sort_key: :queue_name, label: 'Queue' },
|
|
13
|
+
{ sort_key: :priority, label: 'Priority' },
|
|
14
|
+
{ sort_key: nil, label: 'Last Updated' }
|
|
15
|
+
] %>
|
|
16
|
+
|
|
17
|
+
<%= render 'solid_queue_monitor/shared/jobs_table',
|
|
18
|
+
jobs: @recurring_jobs[:records],
|
|
19
|
+
columns: columns,
|
|
20
|
+
sort: @sort,
|
|
21
|
+
filters: @filters,
|
|
22
|
+
row_partial: 'solid_queue_monitor/recurring_jobs/row' %>
|
|
23
|
+
|
|
24
|
+
<%= render 'solid_queue_monitor/shared/pagination',
|
|
25
|
+
current_page: @recurring_jobs[:current_page],
|
|
26
|
+
total_pages: @recurring_jobs[:total_pages] %>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<tr>
|
|
2
|
+
<td><input type="checkbox" name="job_ids[]" value="<%= job.id %>"></td>
|
|
3
|
+
<td><%= link_to job.job.class_name, job_path(job.job), class: 'job-class-link' %></td>
|
|
4
|
+
<td><%= queue_link(job.queue_name) %></td>
|
|
5
|
+
<td><%= format_datetime(job.scheduled_at) %></td>
|
|
6
|
+
<td><%= format_arguments(job.job.arguments) %></td>
|
|
7
|
+
</tr>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<% content_for :title, 'Scheduled Jobs' %>
|
|
2
|
+
|
|
3
|
+
<%= render 'solid_queue_monitor/shared/filters',
|
|
4
|
+
action_path: @action_path,
|
|
5
|
+
filters: @filters,
|
|
6
|
+
show_status: false %>
|
|
7
|
+
|
|
8
|
+
<div class="bulk-actions-bar">
|
|
9
|
+
<button type="button" class="action-button execute-button" id="execute-selected-top" data-action-url="<%= execute_jobs_path %>" disabled>
|
|
10
|
+
Execute Selected
|
|
11
|
+
</button>
|
|
12
|
+
<button type="button" class="action-button discard-button" id="reject-selected-top" data-action-url="<%= reject_jobs_path %>" disabled>
|
|
13
|
+
Reject Selected
|
|
14
|
+
</button>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<form id="scheduled-jobs-form" method="post">
|
|
18
|
+
<% columns = [
|
|
19
|
+
{ sort_key: nil, label: tag.input(type: 'checkbox', id: 'scheduled-jobs-select-all') },
|
|
20
|
+
{ sort_key: :class_name, label: 'Job' },
|
|
21
|
+
{ sort_key: :queue_name, label: 'Queue' },
|
|
22
|
+
{ sort_key: :scheduled_at, label: 'Scheduled At' },
|
|
23
|
+
{ sort_key: nil, label: 'Arguments' }
|
|
24
|
+
] %>
|
|
25
|
+
|
|
26
|
+
<%= render 'solid_queue_monitor/shared/jobs_table',
|
|
27
|
+
jobs: @scheduled_jobs[:records],
|
|
28
|
+
columns: columns,
|
|
29
|
+
sort: @sort,
|
|
30
|
+
filters: @filters,
|
|
31
|
+
row_partial: 'solid_queue_monitor/scheduled_jobs/row' %>
|
|
32
|
+
</form>
|
|
33
|
+
|
|
34
|
+
<%= render 'solid_queue_monitor/shared/pagination',
|
|
35
|
+
current_page: @scheduled_jobs[:current_page],
|
|
36
|
+
total_pages: @scheduled_jobs[:total_pages] %>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<tr>
|
|
2
|
+
<td><%= link_to item.class_name, job_path(item), class: 'job-class-link' %></td>
|
|
3
|
+
<td><%= queue_link(item.queue_name) %></td>
|
|
4
|
+
<td><%= format_arguments(item.arguments) %></td>
|
|
5
|
+
<td><span class="job-timestamp"><%= format_datetime(item.finished_at) %></span></td>
|
|
6
|
+
</tr>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<tr>
|
|
2
|
+
<td><%= link_to item.job.class_name, job_path(item.job), class: 'job-class-link' %></td>
|
|
3
|
+
<td><%= queue_link(item.job.queue_name) %></td>
|
|
4
|
+
<td><div class="error-message"><%= truncate(item.error.to_s, length: 100) %></div></td>
|
|
5
|
+
<td><span class="job-timestamp"><%= format_datetime(item.created_at) %></span></td>
|
|
6
|
+
</tr>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<% solid_queue_job = item.job %>
|
|
2
|
+
<% queue_name = item.respond_to?(:queue_name) && item.queue_name.present? ? item.queue_name : solid_queue_job.queue_name %>
|
|
3
|
+
<% time = item.respond_to?(:scheduled_at) && time_label == 'Scheduled for' ? item.scheduled_at : item.created_at %>
|
|
4
|
+
<tr>
|
|
5
|
+
<td><%= link_to solid_queue_job.class_name, job_path(solid_queue_job), class: 'job-class-link' %></td>
|
|
6
|
+
<td><%= queue_link(queue_name) %></td>
|
|
7
|
+
<td><%= format_arguments(solid_queue_job.arguments) %></td>
|
|
8
|
+
<td><span class="job-timestamp"><%= time_label %>: <%= format_datetime(time) %></span></td>
|
|
9
|
+
</tr>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<div class="search-results-section">
|
|
2
|
+
<h3><%= title %> (<%= items.size %>)</h3>
|
|
3
|
+
<div class="table-container">
|
|
4
|
+
<table>
|
|
5
|
+
<thead>
|
|
6
|
+
<tr>
|
|
7
|
+
<% if title == 'Recurring Tasks' %>
|
|
8
|
+
<th>Key</th><th>Class</th><th>Schedule</th><th>Queue</th>
|
|
9
|
+
<% elsif title == 'Failed Jobs' %>
|
|
10
|
+
<th>Job</th><th>Queue</th><th>Error</th><th>Failed At</th>
|
|
11
|
+
<% elsif title == 'Completed Jobs' %>
|
|
12
|
+
<th>Job</th><th>Queue</th><th>Arguments</th><th>Completed At</th>
|
|
13
|
+
<% else %>
|
|
14
|
+
<th>Job</th><th>Queue</th><th>Arguments</th><th>Time</th>
|
|
15
|
+
<% end %>
|
|
16
|
+
</tr>
|
|
17
|
+
</thead>
|
|
18
|
+
<tbody>
|
|
19
|
+
<% items.each do |item| %>
|
|
20
|
+
<%= render row_partial, item: item, time_label: local_assigns[:time_label] %>
|
|
21
|
+
<% end %>
|
|
22
|
+
</tbody>
|
|
23
|
+
</table>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<% content_for :title, 'Search' %>
|
|
2
|
+
|
|
3
|
+
<% if @query.blank? %>
|
|
4
|
+
<div class="empty-state">
|
|
5
|
+
<p>Enter a search term in the header to find jobs across all categories.</p>
|
|
6
|
+
</div>
|
|
7
|
+
<% elsif @total_count.zero? %>
|
|
8
|
+
<div class="empty-state">
|
|
9
|
+
<p>No results found for "<%= @query %>"</p>
|
|
10
|
+
<p class="results-summary">0 results</p>
|
|
11
|
+
</div>
|
|
12
|
+
<% else %>
|
|
13
|
+
<div class="results-summary">
|
|
14
|
+
<p>Found <%= @total_count %> <%= 'result'.pluralize(@total_count) %> for "<%= @query %>"</p>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<%= render 'section', title: 'Ready Jobs', items: @results[:ready], row_partial: 'solid_queue_monitor/search/job_row', time_label: 'Created at' if @results[:ready].any? %>
|
|
18
|
+
<%= render 'section', title: 'Scheduled Jobs', items: @results[:scheduled], row_partial: 'solid_queue_monitor/search/job_row', time_label: 'Scheduled for' if @results[:scheduled].any? %>
|
|
19
|
+
<%= render 'section', title: 'Failed Jobs', items: @results[:failed], row_partial: 'solid_queue_monitor/search/failed_row' if @results[:failed].any? %>
|
|
20
|
+
<%= render 'section', title: 'In Progress Jobs', items: @results[:in_progress], row_partial: 'solid_queue_monitor/search/job_row', time_label: 'Started at' if @results[:in_progress].any? %>
|
|
21
|
+
<%= render 'section', title: 'Completed Jobs', items: @results[:completed], row_partial: 'solid_queue_monitor/search/completed_row' if @results[:completed].any? %>
|
|
22
|
+
<%= render 'section', title: 'Recurring Tasks', items: @results[:recurring], row_partial: 'solid_queue_monitor/search/recurring_row' if @results[:recurring].any? %>
|
|
23
|
+
<% end %>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<% show_status = local_assigns.fetch(:show_status, true) %>
|
|
2
|
+
|
|
3
|
+
<div class="filter-form-container">
|
|
4
|
+
<%= form_with url: action_path, method: :get, local: true, html: { class: 'filter-form' } do |form| %>
|
|
5
|
+
<div class="filter-group">
|
|
6
|
+
<%= form.label :class_name, 'Job Class:' %>
|
|
7
|
+
<%= form.text_field :class_name,
|
|
8
|
+
value: filters[:class_name],
|
|
9
|
+
placeholder: 'Filter by class name' %>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<div class="filter-group">
|
|
13
|
+
<%= form.label :queue_name, 'Queue:' %>
|
|
14
|
+
<%= form.text_field :queue_name,
|
|
15
|
+
value: filters[:queue_name],
|
|
16
|
+
placeholder: 'Filter by queue' %>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<div class="filter-group">
|
|
20
|
+
<%= form.label :arguments, 'Arguments:' %>
|
|
21
|
+
<%= form.text_field :arguments,
|
|
22
|
+
value: filters[:arguments],
|
|
23
|
+
placeholder: 'Filter by arguments' %>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<% if show_status %>
|
|
27
|
+
<div class="filter-group">
|
|
28
|
+
<%= form.label :status, 'Status:' %>
|
|
29
|
+
<%= form.select :status,
|
|
30
|
+
options_for_select(
|
|
31
|
+
[
|
|
32
|
+
['All Statuses', ''],
|
|
33
|
+
['Completed', 'completed'],
|
|
34
|
+
['Failed', 'failed'],
|
|
35
|
+
['Scheduled', 'scheduled'],
|
|
36
|
+
['Pending', 'pending']
|
|
37
|
+
],
|
|
38
|
+
filters[:status]
|
|
39
|
+
) %>
|
|
40
|
+
</div>
|
|
41
|
+
<% end %>
|
|
42
|
+
|
|
43
|
+
<div class="filter-actions">
|
|
44
|
+
<%= form.submit 'Apply Filters', class: 'filter-button' %>
|
|
45
|
+
<%= link_to 'Reset', action_path, class: 'reset-button' %>
|
|
46
|
+
</div>
|
|
47
|
+
<% end %>
|
|
48
|
+
</div>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<%
|
|
2
|
+
message = @flash_message
|
|
3
|
+
message_type = @flash_type
|
|
4
|
+
begin
|
|
5
|
+
message ||= session[:flash_message]
|
|
6
|
+
message_type ||= session[:flash_type]
|
|
7
|
+
session.delete(:flash_message) if message
|
|
8
|
+
session.delete(:flash_type) if message_type
|
|
9
|
+
rescue StandardError
|
|
10
|
+
# Session not available.
|
|
11
|
+
end
|
|
12
|
+
%>
|
|
13
|
+
<% if message %>
|
|
14
|
+
<div id="flash-message" class="message <%= message_class(message_type) %>">
|
|
15
|
+
<%= message %>
|
|
16
|
+
</div>
|
|
17
|
+
<% end %>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<% nav_items = [
|
|
2
|
+
{ path: root_path, label: 'Overview', match: 'Overview' },
|
|
3
|
+
{ path: ready_jobs_path, label: 'Ready Jobs', match: 'Ready Jobs' },
|
|
4
|
+
{ path: in_progress_jobs_path, label: 'In Progress Jobs', match: 'In Progress' },
|
|
5
|
+
{ path: scheduled_jobs_path, label: 'Scheduled Jobs', match: 'Scheduled Jobs' },
|
|
6
|
+
{ path: recurring_jobs_path, label: 'Recurring Jobs', match: 'Recurring Jobs' },
|
|
7
|
+
{ path: failed_jobs_path, label: 'Failed Jobs', match: 'Failed Jobs' },
|
|
8
|
+
{ path: queues_path, label: 'Queues', match: 'Queues' },
|
|
9
|
+
{ path: workers_path, label: 'Workers', match: 'Workers' }
|
|
10
|
+
] %>
|
|
11
|
+
|
|
12
|
+
<header>
|
|
13
|
+
<div class="header-top">
|
|
14
|
+
<h1>
|
|
15
|
+
<%= link_to 'Solid Queue Monitor', root_path, class: 'header-title-link' %>
|
|
16
|
+
</h1>
|
|
17
|
+
|
|
18
|
+
<%= form_with url: search_path, method: :get, local: true, html: { class: 'header-search-form' } do |form| %>
|
|
19
|
+
<%= form.text_field :q,
|
|
20
|
+
value: params[:q],
|
|
21
|
+
placeholder: 'Search by class, queue, job ID, or error...',
|
|
22
|
+
class: 'header-search-input' %>
|
|
23
|
+
<button type="submit" class="header-search-button" title="Search">
|
|
24
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
25
|
+
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
26
|
+
<circle cx="11" cy="11" r="8"></circle>
|
|
27
|
+
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
|
28
|
+
</svg>
|
|
29
|
+
</button>
|
|
30
|
+
<% end %>
|
|
31
|
+
|
|
32
|
+
<div class="header-controls">
|
|
33
|
+
<% if SolidQueueMonitor.auto_refresh_enabled %>
|
|
34
|
+
<% interval = SolidQueueMonitor.auto_refresh_interval %>
|
|
35
|
+
<div class="auto-refresh-container"
|
|
36
|
+
title="Auto-refresh every <%= interval %>s"
|
|
37
|
+
data-tooltip="Auto-refresh: Dashboard updates automatically every <%= interval %> seconds. Toggle to enable/disable.">
|
|
38
|
+
<span class="auto-refresh-indicator" id="auto-refresh-indicator"></span>
|
|
39
|
+
<span class="auto-refresh-countdown" id="auto-refresh-countdown"><%= interval %>s</span>
|
|
40
|
+
<label class="auto-refresh-switch" title="Toggle auto-refresh">
|
|
41
|
+
<input type="checkbox" id="auto-refresh-toggle" checked>
|
|
42
|
+
<span class="switch-slider"></span>
|
|
43
|
+
</label>
|
|
44
|
+
<button class="refresh-now-btn" id="refresh-now-btn" title="Refresh now">
|
|
45
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
46
|
+
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
47
|
+
<path d="M21 2v6h-6M3 12a9 9 0 0 1 15-6.7L21 8M3 22v-6h6M21 12a9 9 0 0 1-15 6.7L3 16"/>
|
|
48
|
+
</svg>
|
|
49
|
+
</button>
|
|
50
|
+
</div>
|
|
51
|
+
<% end %>
|
|
52
|
+
|
|
53
|
+
<button class="theme-toggle-btn" id="theme-toggle-btn" title="Toggle dark mode">
|
|
54
|
+
<svg class="theme-icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
55
|
+
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
56
|
+
<circle cx="12" cy="12" r="5"></circle>
|
|
57
|
+
<line x1="12" y1="1" x2="12" y2="3"></line>
|
|
58
|
+
<line x1="12" y1="21" x2="12" y2="23"></line>
|
|
59
|
+
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
|
|
60
|
+
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
|
|
61
|
+
<line x1="1" y1="12" x2="3" y2="12"></line>
|
|
62
|
+
<line x1="21" y1="12" x2="23" y2="12"></line>
|
|
63
|
+
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
|
|
64
|
+
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
|
|
65
|
+
</svg>
|
|
66
|
+
<svg class="theme-icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
67
|
+
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
68
|
+
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
|
|
69
|
+
</svg>
|
|
70
|
+
</button>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<nav class="navigation">
|
|
75
|
+
<% nav_items.each do |item| %>
|
|
76
|
+
<%= link_to item[:label],
|
|
77
|
+
item[:path],
|
|
78
|
+
class: class_names('nav-link', active: content_for(:title).to_s.include?(item[:match])) %>
|
|
79
|
+
<% end %>
|
|
80
|
+
</nav>
|
|
81
|
+
</header>
|