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
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SolidQueueMonitor
|
|
4
|
-
class JobsPresenter < BasePresenter
|
|
5
|
-
include Rails.application.routes.url_helpers
|
|
6
|
-
include SolidQueueMonitor::Engine.routes.url_helpers
|
|
7
|
-
|
|
8
|
-
def initialize(jobs, current_page: 1, total_pages: 1, filters: {}, sort: {})
|
|
9
|
-
@jobs = jobs
|
|
10
|
-
@current_page = current_page
|
|
11
|
-
@total_pages = total_pages
|
|
12
|
-
@filters = filters
|
|
13
|
-
@sort = sort
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def render
|
|
17
|
-
<<-HTML
|
|
18
|
-
<div class="section-wrapper">
|
|
19
|
-
<div class="section">
|
|
20
|
-
<h3>Recent Jobs</h3>
|
|
21
|
-
#{generate_filter_form}
|
|
22
|
-
#{generate_table}
|
|
23
|
-
#{generate_pagination(@current_page, @total_pages)}
|
|
24
|
-
</div>
|
|
25
|
-
</div>
|
|
26
|
-
HTML
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
private
|
|
30
|
-
|
|
31
|
-
def generate_filter_form
|
|
32
|
-
<<-HTML
|
|
33
|
-
<div class="filter-form-container">
|
|
34
|
-
<form method="get" action="" class="filter-form">
|
|
35
|
-
<div class="filter-group">
|
|
36
|
-
<label for="class_name">Job Class:</label>
|
|
37
|
-
<input type="text" name="class_name" id="class_name" value="#{@filters[:class_name]}" placeholder="Filter by class name">
|
|
38
|
-
</div>
|
|
39
|
-
|
|
40
|
-
<div class="filter-group">
|
|
41
|
-
<label for="queue_name">Queue:</label>
|
|
42
|
-
<input type="text" name="queue_name" id="queue_name" value="#{@filters[:queue_name]}" placeholder="Filter by queue">
|
|
43
|
-
</div>
|
|
44
|
-
|
|
45
|
-
<div class="filter-group">
|
|
46
|
-
<label for="arguments">Arguments:</label>
|
|
47
|
-
<input type="text" name="arguments" id="arguments" value="#{@filters[:arguments]}" placeholder="Filter by arguments">
|
|
48
|
-
</div>
|
|
49
|
-
|
|
50
|
-
<div class="filter-group">
|
|
51
|
-
<label for="status">Status:</label>
|
|
52
|
-
<select name="status" id="status">
|
|
53
|
-
<option value="">All Statuses</option>
|
|
54
|
-
<option value="completed" #{@filters[:status] == 'completed' ? 'selected' : ''}>Completed</option>
|
|
55
|
-
<option value="failed" #{@filters[:status] == 'failed' ? 'selected' : ''}>Failed</option>
|
|
56
|
-
<option value="scheduled" #{@filters[:status] == 'scheduled' ? 'selected' : ''}>Scheduled</option>
|
|
57
|
-
<option value="pending" #{@filters[:status] == 'pending' ? 'selected' : ''}>Pending</option>
|
|
58
|
-
</select>
|
|
59
|
-
</div>
|
|
60
|
-
|
|
61
|
-
<div class="filter-actions">
|
|
62
|
-
<button type="submit" class="filter-button">Apply Filters</button>
|
|
63
|
-
<a href="#{root_path}" class="reset-button">Reset</a>
|
|
64
|
-
</div>
|
|
65
|
-
</form>
|
|
66
|
-
</div>
|
|
67
|
-
HTML
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def generate_table
|
|
71
|
-
<<-HTML
|
|
72
|
-
<div class="table-container">
|
|
73
|
-
<table>
|
|
74
|
-
<thead>
|
|
75
|
-
<tr>
|
|
76
|
-
<th>ID</th>
|
|
77
|
-
#{sortable_header('class_name', 'Job')}
|
|
78
|
-
#{sortable_header('queue_name', 'Queue')}
|
|
79
|
-
<th>Arguments</th>
|
|
80
|
-
<th>Status</th>
|
|
81
|
-
#{sortable_header('created_at', 'Created At')}
|
|
82
|
-
<th>Actions</th>
|
|
83
|
-
</tr>
|
|
84
|
-
</thead>
|
|
85
|
-
<tbody>
|
|
86
|
-
#{@jobs.map { |job| generate_row(job) }.join}
|
|
87
|
-
</tbody>
|
|
88
|
-
</table>
|
|
89
|
-
</div>
|
|
90
|
-
HTML
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def generate_row(job)
|
|
94
|
-
status = job_status(job)
|
|
95
|
-
|
|
96
|
-
# Build the row HTML
|
|
97
|
-
row_html = <<-HTML
|
|
98
|
-
<tr>
|
|
99
|
-
<td><a href="#{job_path(job)}" class="job-class-link">#{job.id}</a></td>
|
|
100
|
-
<td><a href="#{job_path(job)}" class="job-class-link">#{job.class_name}</a></td>
|
|
101
|
-
<td>#{queue_link(job.queue_name)}</td>
|
|
102
|
-
<td>#{format_arguments(job.arguments)}</td>
|
|
103
|
-
<td><span class='status-badge status-#{status}'>#{status}</span></td>
|
|
104
|
-
<td>#{format_datetime(job.created_at)}</td>
|
|
105
|
-
HTML
|
|
106
|
-
|
|
107
|
-
# Add actions column only for failed jobs
|
|
108
|
-
if status == 'failed'
|
|
109
|
-
# Find the failed execution record for this job
|
|
110
|
-
failed_execution = SolidQueue::FailedExecution.find_by(job_id: job.id)
|
|
111
|
-
|
|
112
|
-
row_html += if failed_execution
|
|
113
|
-
<<-HTML
|
|
114
|
-
<td class="actions-cell">
|
|
115
|
-
<div class="job-actions">
|
|
116
|
-
<form method="post" action="#{retry_failed_job_path(id: failed_execution.id)}" class="inline-form">
|
|
117
|
-
<input type="hidden" name="redirect_to" value="#{root_path}">
|
|
118
|
-
<button type="submit" class="action-button retry-button">Retry</button>
|
|
119
|
-
</form>
|
|
120
|
-
|
|
121
|
-
<form method="post" action="#{discard_failed_job_path(id: failed_execution.id)}" class="inline-form"
|
|
122
|
-
onsubmit="return confirm('Are you sure you want to discard this job?');">
|
|
123
|
-
<input type="hidden" name="redirect_to" value="#{root_path}">
|
|
124
|
-
<button type="submit" class="action-button discard-button">Discard</button>
|
|
125
|
-
</form>
|
|
126
|
-
</div>
|
|
127
|
-
</td>
|
|
128
|
-
HTML
|
|
129
|
-
else
|
|
130
|
-
'<td></td>'
|
|
131
|
-
end
|
|
132
|
-
else
|
|
133
|
-
row_html += '<td></td>'
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
row_html += '</tr>'
|
|
137
|
-
row_html
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
def job_status(job)
|
|
141
|
-
SolidQueueMonitor::StatusCalculator.new(job).calculate
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
|
-
end
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SolidQueueMonitor
|
|
4
|
-
class QueueDetailsPresenter < BasePresenter
|
|
5
|
-
def initialize(queue_name:, paused:, jobs:, counts:, current_page: 1, total_pages: 1, filters: {}, sort: {})
|
|
6
|
-
@queue_name = queue_name
|
|
7
|
-
@paused = paused
|
|
8
|
-
@jobs = jobs
|
|
9
|
-
@counts = counts
|
|
10
|
-
@current_page = current_page
|
|
11
|
-
@total_pages = total_pages
|
|
12
|
-
@filters = filters
|
|
13
|
-
@sort = sort
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def render
|
|
17
|
-
section_wrapper("Queue: #{@queue_name}",
|
|
18
|
-
render_header + render_stats_cards + generate_filter_form + generate_table + generate_pagination(@current_page, @total_pages))
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
private
|
|
22
|
-
|
|
23
|
-
def render_header
|
|
24
|
-
<<-HTML
|
|
25
|
-
<div class="section-header-row">
|
|
26
|
-
<div class="section-header-left">
|
|
27
|
-
#{status_badge}
|
|
28
|
-
</div>
|
|
29
|
-
<div class="section-header-right">
|
|
30
|
-
#{action_button}
|
|
31
|
-
</div>
|
|
32
|
-
</div>
|
|
33
|
-
HTML
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def status_badge
|
|
37
|
-
if @paused
|
|
38
|
-
'<span class="status-badge status-paused">Paused</span>'
|
|
39
|
-
else
|
|
40
|
-
'<span class="status-badge status-active">Active</span>'
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def action_button
|
|
45
|
-
if @paused
|
|
46
|
-
<<-HTML
|
|
47
|
-
<form action="#{resume_queue_path}" method="post" class="inline-form">
|
|
48
|
-
<input type="hidden" name="queue_name" value="#{@queue_name}">
|
|
49
|
-
<input type="hidden" name="redirect_to" value="#{queue_details_path(queue_name: @queue_name)}">
|
|
50
|
-
<button type="submit" class="action-button resume-button">Resume Queue</button>
|
|
51
|
-
</form>
|
|
52
|
-
HTML
|
|
53
|
-
else
|
|
54
|
-
<<-HTML
|
|
55
|
-
<form action="#{pause_queue_path}" method="post" class="inline-form"
|
|
56
|
-
onsubmit="return confirm('Are you sure you want to pause this queue?');">
|
|
57
|
-
<input type="hidden" name="queue_name" value="#{@queue_name}">
|
|
58
|
-
<input type="hidden" name="redirect_to" value="#{queue_details_path(queue_name: @queue_name)}">
|
|
59
|
-
<button type="submit" class="action-button pause-button">Pause Queue</button>
|
|
60
|
-
</form>
|
|
61
|
-
HTML
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def render_stats_cards
|
|
66
|
-
<<-HTML
|
|
67
|
-
<div class="stats-container">
|
|
68
|
-
<div class="stats">
|
|
69
|
-
#{generate_stat_card('Total Jobs', @counts[:total])}
|
|
70
|
-
#{generate_stat_card('Ready', @counts[:ready])}
|
|
71
|
-
#{generate_stat_card('Scheduled', @counts[:scheduled])}
|
|
72
|
-
#{generate_stat_card('In Progress', @counts[:in_progress])}
|
|
73
|
-
#{generate_stat_card('Completed', @counts[:completed])}
|
|
74
|
-
#{generate_stat_card('Failed', @counts[:failed])}
|
|
75
|
-
</div>
|
|
76
|
-
</div>
|
|
77
|
-
HTML
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def generate_stat_card(title, value)
|
|
81
|
-
<<-HTML
|
|
82
|
-
<div class="stat-card">
|
|
83
|
-
<h3>#{title}</h3>
|
|
84
|
-
<p>#{value}</p>
|
|
85
|
-
</div>
|
|
86
|
-
HTML
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def generate_filter_form
|
|
90
|
-
<<-HTML
|
|
91
|
-
<div class="filter-form-container">
|
|
92
|
-
<form method="get" action="#{queue_details_path(queue_name: @queue_name)}" class="filter-form">
|
|
93
|
-
<div class="filter-group">
|
|
94
|
-
<label for="class_name">Job Class:</label>
|
|
95
|
-
<input type="text" name="class_name" id="class_name" value="#{@filters[:class_name]}" placeholder="Filter by class name">
|
|
96
|
-
</div>
|
|
97
|
-
|
|
98
|
-
<div class="filter-group">
|
|
99
|
-
<label for="arguments">Arguments:</label>
|
|
100
|
-
<input type="text" name="arguments" id="arguments" value="#{@filters[:arguments]}" placeholder="Filter by arguments">
|
|
101
|
-
</div>
|
|
102
|
-
|
|
103
|
-
<div class="filter-group">
|
|
104
|
-
<label for="status">Status:</label>
|
|
105
|
-
<select name="status" id="status">
|
|
106
|
-
<option value="">All Statuses</option>
|
|
107
|
-
<option value="completed" #{@filters[:status] == 'completed' ? 'selected' : ''}>Completed</option>
|
|
108
|
-
<option value="failed" #{@filters[:status] == 'failed' ? 'selected' : ''}>Failed</option>
|
|
109
|
-
<option value="scheduled" #{@filters[:status] == 'scheduled' ? 'selected' : ''}>Scheduled</option>
|
|
110
|
-
<option value="pending" #{@filters[:status] == 'pending' ? 'selected' : ''}>Pending</option>
|
|
111
|
-
<option value="in_progress" #{@filters[:status] == 'in_progress' ? 'selected' : ''}>In Progress</option>
|
|
112
|
-
</select>
|
|
113
|
-
</div>
|
|
114
|
-
|
|
115
|
-
<div class="filter-actions">
|
|
116
|
-
<button type="submit" class="filter-button">Apply Filters</button>
|
|
117
|
-
<a href="#{queue_details_path(queue_name: @queue_name)}" class="reset-button">Reset</a>
|
|
118
|
-
</div>
|
|
119
|
-
</form>
|
|
120
|
-
</div>
|
|
121
|
-
HTML
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def generate_table
|
|
125
|
-
return '<p class="empty-message">No jobs in this queue</p>' if @jobs.empty?
|
|
126
|
-
|
|
127
|
-
<<-HTML
|
|
128
|
-
<div class="table-container">
|
|
129
|
-
<table>
|
|
130
|
-
<thead>
|
|
131
|
-
<tr>
|
|
132
|
-
<th>ID</th>
|
|
133
|
-
#{sortable_header('class_name', 'Job')}
|
|
134
|
-
<th>Arguments</th>
|
|
135
|
-
<th>Status</th>
|
|
136
|
-
#{sortable_header('created_at', 'Created At')}
|
|
137
|
-
<th>Actions</th>
|
|
138
|
-
</tr>
|
|
139
|
-
</thead>
|
|
140
|
-
<tbody>
|
|
141
|
-
#{@jobs.map { |job| generate_row(job) }.join}
|
|
142
|
-
</tbody>
|
|
143
|
-
</table>
|
|
144
|
-
</div>
|
|
145
|
-
HTML
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def generate_row(job)
|
|
149
|
-
status = job_status(job)
|
|
150
|
-
|
|
151
|
-
row_html = <<-HTML
|
|
152
|
-
<tr>
|
|
153
|
-
<td><a href="#{job_path(job)}" class="job-class-link">#{job.id}</a></td>
|
|
154
|
-
<td><a href="#{job_path(job)}" class="job-class-link">#{job.class_name}</a></td>
|
|
155
|
-
<td>#{format_arguments(job.arguments)}</td>
|
|
156
|
-
<td><span class="status-badge status-#{status}">#{status}</span></td>
|
|
157
|
-
<td>#{format_datetime(job.created_at)}</td>
|
|
158
|
-
HTML
|
|
159
|
-
|
|
160
|
-
# Add actions column for failed jobs
|
|
161
|
-
if status == 'failed'
|
|
162
|
-
failed_execution = SolidQueue::FailedExecution.find_by(job_id: job.id)
|
|
163
|
-
|
|
164
|
-
row_html += if failed_execution
|
|
165
|
-
<<-HTML
|
|
166
|
-
<td class="actions-cell">
|
|
167
|
-
<div class="job-actions">
|
|
168
|
-
<form method="post" action="#{retry_failed_job_path(id: failed_execution.id)}" class="inline-form">
|
|
169
|
-
<input type="hidden" name="redirect_to" value="#{queue_details_path(queue_name: @queue_name)}">
|
|
170
|
-
<button type="submit" class="action-button retry-button">Retry</button>
|
|
171
|
-
</form>
|
|
172
|
-
<form method="post" action="#{discard_failed_job_path(id: failed_execution.id)}" class="inline-form"
|
|
173
|
-
onsubmit="return confirm('Are you sure you want to discard this job?');">
|
|
174
|
-
<input type="hidden" name="redirect_to" value="#{queue_details_path(queue_name: @queue_name)}">
|
|
175
|
-
<button type="submit" class="action-button discard-button">Discard</button>
|
|
176
|
-
</form>
|
|
177
|
-
</div>
|
|
178
|
-
</td>
|
|
179
|
-
HTML
|
|
180
|
-
else
|
|
181
|
-
'<td></td>'
|
|
182
|
-
end
|
|
183
|
-
else
|
|
184
|
-
row_html += '<td></td>'
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
row_html += '</tr>'
|
|
188
|
-
row_html
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
def job_status(job)
|
|
192
|
-
SolidQueueMonitor::StatusCalculator.new(job).calculate
|
|
193
|
-
end
|
|
194
|
-
end
|
|
195
|
-
end
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SolidQueueMonitor
|
|
4
|
-
class QueuesPresenter < BasePresenter
|
|
5
|
-
def initialize(records, paused_queues = [], sort: {}, queue_stats: {})
|
|
6
|
-
@records = records
|
|
7
|
-
@paused_queues = paused_queues
|
|
8
|
-
@sort = sort
|
|
9
|
-
@queue_stats = queue_stats
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def render
|
|
13
|
-
section_wrapper('Queues', generate_table)
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
private
|
|
17
|
-
|
|
18
|
-
def generate_table
|
|
19
|
-
<<-HTML
|
|
20
|
-
<div class="table-container">
|
|
21
|
-
<table>
|
|
22
|
-
<thead>
|
|
23
|
-
<tr>
|
|
24
|
-
#{sortable_header('queue_name', 'Queue Name')}
|
|
25
|
-
<th>Status</th>
|
|
26
|
-
#{sortable_header('job_count', 'Total Jobs')}
|
|
27
|
-
<th>Ready Jobs</th>
|
|
28
|
-
<th>Scheduled Jobs</th>
|
|
29
|
-
<th>Failed Jobs</th>
|
|
30
|
-
<th>Actions</th>
|
|
31
|
-
</tr>
|
|
32
|
-
</thead>
|
|
33
|
-
<tbody>
|
|
34
|
-
#{@records.map { |queue| generate_row(queue) }.join}
|
|
35
|
-
</tbody>
|
|
36
|
-
</table>
|
|
37
|
-
</div>
|
|
38
|
-
HTML
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def generate_row(queue)
|
|
42
|
-
queue_name = queue.queue_name || 'default'
|
|
43
|
-
paused = @paused_queues.include?(queue_name)
|
|
44
|
-
|
|
45
|
-
<<-HTML
|
|
46
|
-
<tr class="#{paused ? 'queue-paused' : ''}">
|
|
47
|
-
<td>#{queue_link(queue_name)}</td>
|
|
48
|
-
<td>#{status_badge(paused)}</td>
|
|
49
|
-
<td>#{queue.job_count}</td>
|
|
50
|
-
<td>#{@queue_stats.dig(:ready, queue_name) || 0}</td>
|
|
51
|
-
<td>#{@queue_stats.dig(:scheduled, queue_name) || 0}</td>
|
|
52
|
-
<td>#{@queue_stats.dig(:failed, queue_name) || 0}</td>
|
|
53
|
-
<td class="actions-cell">#{action_button(queue_name, paused)}</td>
|
|
54
|
-
</tr>
|
|
55
|
-
HTML
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def status_badge(paused)
|
|
59
|
-
if paused
|
|
60
|
-
'<span class="status-badge status-paused">Paused</span>'
|
|
61
|
-
else
|
|
62
|
-
'<span class="status-badge status-active">Active</span>'
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def action_button(queue_name, paused)
|
|
67
|
-
if paused
|
|
68
|
-
<<-HTML
|
|
69
|
-
<form action="#{resume_queue_path}" method="post" class="inline-form">
|
|
70
|
-
<input type="hidden" name="queue_name" value="#{queue_name}">
|
|
71
|
-
<button type="submit" class="action-button resume-button" title="Resume queue processing">
|
|
72
|
-
Resume
|
|
73
|
-
</button>
|
|
74
|
-
</form>
|
|
75
|
-
HTML
|
|
76
|
-
else
|
|
77
|
-
<<-HTML
|
|
78
|
-
<form action="#{pause_queue_path}" method="post" class="inline-form"
|
|
79
|
-
onsubmit="return confirm('Are you sure you want to pause the #{queue_name} queue? Workers will stop processing jobs from this queue.');">
|
|
80
|
-
<input type="hidden" name="queue_name" value="#{queue_name}">
|
|
81
|
-
<button type="submit" class="action-button pause-button" title="Pause queue processing">
|
|
82
|
-
Pause
|
|
83
|
-
</button>
|
|
84
|
-
</form>
|
|
85
|
-
HTML
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
end
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SolidQueueMonitor
|
|
4
|
-
class ReadyJobsPresenter < BasePresenter
|
|
5
|
-
def initialize(jobs, current_page: 1, total_pages: 1, filters: {}, sort: {})
|
|
6
|
-
@jobs = jobs
|
|
7
|
-
@current_page = current_page
|
|
8
|
-
@total_pages = total_pages
|
|
9
|
-
@filters = filters
|
|
10
|
-
@sort = sort
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def render
|
|
14
|
-
section_wrapper('Ready Jobs',
|
|
15
|
-
generate_filter_form + generate_table + generate_pagination(@current_page, @total_pages))
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
private
|
|
19
|
-
|
|
20
|
-
def generate_filter_form
|
|
21
|
-
<<-HTML
|
|
22
|
-
<div class="filter-form-container">
|
|
23
|
-
<form method="get" action="" class="filter-form">
|
|
24
|
-
<div class="filter-group">
|
|
25
|
-
<label for="class_name">Job Class:</label>
|
|
26
|
-
<input type="text" name="class_name" id="class_name" value="#{@filters[:class_name]}" placeholder="Filter by class name">
|
|
27
|
-
</div>
|
|
28
|
-
|
|
29
|
-
<div class="filter-group">
|
|
30
|
-
<label for="queue_name">Queue:</label>
|
|
31
|
-
<input type="text" name="queue_name" id="queue_name" value="#{@filters[:queue_name]}" placeholder="Filter by queue">
|
|
32
|
-
</div>
|
|
33
|
-
|
|
34
|
-
<div class="filter-group">
|
|
35
|
-
<label for="arguments">Arguments:</label>
|
|
36
|
-
<input type="text" name="arguments" id="arguments" value="#{@filters[:arguments]}" placeholder="Filter by arguments">
|
|
37
|
-
</div>
|
|
38
|
-
|
|
39
|
-
<div class="filter-actions">
|
|
40
|
-
<button type="submit" class="filter-button">Apply Filters</button>
|
|
41
|
-
<a href="#{ready_jobs_path}" class="reset-button">Reset</a>
|
|
42
|
-
</div>
|
|
43
|
-
</form>
|
|
44
|
-
</div>
|
|
45
|
-
HTML
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def generate_table
|
|
49
|
-
<<-HTML
|
|
50
|
-
<div class="table-container">
|
|
51
|
-
<table>
|
|
52
|
-
<thead>
|
|
53
|
-
<tr>
|
|
54
|
-
#{sortable_header('class_name', 'Job')}
|
|
55
|
-
#{sortable_header('queue_name', 'Queue')}
|
|
56
|
-
#{sortable_header('priority', 'Priority')}
|
|
57
|
-
<th>Arguments</th>
|
|
58
|
-
#{sortable_header('created_at', 'Created At')}
|
|
59
|
-
</tr>
|
|
60
|
-
</thead>
|
|
61
|
-
<tbody>
|
|
62
|
-
#{@jobs.map { |execution| generate_row(execution) }.join}
|
|
63
|
-
</tbody>
|
|
64
|
-
</table>
|
|
65
|
-
</div>
|
|
66
|
-
HTML
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def generate_row(execution)
|
|
70
|
-
<<-HTML
|
|
71
|
-
<tr>
|
|
72
|
-
<td><a href="#{job_path(execution.job)}" class="job-class-link">#{execution.job.class_name}</a></td>
|
|
73
|
-
<td>#{queue_link(execution.queue_name)}</td>
|
|
74
|
-
<td>#{execution.priority}</td>
|
|
75
|
-
<td>#{format_arguments(execution.job.arguments)}</td>
|
|
76
|
-
<td>#{format_datetime(execution.created_at)}</td>
|
|
77
|
-
</tr>
|
|
78
|
-
HTML
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SolidQueueMonitor
|
|
4
|
-
class RecurringJobsPresenter < BasePresenter
|
|
5
|
-
include Rails.application.routes.url_helpers
|
|
6
|
-
include SolidQueueMonitor::Engine.routes.url_helpers
|
|
7
|
-
|
|
8
|
-
def initialize(jobs, current_page: 1, total_pages: 1, filters: {}, sort: {})
|
|
9
|
-
@jobs = jobs
|
|
10
|
-
@current_page = current_page
|
|
11
|
-
@total_pages = total_pages
|
|
12
|
-
@filters = filters
|
|
13
|
-
@sort = sort
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def render
|
|
17
|
-
section_wrapper('Recurring Jobs',
|
|
18
|
-
generate_filter_form + generate_table + generate_pagination(@current_page, @total_pages))
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
private
|
|
22
|
-
|
|
23
|
-
def generate_filter_form
|
|
24
|
-
<<-HTML
|
|
25
|
-
<div class="filter-form-container">
|
|
26
|
-
<form method="get" action="" class="filter-form">
|
|
27
|
-
<div class="filter-group">
|
|
28
|
-
<label for="class_name">Job Class:</label>
|
|
29
|
-
<input type="text" name="class_name" id="class_name" value="#{@filters[:class_name]}" placeholder="Filter by class name">
|
|
30
|
-
</div>
|
|
31
|
-
|
|
32
|
-
<div class="filter-group">
|
|
33
|
-
<label for="queue_name">Queue:</label>
|
|
34
|
-
<input type="text" name="queue_name" id="queue_name" value="#{@filters[:queue_name]}" placeholder="Filter by queue">
|
|
35
|
-
</div>
|
|
36
|
-
|
|
37
|
-
<div class="filter-actions">
|
|
38
|
-
<button type="submit" class="filter-button">Apply Filters</button>
|
|
39
|
-
<a href="#{recurring_jobs_path}" class="reset-button">Reset</a>
|
|
40
|
-
</div>
|
|
41
|
-
</form>
|
|
42
|
-
</div>
|
|
43
|
-
HTML
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def generate_table
|
|
47
|
-
<<-HTML
|
|
48
|
-
<div class="table-container">
|
|
49
|
-
<table>
|
|
50
|
-
<thead>
|
|
51
|
-
<tr>
|
|
52
|
-
#{sortable_header('key', 'Key')}
|
|
53
|
-
#{sortable_header('class_name', 'Job')}
|
|
54
|
-
<th>Schedule</th>
|
|
55
|
-
#{sortable_header('queue_name', 'Queue')}
|
|
56
|
-
#{sortable_header('priority', 'Priority')}
|
|
57
|
-
<th>Last Updated</th>
|
|
58
|
-
</tr>
|
|
59
|
-
</thead>
|
|
60
|
-
<tbody>
|
|
61
|
-
#{@jobs.map { |task| generate_row(task) }.join}
|
|
62
|
-
</tbody>
|
|
63
|
-
</table>
|
|
64
|
-
</div>
|
|
65
|
-
HTML
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def generate_row(task)
|
|
69
|
-
<<-HTML
|
|
70
|
-
<tr>
|
|
71
|
-
<td>#{task.key}</td>
|
|
72
|
-
<td>#{task.class_name}</td>
|
|
73
|
-
<td>#{task.schedule}</td>
|
|
74
|
-
<td>#{queue_link(task.queue_name)}</td>
|
|
75
|
-
<td>#{task.priority || 'Default'}</td>
|
|
76
|
-
<td>#{format_datetime(task.updated_at)}</td>
|
|
77
|
-
</tr>
|
|
78
|
-
HTML
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|