que-view 0.3.1 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +5 -4
- data/app/assets/stylesheets/que/view/application.css +22 -1
- data/app/controllers/que/view/jobs_controller.rb +1 -1
- data/app/controllers/que/view/queue_metrics_controller.rb +17 -0
- data/app/controllers/que/view/welcome_controller.rb +1 -0
- data/app/views/layouts/que/view/application.html.erb +3 -0
- data/app/views/que/view/jobs/index.html.erb +58 -61
- data/app/views/que/view/queue_metrics/index.html.erb +29 -0
- data/app/views/que/view/welcome/index.html.erb +26 -0
- data/config/routes.rb +1 -0
- data/lib/que/view/dsl.rb +50 -1
- data/lib/que/view/version.rb +1 -1
- data/lib/que/view.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1b7b44890b63b063c130537e5fcc117f40bf1236dd89b156ca48e87a78d71248
|
4
|
+
data.tar.gz: 629fd230eb265fdbe7a41f0aec19e37f4047d797750eb635612687cceb7d5af9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1412a8a378aaa977fd92b40c79dad153267cbf50d1e7ac7409380469106269e5b96484d12193ac57ee78187ad172e79a61783e54e0948aff9242e4947bf6d381
|
7
|
+
data.tar.gz: ac32754839e4105584d330cc34a19cb5fccb48f8ce60afd58c1c3f5bb9b933a7ab4bd8616e109d7a2a5b91b384dff878b4a5699ee576f5d5fa1822c60d4392b0
|
data/README.md
CHANGED
@@ -4,12 +4,13 @@ SQL queries came from Que::Web, some styling from there too.
|
|
4
4
|
|
5
5
|
Benefits for using this one:
|
6
6
|
- no Sinatra (que-web based on Sinatra),
|
7
|
-
- no Foundation for styles,
|
8
|
-
- no jQuery,
|
7
|
+
- no Foundation for styles (and no external styles at all),
|
8
|
+
- no jQuery (and no java scripts at all),
|
9
9
|
- Que::Web was last updated 2 years ago.
|
10
10
|
|
11
|
-
<img width="
|
12
|
-
<img width="
|
11
|
+
<img width="1731" alt="Снимок экрана 2024-03-03 в 08 28 09" src="https://github.com/kortirso/que-view/assets/6195394/fdaf315d-6c6e-40ee-a60f-cd740cc7ec93">
|
12
|
+
<img width="1734" alt="Снимок экрана 2024-03-03 в 08 27 47" src="https://github.com/kortirso/que-view/assets/6195394/e45d334e-7637-41f8-af16-d6a9ac35f263">
|
13
|
+
<img width="1730" alt="Снимок экрана 2024-03-03 в 23 09 57" src="https://github.com/kortirso/que-view/assets/6195394/d790066f-3e96-4775-afd6-018750d0afd3">
|
13
14
|
|
14
15
|
|
15
16
|
## Installation
|
@@ -29,6 +29,10 @@ body {
|
|
29
29
|
background: #fafafa;
|
30
30
|
}
|
31
31
|
|
32
|
+
h1 {
|
33
|
+
margin-top: 0;
|
34
|
+
}
|
35
|
+
|
32
36
|
.row {
|
33
37
|
display: flex;
|
34
38
|
}
|
@@ -112,7 +116,7 @@ body {
|
|
112
116
|
}
|
113
117
|
|
114
118
|
.pagination {
|
115
|
-
margin:
|
119
|
+
margin: 0 auto 1rem;
|
116
120
|
}
|
117
121
|
|
118
122
|
.pagination .pagination-link {
|
@@ -181,6 +185,10 @@ body {
|
|
181
185
|
background: #e7e5e4;
|
182
186
|
}
|
183
187
|
|
188
|
+
.search-form {
|
189
|
+
margin-bottom: 1rem;
|
190
|
+
}
|
191
|
+
|
184
192
|
.search-form .form-select {
|
185
193
|
margin-right: 1rem;
|
186
194
|
padding: .5rem 1rem;
|
@@ -221,6 +229,19 @@ table p {
|
|
221
229
|
margin: 0;
|
222
230
|
}
|
223
231
|
|
232
|
+
.lockers {
|
233
|
+
margin-top: 2rem;
|
234
|
+
}
|
235
|
+
|
236
|
+
.lockers .locker-process {
|
237
|
+
display: flex;
|
238
|
+
flex-direction: column;
|
239
|
+
}
|
240
|
+
|
241
|
+
.metrics a {
|
242
|
+
color: #000;
|
243
|
+
}
|
244
|
+
|
224
245
|
@media screen and (max-width: 768px) {
|
225
246
|
.dashboard-row {
|
226
247
|
flex-direction: column;
|
@@ -84,7 +84,7 @@ module Que
|
|
84
84
|
|
85
85
|
def find_jobs(params)
|
86
86
|
case params[:status]&.to_sym
|
87
|
-
when :running then ::Que::View.fetch_running_jobs
|
87
|
+
when :running then ::Que::View.fetch_running_jobs
|
88
88
|
when :failing then ::Que::View.fetch_failing_jobs(PER_PAGE, offset, params)
|
89
89
|
when :scheduled then ::Que::View.fetch_scheduled_jobs(PER_PAGE, offset, params)
|
90
90
|
when :finished then ::Que::View.fetch_finished_jobs(PER_PAGE, offset, params)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Que
|
4
|
+
module View
|
5
|
+
class QueueMetricsController < Que::View::ApplicationController
|
6
|
+
before_action :find_queue_metrics, only: %i[index]
|
7
|
+
|
8
|
+
def index; end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def find_queue_metrics
|
13
|
+
@queue_metrics = ::Que::View.fetch_queue_metrics
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -26,6 +26,9 @@
|
|
26
26
|
<li class="<%= 'active' if params[:status] == 'expired' %>">
|
27
27
|
<%= link_to 'Expired', jobs_path(status: 'expired') %>
|
28
28
|
</li>
|
29
|
+
<li class="<%= 'active' if current_page?(queue_metrics_path) %>">
|
30
|
+
<%= link_to 'Queue metrics', queue_metrics_path %>
|
31
|
+
</li>
|
29
32
|
</ul>
|
30
33
|
<ul class="version">
|
31
34
|
<li>Que <%= Que::VERSION %></li>
|
@@ -1,3 +1,4 @@
|
|
1
|
+
<h1><%= params[:status].capitalize %> jobs</h1>
|
1
2
|
<%= form_with url: jobs_path, method: :get, class: 'search-form' do |form| %>
|
2
3
|
<%= form.hidden_field :status, value: params[:status] %>
|
3
4
|
<div class="row">
|
@@ -6,77 +7,73 @@
|
|
6
7
|
<%= form.submit 'Search', class: 'btn-primary' %>
|
7
8
|
</div>
|
8
9
|
<% end %>
|
9
|
-
<% if @
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
10
|
+
<% if @pagination %>
|
11
|
+
<div class="row pagination">
|
12
|
+
<% if @pagination.previous_page? %>
|
13
|
+
<%= link_to 'First', jobs_path(status: params[:status], page: 1), class: 'pagination-link' %>
|
14
|
+
<%= link_to '< Previous', jobs_path(status: params[:status], page: @pagination.previous_page), class: 'pagination-link' %>
|
15
|
+
<% else %>
|
16
|
+
<p class="pagination-link disabled">First</p>
|
17
|
+
<p class="pagination-link disabled">< Previous</p>
|
18
|
+
<% end %>
|
19
|
+
<p class="total-pages"><%= "Page #{@pagination.page} of #{@pagination.total_pages}" %></p>
|
20
|
+
<% if @pagination.next_page? %>
|
21
|
+
<%= link_to 'Next >', jobs_path(status: params[:status], page: @pagination.next_page), class: 'pagination-link' %>
|
22
|
+
<%= link_to 'Last', jobs_path(status: params[:status], page: @pagination.total_pages), class: 'pagination-link' %>
|
23
|
+
<% else %>
|
24
|
+
<p class="pagination-link disabled">Next ></p>
|
25
|
+
<p class="pagination-link disabled">Last </p>
|
26
|
+
<% end %>
|
27
|
+
</div>
|
28
|
+
<% end %>
|
29
|
+
<table cellspacing="0" class="jobs-list">
|
30
|
+
<thead>
|
31
|
+
<tr>
|
32
|
+
<th>ID</th>
|
33
|
+
<th>Job</th>
|
34
|
+
<th>Queue</th>
|
35
|
+
<th>Run at</th>
|
36
|
+
<th>Arguments</th>
|
37
|
+
<% if params[:status] == 'failing' %>
|
38
|
+
<th>Failures</th>
|
39
|
+
<th>Error</th>
|
18
40
|
<% end %>
|
19
|
-
|
20
|
-
|
21
|
-
<%= link_to 'Next >', jobs_path(status: params[:status], page: @pagination.next_page), class: 'pagination-link' %>
|
22
|
-
<%= link_to 'Last', jobs_path(status: params[:status], page: @pagination.total_pages), class: 'pagination-link' %>
|
23
|
-
<% else %>
|
24
|
-
<p class="pagination-link disabled">Next ></p>
|
25
|
-
<p class="pagination-link disabled">Last </p>
|
41
|
+
<% if %w[failing scheduled].include?(params[:status]) %>
|
42
|
+
<th>Actions</th>
|
26
43
|
<% end %>
|
27
|
-
</
|
28
|
-
|
29
|
-
<
|
30
|
-
|
44
|
+
</tr>
|
45
|
+
</thead>
|
46
|
+
<tbody>
|
47
|
+
<% @jobs.each do |job| %>
|
31
48
|
<tr>
|
32
|
-
<
|
33
|
-
<
|
34
|
-
<
|
35
|
-
<
|
36
|
-
<
|
49
|
+
<td><%= link_to job[:id], job_path(job[:id]) %></td>
|
50
|
+
<td><%= humanized_job_class(job) %></td>
|
51
|
+
<td><%= job[:queue] %></td>
|
52
|
+
<td><%= job[:run_at].strftime("%Y-%m-%d %H:%M:%S") %></td>
|
53
|
+
<td>
|
54
|
+
<% job.dig(:args, 0, :arguments).each do |argument| %>
|
55
|
+
<p><%= argument %></p>
|
56
|
+
<% end %>
|
57
|
+
</td>
|
37
58
|
<% if params[:status] == 'failing' %>
|
38
|
-
<
|
39
|
-
<
|
59
|
+
<td><%= job[:error_count] %></td>
|
60
|
+
<td><%= format_error(job) %></td>
|
40
61
|
<% end %>
|
41
62
|
<% if %w[failing scheduled].include?(params[:status]) %>
|
42
|
-
<th>Actions</th>
|
43
|
-
<% end %>
|
44
|
-
</tr>
|
45
|
-
</thead>
|
46
|
-
<tbody>
|
47
|
-
<% @jobs.each do |job| %>
|
48
|
-
<tr>
|
49
|
-
<td><%= link_to job[:id], job_path(job[:id]) %></td>
|
50
|
-
<td><%= humanized_job_class(job) %></td>
|
51
|
-
<td><%= job[:queue] %></td>
|
52
|
-
<td><%= job[:run_at].strftime("%Y-%m-%d %H:%M:%S") %></td>
|
53
63
|
<td>
|
54
|
-
|
55
|
-
|
56
|
-
|
64
|
+
<div class="actions">
|
65
|
+
<%= button_to 'Run', job_path(job[:id]), class: 'btn-danger', method: :patch, onclick: "return confirm('Are you sure you wish to reschedule job?')" %>
|
66
|
+
<%= button_to 'Delete', job_path(job[:id]), class: 'btn-danger', method: :delete, onclick: "return confirm('Are you sure you wish to delete job?')" %>
|
67
|
+
</div>
|
57
68
|
</td>
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
<div class="actions">
|
65
|
-
<%= button_to 'Run', job_path(job[:id]), class: 'btn-danger', method: :patch, onclick: "return confirm('Are you sure you wish to reschedule job?')" %>
|
66
|
-
<%= button_to 'Delete', job_path(job[:id]), class: 'btn-danger', method: :delete, onclick: "return confirm('Are you sure you wish to delete job?')" %>
|
67
|
-
</div>
|
68
|
-
</td>
|
69
|
-
<% end %>
|
70
|
-
</tr>
|
71
|
-
<% end %>
|
72
|
-
</tbody>
|
73
|
-
</table>
|
69
|
+
<% end %>
|
70
|
+
</tr>
|
71
|
+
<% end %>
|
72
|
+
</tbody>
|
73
|
+
</table>
|
74
|
+
<% if @pagination && %w[failing scheduled].include?(params[:status]) %>
|
74
75
|
<div class="row">
|
75
76
|
<%= button_to 'Run All', reschedule_all_jobs_path(status: params[:status]), class: 'btn-danger', method: :post, onclick: "return confirm('Are you sure you wish to reschedule all jobs?')" %>
|
76
77
|
<%= button_to 'Delete All', destroy_all_jobs_path(status: params[:status]), class: 'btn-danger', method: :delete, onclick: "return confirm('Are you sure you wish to delete all jobs?')" %>
|
77
78
|
</div>
|
78
|
-
<% else %>
|
79
|
-
<div class="row">
|
80
|
-
<p>No jobs found</p>
|
81
|
-
</div>
|
82
79
|
<% end %>
|
@@ -0,0 +1,29 @@
|
|
1
|
+
<div class="metrics">
|
2
|
+
<h1>Queue Metrics</h1>
|
3
|
+
<table cellspacing="0">
|
4
|
+
<thead>
|
5
|
+
<tr>
|
6
|
+
<th>Queue</th>
|
7
|
+
<th>Scheduled</th>
|
8
|
+
<th>Failing</th>
|
9
|
+
<th>Running</th>
|
10
|
+
<th>Finished</th>
|
11
|
+
<th>Expired</th>
|
12
|
+
<th>Latency</th>
|
13
|
+
</tr>
|
14
|
+
</thead>
|
15
|
+
<tbody>
|
16
|
+
<% @queue_metrics.each do |queue, values| %>
|
17
|
+
<tr>
|
18
|
+
<td><%= queue %></td>
|
19
|
+
<td><%= link_to values[:scheduled], jobs_path(status: 'scheduled', queue_name: queue) %></td>
|
20
|
+
<td><%= link_to values[:failing], jobs_path(status: 'failing', queue_name: queue) %></td>
|
21
|
+
<td><%= link_to values[:running], jobs_path(status: 'running', queue_name: queue) %></td>
|
22
|
+
<td><%= link_to values[:finished], jobs_path(status: 'finished', queue_name: queue) %></td>
|
23
|
+
<td><%= link_to values[:expired], jobs_path(status: 'expired', queue_name: queue) %></td>
|
24
|
+
<td></td>
|
25
|
+
</tr>
|
26
|
+
<% end %>
|
27
|
+
</tbody>
|
28
|
+
</table>
|
29
|
+
</div>
|
@@ -58,3 +58,29 @@
|
|
58
58
|
<% end %>
|
59
59
|
</div>
|
60
60
|
</div>
|
61
|
+
<div class="lockers">
|
62
|
+
<h1>Processes</h1>
|
63
|
+
<table cellspacing="0">
|
64
|
+
<thead>
|
65
|
+
<tr>
|
66
|
+
<th>ID</th>
|
67
|
+
<th>Worker count</th>
|
68
|
+
<th>Worker priorities</th>
|
69
|
+
<th>Listening</th>
|
70
|
+
</tr>
|
71
|
+
</thead>
|
72
|
+
<tbody>
|
73
|
+
<% @lockers.each.with_index do |locker, index| %>
|
74
|
+
<tr>
|
75
|
+
<td class="locker-process">
|
76
|
+
<span>Process <%= index + 1 %></span>
|
77
|
+
<span><strong>Queues:</strong> <%= locker[:queues][1..-2].split(',').join(', ') %></span>
|
78
|
+
</td>
|
79
|
+
<td><%= locker[:worker_count] %></td>
|
80
|
+
<td><%= locker[:worker_priorities] %></td>
|
81
|
+
<td><%= locker[:listening] %></td>
|
82
|
+
</tr>
|
83
|
+
<% end %>
|
84
|
+
</tbody>
|
85
|
+
</table>
|
86
|
+
</div>
|
data/config/routes.rb
CHANGED
data/lib/que/view/dsl.rb
CHANGED
@@ -8,6 +8,17 @@ module Que
|
|
8
8
|
execute(fetch_dashboard_stats_sql)
|
9
9
|
end
|
10
10
|
|
11
|
+
def fetch_que_lockers
|
12
|
+
execute(fetch_que_lockers_sql)
|
13
|
+
end
|
14
|
+
|
15
|
+
def fetch_queue_metrics
|
16
|
+
execute(fetch_queue_metrics_sql).each_with_object({}) { |element, acc|
|
17
|
+
acc[element[:queue_name].to_sym] ||= basis_queue_stats
|
18
|
+
acc[element[:queue_name].to_sym][element[:status].to_sym] = element[:count_all]
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
11
22
|
def fetch_queue_names
|
12
23
|
execute(fetch_queue_names_sql).map { |queues_data|
|
13
24
|
["#{queues_data[:queue_name]} (#{queues_data[:count_all]})", queues_data[:queue_name]]
|
@@ -20,7 +31,7 @@ module Que
|
|
20
31
|
}
|
21
32
|
end
|
22
33
|
|
23
|
-
def fetch_running_jobs
|
34
|
+
def fetch_running_jobs
|
24
35
|
Que.job_states
|
25
36
|
end
|
26
37
|
|
@@ -70,6 +81,10 @@ module Que
|
|
70
81
|
|
71
82
|
private
|
72
83
|
|
84
|
+
def basis_queue_stats
|
85
|
+
{ scheduled: 0, failing: 0, running: 0, finished: 0, expired: 0 }
|
86
|
+
end
|
87
|
+
|
73
88
|
# rubocop: disable Metrics/MethodLength
|
74
89
|
def fetch_dashboard_stats_sql
|
75
90
|
<<-SQL.squish
|
@@ -88,6 +103,40 @@ module Que
|
|
88
103
|
SQL
|
89
104
|
end
|
90
105
|
|
106
|
+
def fetch_que_lockers_sql
|
107
|
+
<<-SQL.squish
|
108
|
+
SELECT *
|
109
|
+
FROM que_lockers
|
110
|
+
SQL
|
111
|
+
end
|
112
|
+
|
113
|
+
def fetch_queue_metrics_sql
|
114
|
+
<<-SQL.squish
|
115
|
+
SELECT COUNT(*) AS count_all, queue AS queue_name,
|
116
|
+
CASE
|
117
|
+
WHEN expired_at IS NOT NULL THEN 'expired'
|
118
|
+
WHEN finished_at IS NOT NULL THEN 'finished'
|
119
|
+
WHEN locks.job_id IS NULL AND error_count > 0 THEN 'failing'
|
120
|
+
WHEN locks.job_id IS NULL AND error_count = 0 THEN 'scheduled'
|
121
|
+
ELSE 'running'
|
122
|
+
END status
|
123
|
+
FROM que_jobs
|
124
|
+
LEFT JOIN (
|
125
|
+
SELECT (classid::bigint << 32) + objid::bigint AS job_id
|
126
|
+
FROM pg_locks
|
127
|
+
WHERE locktype = 'advisory'
|
128
|
+
) locks ON (que_jobs.id=locks.job_id)
|
129
|
+
GROUP BY queue,
|
130
|
+
CASE
|
131
|
+
WHEN expired_at IS NOT NULL THEN 'expired'
|
132
|
+
WHEN finished_at IS NOT NULL THEN 'finished'
|
133
|
+
WHEN locks.job_id IS NULL AND error_count > 0 THEN 'failing'
|
134
|
+
WHEN locks.job_id IS NULL AND error_count = 0 THEN 'scheduled'
|
135
|
+
ELSE 'running'
|
136
|
+
END
|
137
|
+
SQL
|
138
|
+
end
|
139
|
+
|
91
140
|
def fetch_queue_names_sql
|
92
141
|
<<-SQL.squish
|
93
142
|
SELECT COUNT(*) AS count_all, queue AS queue_name
|
data/lib/que/view/version.rb
CHANGED
data/lib/que/view.rb
CHANGED
@@ -34,7 +34,7 @@ module Que
|
|
34
34
|
end
|
35
35
|
|
36
36
|
# Public: All the methods delegated to instance. These should match the interface of Que::View::DSL.
|
37
|
-
def_delegators :instance, :fetch_dashboard_stats,
|
37
|
+
def_delegators :instance, :fetch_dashboard_stats, :fetch_que_lockers, :fetch_queue_metrics,
|
38
38
|
:fetch_queue_names, :fetch_job_names,
|
39
39
|
:fetch_running_jobs, :fetch_failing_jobs, :fetch_scheduled_jobs, :fetch_finished_jobs,
|
40
40
|
:fetch_expired_jobs, :fetch_job,
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: que-view
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bogdanov Anton
|
@@ -164,11 +164,13 @@ files:
|
|
164
164
|
- app/assets/stylesheets/que/view/application.css
|
165
165
|
- app/controllers/que/view/application_controller.rb
|
166
166
|
- app/controllers/que/view/jobs_controller.rb
|
167
|
+
- app/controllers/que/view/queue_metrics_controller.rb
|
167
168
|
- app/controllers/que/view/welcome_controller.rb
|
168
169
|
- app/helpers/que/view/application_helper.rb
|
169
170
|
- app/views/layouts/que/view/application.html.erb
|
170
171
|
- app/views/que/view/jobs/index.html.erb
|
171
172
|
- app/views/que/view/jobs/show.html.erb
|
173
|
+
- app/views/que/view/queue_metrics/index.html.erb
|
172
174
|
- app/views/que/view/welcome/index.html.erb
|
173
175
|
- config/routes.rb
|
174
176
|
- lib/que/view.rb
|