que-view 0.2.3 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +10 -9
- data/app/assets/stylesheets/que/view/application.css +166 -49
- data/app/controllers/que/view/jobs_controller.rb +42 -21
- data/app/controllers/que/view/welcome_controller.rb +1 -1
- data/app/views/layouts/que/view/application.html.erb +30 -1
- data/app/views/que/view/jobs/index.html.erb +70 -43
- data/app/views/que/view/jobs/show.html.erb +1 -1
- data/app/views/que/view/welcome/index.html.erb +55 -33
- data/lib/que/view/dsl.rb +85 -30
- data/lib/que/view/pagination.rb +49 -0
- data/lib/que/view/version.rb +1 -1
- data/lib/que/view.rb +7 -4
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d07972f8adf809f3f10f495824376c64724e1526e7afd6d85a05da93632741e0
|
4
|
+
data.tar.gz: 95d6ce6e4d1340dcb550a1ae4b85560f19a18de0b9c0dec1eea5ce5a44726ddf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9553edb70775fc2fa268ad2ec3e14d3ab51c3858b54363efbb0d6b8aae85f00d450d99e925cd576dc7117f1ff784bbbe664d763305123598acc35bda57e38e8b
|
7
|
+
data.tar.gz: ce8bc2cbfef7844814f60125bbad375bf676bc2ef14180d5a2bdc2b4a9fd5cc3ac1920f5b5f40b540a2e600ee4107c247c3b89f90d3e7662725de1a6539c2461
|
data/README.md
CHANGED
@@ -1,7 +1,16 @@
|
|
1
1
|
# Que::View
|
2
2
|
Rails engine inspired by [Que::Web](https://github.com/statianzo/que-web) for [Que](https://github.com/que-rb/que) job queue.
|
3
3
|
SQL queries came from Que::Web, some styling from there too.
|
4
|
-
|
4
|
+
|
5
|
+
Benefits for using this one:
|
6
|
+
- no Sinatra (que-web based on Sinatra),
|
7
|
+
- no Foundation for styles,
|
8
|
+
- no jQuery,
|
9
|
+
- Que::Web was last updated 2 years ago.
|
10
|
+
|
11
|
+
<img width="1735" alt="Снимок экрана 2024-02-21 в 15 10 35" src="https://github.com/kortirso/que-view/assets/6195394/cd2812c7-abb0-48d9-92d5-4dbef93bcd9e">
|
12
|
+
<img width="1735" alt="Снимок экрана 2024-02-21 в 15 11 12" src="https://github.com/kortirso/que-view/assets/6195394/8af01e7f-a002-4ef1-aeff-f96fd27c639f">
|
13
|
+
|
5
14
|
|
6
15
|
## Installation
|
7
16
|
|
@@ -44,13 +53,5 @@ Add this line to assets/config/manifest.js
|
|
44
53
|
//= link que/view/application.css
|
45
54
|
```
|
46
55
|
|
47
|
-
## TODO
|
48
|
-
|
49
|
-
- [X] rescheduling jobs
|
50
|
-
- [X] deleting jobs
|
51
|
-
- [ ] better styles for UI
|
52
|
-
- [ ] rendering running jobs
|
53
|
-
- [ ] tests
|
54
|
-
|
55
56
|
## License
|
56
57
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -20,67 +20,64 @@
|
|
20
20
|
font-family: 'Source Sans Pro';
|
21
21
|
}
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
margin: 0 auto;
|
27
|
-
max-width: 75rem;
|
28
|
-
width: 100%;
|
29
|
-
}
|
30
|
-
|
31
|
-
.row .column {
|
32
|
-
flex: 1;
|
33
|
-
padding: 1rem;
|
34
|
-
}
|
35
|
-
|
36
|
-
.row table {
|
37
|
-
flex: 1;
|
38
|
-
margin-bottom: 1rem;
|
39
|
-
}
|
40
|
-
|
41
|
-
table th, table td {
|
42
|
-
padding: .25rem .75rem;
|
43
|
-
border-bottom: 1px solid #BBB;
|
23
|
+
html, body {
|
24
|
+
margin: 0;
|
25
|
+
padding: 0;
|
44
26
|
}
|
45
27
|
|
46
|
-
|
47
|
-
|
28
|
+
body {
|
29
|
+
background: #fafafa;
|
48
30
|
}
|
49
31
|
|
50
|
-
|
51
|
-
|
32
|
+
.row {
|
33
|
+
display: flex;
|
52
34
|
}
|
53
35
|
|
54
|
-
|
55
|
-
|
36
|
+
.content {
|
37
|
+
padding: 1rem;
|
56
38
|
}
|
57
39
|
|
58
|
-
|
59
|
-
|
40
|
+
.actions {
|
41
|
+
display: flex;
|
60
42
|
}
|
61
43
|
|
62
|
-
.
|
44
|
+
.dashboard-row {
|
63
45
|
display: flex;
|
46
|
+
margin: 0 auto;
|
47
|
+
width: 100%;
|
48
|
+
border: 1px solid #a8a29e;
|
49
|
+
border-radius: .25rem;
|
50
|
+
overflow: hidden;
|
64
51
|
}
|
65
52
|
|
66
|
-
.dashboard-stat {
|
53
|
+
.dashboard-row .dashboard-stat {
|
67
54
|
text-align: center;
|
68
55
|
display: table;
|
69
56
|
width: 100%;
|
57
|
+
background: #fff;
|
58
|
+
border-right: 1px solid #a8a29e;
|
59
|
+
}
|
60
|
+
|
61
|
+
.dashboard-row .dashboard-stat:hover {
|
62
|
+
background: #f5f5f4;
|
70
63
|
}
|
71
64
|
|
72
|
-
.dashboard-stat
|
65
|
+
.dashboard-row .dashboard-stat:nth-last-of-type(1) {
|
66
|
+
border: none;
|
67
|
+
}
|
68
|
+
|
69
|
+
.dashboard-row .dashboard-stat a {
|
73
70
|
text-decoration: none;
|
74
71
|
}
|
75
72
|
|
76
|
-
.dashboard-stat .cell {
|
73
|
+
.dashboard-row .dashboard-stat .cell {
|
77
74
|
display: flex;
|
78
75
|
flex-direction: column;
|
79
76
|
justify-content: center;
|
80
|
-
height:
|
77
|
+
height: 150px;
|
81
78
|
}
|
82
79
|
|
83
|
-
.dashboard-stat h2 {
|
80
|
+
.dashboard-row .dashboard-stat h2 {
|
84
81
|
color: #222;
|
85
82
|
font-size: 1rem;
|
86
83
|
font-weight: normal;
|
@@ -88,24 +85,23 @@ table p {
|
|
88
85
|
margin: 0;
|
89
86
|
}
|
90
87
|
|
91
|
-
.dashboard-stat.running {
|
92
|
-
background: #CFD0C1;
|
93
|
-
}
|
94
|
-
|
95
|
-
.dashboard-stat.scheduled {
|
96
|
-
background: #828E8C;
|
97
|
-
}
|
98
|
-
|
99
|
-
.dashboard-stat.failing {
|
100
|
-
background: #E8866C;
|
101
|
-
}
|
102
|
-
|
103
88
|
.dashboard-value {
|
104
|
-
font-size:
|
105
|
-
line-height:
|
89
|
+
font-size: 2rem;
|
90
|
+
line-height: 2rem;
|
106
91
|
color: black;
|
107
92
|
}
|
108
93
|
|
94
|
+
.btn-primary {
|
95
|
+
cursor: pointer;
|
96
|
+
border: none;
|
97
|
+
border-radius: .25rem;
|
98
|
+
background: #fde68a;
|
99
|
+
border: 1px solid #fcd34d;
|
100
|
+
border-radius: .25rem;
|
101
|
+
padding: .25rem .5rem;
|
102
|
+
margin-right: .5rem;
|
103
|
+
}
|
104
|
+
|
109
105
|
.btn-danger {
|
110
106
|
cursor: pointer;
|
111
107
|
border: none;
|
@@ -114,3 +110,124 @@ table p {
|
|
114
110
|
padding: .25rem .5rem;
|
115
111
|
margin-right: .5rem;
|
116
112
|
}
|
113
|
+
|
114
|
+
.pagination {
|
115
|
+
margin: 1rem auto;
|
116
|
+
}
|
117
|
+
|
118
|
+
.pagination .pagination-link {
|
119
|
+
margin: 0 .5rem 0 0;
|
120
|
+
padding: .25rem .5rem;
|
121
|
+
background: #fde68a;
|
122
|
+
border: 1px solid #fcd34d;
|
123
|
+
border-radius: .25rem;
|
124
|
+
text-decoration: none;
|
125
|
+
}
|
126
|
+
|
127
|
+
.pagination .pagination-link.disabled {
|
128
|
+
background: #fef3c7;
|
129
|
+
}
|
130
|
+
|
131
|
+
.pagination .total-pages {
|
132
|
+
margin: 0 .5rem 0 0;
|
133
|
+
padding: .25rem .5rem;
|
134
|
+
}
|
135
|
+
|
136
|
+
.navigation {
|
137
|
+
display: flex;
|
138
|
+
padding: 0 1rem;
|
139
|
+
background: #fff;
|
140
|
+
border-bottom: 1px solid #e7e5e4;
|
141
|
+
color: #000;
|
142
|
+
}
|
143
|
+
|
144
|
+
.navigation h1 {
|
145
|
+
margin: 0 2rem 0 0;
|
146
|
+
display: flex;
|
147
|
+
align-items: center;
|
148
|
+
}
|
149
|
+
|
150
|
+
.navigation h1 a {
|
151
|
+
text-decoration: none;
|
152
|
+
color: #000;
|
153
|
+
}
|
154
|
+
|
155
|
+
.navigation .navigation-section {
|
156
|
+
flex: 1;
|
157
|
+
display: flex;
|
158
|
+
justify-content: space-between;
|
159
|
+
align-items: center;
|
160
|
+
}
|
161
|
+
|
162
|
+
.navigation .navigation-section ul {
|
163
|
+
margin: 0;
|
164
|
+
padding: 0;
|
165
|
+
list-style: none;
|
166
|
+
display: flex;
|
167
|
+
}
|
168
|
+
|
169
|
+
.navigation .navigation-section ul.version li {
|
170
|
+
margin-left: 1rem;
|
171
|
+
}
|
172
|
+
|
173
|
+
.navigation .navigation-section ul li a {
|
174
|
+
display: inline-block;
|
175
|
+
padding: 1rem;
|
176
|
+
text-decoration: none;
|
177
|
+
color: #000;
|
178
|
+
}
|
179
|
+
|
180
|
+
.navigation .navigation-section ul li.active {
|
181
|
+
background: #e7e5e4;
|
182
|
+
}
|
183
|
+
|
184
|
+
.search-form .form-select {
|
185
|
+
margin-right: 1rem;
|
186
|
+
padding: .5rem 1rem;
|
187
|
+
border: 1px solid #e7e5e4;
|
188
|
+
border-radius: .25rem;
|
189
|
+
background: #fff;
|
190
|
+
}
|
191
|
+
|
192
|
+
table {
|
193
|
+
width: 100%;
|
194
|
+
margin-bottom: 1rem;
|
195
|
+
background: #fff;
|
196
|
+
border: 1px solid #e7e5e4;
|
197
|
+
border-bottom: none;
|
198
|
+
border-radius: .25rem;
|
199
|
+
}
|
200
|
+
|
201
|
+
table th, table td {
|
202
|
+
padding: .5rem .75rem;
|
203
|
+
border-bottom: 1px solid #e7e5e4;
|
204
|
+
}
|
205
|
+
|
206
|
+
table th {
|
207
|
+
text-align: left;
|
208
|
+
font-weight: normal;
|
209
|
+
text-transform: uppercase;
|
210
|
+
}
|
211
|
+
|
212
|
+
table tbody tr:nth-of-type(2n), table tbody tr:hover {
|
213
|
+
background: #EEE;
|
214
|
+
}
|
215
|
+
|
216
|
+
table tbody tr:nth-of-type(2n):hover {
|
217
|
+
background: #DDD;
|
218
|
+
}
|
219
|
+
|
220
|
+
table p {
|
221
|
+
margin: 0;
|
222
|
+
}
|
223
|
+
|
224
|
+
@media screen and (max-width: 768px) {
|
225
|
+
.dashboard-row {
|
226
|
+
flex-direction: column;
|
227
|
+
}
|
228
|
+
|
229
|
+
.dashboard-row .dashboard-stat {
|
230
|
+
border-right: none;
|
231
|
+
border-bottom: 1px solid #a8a29e;
|
232
|
+
}
|
233
|
+
}
|
@@ -5,11 +5,13 @@ module Que
|
|
5
5
|
class JobsController < Que::View::ApplicationController
|
6
6
|
PER_PAGE = 20
|
7
7
|
|
8
|
+
before_action :find_queue_names, only: %i[index]
|
9
|
+
before_action :find_job_names, only: %i[index]
|
8
10
|
before_action :find_job, only: %i[show]
|
9
11
|
|
10
12
|
def index
|
11
|
-
@jobs = find_jobs(
|
12
|
-
|
13
|
+
@jobs = find_jobs(index_params)
|
14
|
+
paginate
|
13
15
|
end
|
14
16
|
|
15
17
|
def show; end
|
@@ -48,6 +50,18 @@ module Que
|
|
48
50
|
|
49
51
|
private
|
50
52
|
|
53
|
+
def find_queue_names
|
54
|
+
@queue_names = [
|
55
|
+
['All queues', nil]
|
56
|
+
] + ::Que::View.fetch_queue_names
|
57
|
+
end
|
58
|
+
|
59
|
+
def find_job_names
|
60
|
+
@job_names = [
|
61
|
+
['All jobs', nil]
|
62
|
+
] + ::Que::View.fetch_job_names(params[:queue_name])
|
63
|
+
end
|
64
|
+
|
51
65
|
def find_job
|
52
66
|
@job = ::Que::View.fetch_job(params[:id])[0]
|
53
67
|
return if @job
|
@@ -55,16 +69,32 @@ module Que
|
|
55
69
|
redirect_to root_path, notice: 'Job is not found'
|
56
70
|
end
|
57
71
|
|
58
|
-
def
|
59
|
-
|
60
|
-
|
61
|
-
|
72
|
+
def paginate
|
73
|
+
return if %w[failing scheduled].exclude?(params[:status])
|
74
|
+
return unless @jobs.any?
|
75
|
+
|
76
|
+
@pagination = Que::View::Pagination.new(
|
77
|
+
params: {
|
78
|
+
page: page,
|
79
|
+
per_page: params[:per_page] || PER_PAGE,
|
80
|
+
count: find_jobs_total_amount(params[:status])
|
81
|
+
}
|
82
|
+
)
|
83
|
+
end
|
84
|
+
|
85
|
+
def find_jobs(params)
|
86
|
+
case params[:status]&.to_sym
|
87
|
+
when :running then ::Que::View.fetch_running_jobs(params)
|
88
|
+
when :failing then ::Que::View.fetch_failing_jobs(PER_PAGE, offset, params)
|
89
|
+
when :scheduled then ::Que::View.fetch_scheduled_jobs(PER_PAGE, offset, params)
|
90
|
+
when :finished then ::Que::View.fetch_finished_jobs(PER_PAGE, offset, params)
|
91
|
+
when :expired then ::Que::View.fetch_expired_jobs(PER_PAGE, offset, params)
|
62
92
|
else []
|
63
93
|
end
|
64
94
|
end
|
65
95
|
|
66
|
-
def
|
67
|
-
::Que::View.fetch_dashboard_stats
|
96
|
+
def find_jobs_total_amount(status)
|
97
|
+
::Que::View.fetch_dashboard_stats[0][status&.to_sym]
|
68
98
|
end
|
69
99
|
|
70
100
|
def reschedule_all_jobs(status, time)
|
@@ -83,25 +113,16 @@ module Que
|
|
83
113
|
end
|
84
114
|
end
|
85
115
|
|
86
|
-
def
|
87
|
-
|
88
|
-
|
89
|
-
"%#{search_param}%"
|
90
|
-
end
|
91
|
-
|
92
|
-
def search_param
|
93
|
-
sanitised = (params[:search] || '').gsub(/[^0-9a-z:]/i, '')
|
94
|
-
return if sanitised.empty?
|
95
|
-
|
96
|
-
sanitised
|
116
|
+
def offset
|
117
|
+
(page - 1) * PER_PAGE
|
97
118
|
end
|
98
119
|
|
99
120
|
def page
|
100
121
|
(params[:page] || 1).to_i
|
101
122
|
end
|
102
123
|
|
103
|
-
def
|
104
|
-
(
|
124
|
+
def index_params
|
125
|
+
params.permit(:status, :queue_name, :job_name).to_h.symbolize_keys
|
105
126
|
end
|
106
127
|
end
|
107
128
|
end
|
@@ -7,6 +7,35 @@
|
|
7
7
|
<%= stylesheet_link_tag 'que/view/application', media: 'all' %>
|
8
8
|
</head>
|
9
9
|
<body>
|
10
|
-
|
10
|
+
<nav class="navigation" role="navigation">
|
11
|
+
<h1><%= link_to 'Que View', root_path %></h1>
|
12
|
+
<section class="navigation-section">
|
13
|
+
<ul>
|
14
|
+
<li class="<%= 'active' if params[:status] == 'scheduled' %>">
|
15
|
+
<%= link_to 'Scheduled', jobs_path(status: 'scheduled') %>
|
16
|
+
</li>
|
17
|
+
<li class="<%= 'active' if params[:status] == 'failing' %>">
|
18
|
+
<%= link_to 'Failing', jobs_path(status: 'failing') %>
|
19
|
+
</li>
|
20
|
+
<li class="<%= 'active' if params[:status] == 'running' %>">
|
21
|
+
<%= link_to 'Running', jobs_path(status: 'running') %>
|
22
|
+
</li>
|
23
|
+
<li class="<%= 'active' if params[:status] == 'finished' %>">
|
24
|
+
<%= link_to 'Finished', jobs_path(status: 'finished') %>
|
25
|
+
</li>
|
26
|
+
<li class="<%= 'active' if params[:status] == 'expired' %>">
|
27
|
+
<%= link_to 'Expired', jobs_path(status: 'expired') %>
|
28
|
+
</li>
|
29
|
+
</ul>
|
30
|
+
<ul class="version">
|
31
|
+
<li>Que <%= Que::VERSION %></li>
|
32
|
+
<li>Que View <%= Que::View::VERSION %></li>
|
33
|
+
<li><%= Time.now.utc.strftime("%Y-%m-%d %H:%M:%S") %></li>
|
34
|
+
</ul>
|
35
|
+
</section>
|
36
|
+
</nav>
|
37
|
+
<section class="content">
|
38
|
+
<%= yield %>
|
39
|
+
</section>
|
11
40
|
</body>
|
12
41
|
</html>
|
@@ -1,55 +1,82 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
<%= form_with url: jobs_path, method: :get, class: 'search-form' do |form| %>
|
2
|
+
<%= form.hidden_field :status, value: params[:status] %>
|
3
|
+
<div class="row">
|
4
|
+
<%= form.select :queue_name, options_for_select(@queue_names, params[:queue_name]), {}, class: 'form-select' %>
|
5
|
+
<%= form.select :job_name, options_for_select(@job_names, params[:job_name]), {}, class: 'form-select' %>
|
6
|
+
<%= form.submit 'Search', class: 'btn-primary' %>
|
7
|
+
</div>
|
8
|
+
<% end %>
|
9
|
+
<% if @jobs.any? %>
|
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>
|
40
|
+
<% end %>
|
41
|
+
<% if %w[failing scheduled].include?(params[:status]) %>
|
42
|
+
<th>Actions</th>
|
43
|
+
<% end %>
|
44
|
+
</tr>
|
45
|
+
</thead>
|
46
|
+
<tbody>
|
47
|
+
<% @jobs.each do |job| %>
|
5
48
|
<tr>
|
6
|
-
<
|
7
|
-
<
|
8
|
-
<
|
9
|
-
<
|
10
|
-
<
|
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>
|
11
58
|
<% if params[:status] == 'failing' %>
|
12
|
-
<
|
13
|
-
<
|
59
|
+
<td><%= job[:error_count] %></td>
|
60
|
+
<td><%= format_error(job) %></td>
|
14
61
|
<% end %>
|
15
62
|
<% if %w[failing scheduled].include?(params[:status]) %>
|
16
|
-
<th></th>
|
17
|
-
<% end %>
|
18
|
-
</tr>
|
19
|
-
</thead>
|
20
|
-
<tbody>
|
21
|
-
<% @jobs.each do |job| %>
|
22
|
-
<tr>
|
23
|
-
<td><%= link_to job[:id], job_path(job[:id]) %></td>
|
24
|
-
<td><%= job[:run_at].utc %></td>
|
25
|
-
<td><%= humanized_job_class(job) %></td>
|
26
|
-
<td><%= job[:queue] %></td>
|
27
63
|
<td>
|
28
|
-
|
29
|
-
<p><%= argument %></p>
|
30
|
-
<% end %>
|
31
|
-
</td>
|
32
|
-
<% if params[:status] == 'failing' %>
|
33
|
-
<td><%= job[:error_count] %></td>
|
34
|
-
<td><%= format_error(job) %></td>
|
35
|
-
<% end %>
|
36
|
-
<% if %w[failing scheduled].include?(params[:status]) %>
|
37
|
-
<td class="actions">
|
64
|
+
<div class="actions">
|
38
65
|
<%= button_to 'Run', job_path(job[:id]), class: 'btn-danger', method: :patch, onclick: "return confirm('Are you sure you wish to reschedule job?')" %>
|
39
66
|
<%= button_to 'Delete', job_path(job[:id]), class: 'btn-danger', method: :delete, onclick: "return confirm('Are you sure you wish to delete job?')" %>
|
40
|
-
</
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
</
|
46
|
-
|
47
|
-
<p>No jobs found</p>
|
48
|
-
<% end %>
|
49
|
-
</div>
|
50
|
-
<% if %w[failing scheduled].include?(params[:status]) && @jobs_amount.positive? %>
|
67
|
+
</div>
|
68
|
+
</td>
|
69
|
+
<% end %>
|
70
|
+
</tr>
|
71
|
+
<% end %>
|
72
|
+
</tbody>
|
73
|
+
</table>
|
51
74
|
<div class="row">
|
52
75
|
<%= 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?')" %>
|
53
76
|
<%= 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?')" %>
|
54
77
|
</div>
|
78
|
+
<% else %>
|
79
|
+
<div class="row">
|
80
|
+
<p>No jobs found</p>
|
81
|
+
</div>
|
55
82
|
<% end %>
|
@@ -1,38 +1,60 @@
|
|
1
|
-
<div class="row">
|
2
|
-
<div class="
|
3
|
-
<div class="
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
<%= @dashboard_stats[:running] %>
|
9
|
-
</span>
|
10
|
-
</div>
|
11
|
-
<% end %>
|
1
|
+
<div class="dashboard-row">
|
2
|
+
<div class="dashboard-stat">
|
3
|
+
<div class="cell">
|
4
|
+
<h2>Total</h2>
|
5
|
+
<span class="dashboard-value">
|
6
|
+
<%= @dashboard_stats[:total] %>
|
7
|
+
</span>
|
12
8
|
</div>
|
13
9
|
</div>
|
14
|
-
<div class="
|
15
|
-
|
16
|
-
|
17
|
-
<
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
<% end %>
|
24
|
-
</div>
|
10
|
+
<div class="dashboard-stat">
|
11
|
+
<%= link_to jobs_path(status: 'scheduled') do %>
|
12
|
+
<div class="cell">
|
13
|
+
<h2>Scheduled</h2>
|
14
|
+
<span class="dashboard-value">
|
15
|
+
<%= @dashboard_stats[:scheduled] %>
|
16
|
+
</span>
|
17
|
+
</div>
|
18
|
+
<% end %>
|
25
19
|
</div>
|
26
|
-
<div class="
|
27
|
-
|
28
|
-
|
29
|
-
<
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
20
|
+
<div class="dashboard-stat">
|
21
|
+
<%= link_to jobs_path(status: 'failing') do %>
|
22
|
+
<div class="cell">
|
23
|
+
<h2>Failing</h2>
|
24
|
+
<span class="dashboard-value">
|
25
|
+
<%= @dashboard_stats[:failing] %>
|
26
|
+
</span>
|
27
|
+
</div>
|
28
|
+
<% end %>
|
29
|
+
</div>
|
30
|
+
<div class="dashboard-stat">
|
31
|
+
<%= link_to jobs_path(status: 'running') do %>
|
32
|
+
<div class="cell">
|
33
|
+
<h2>Running</h2>
|
34
|
+
<span class="dashboard-value">
|
35
|
+
<%= @dashboard_stats[:running] %>
|
36
|
+
</span>
|
37
|
+
</div>
|
38
|
+
<% end %>
|
39
|
+
</div>
|
40
|
+
<div class="dashboard-stat">
|
41
|
+
<%= link_to jobs_path(status: 'finished') do %>
|
42
|
+
<div class="cell">
|
43
|
+
<h2>Finished</h2>
|
44
|
+
<span class="dashboard-value">
|
45
|
+
<%= @dashboard_stats[:finished] %>
|
46
|
+
</span>
|
47
|
+
</div>
|
48
|
+
<% end %>
|
49
|
+
</div>
|
50
|
+
<div class="dashboard-stat">
|
51
|
+
<%= link_to jobs_path(status: 'expired') do %>
|
52
|
+
<div class="cell">
|
53
|
+
<h2>Expired</h2>
|
54
|
+
<span class="dashboard-value">
|
55
|
+
<%= @dashboard_stats[:expired] %>
|
56
|
+
</span>
|
57
|
+
</div>
|
58
|
+
<% end %>
|
37
59
|
</div>
|
38
60
|
</div>
|
data/lib/que/view/dsl.rb
CHANGED
@@ -4,8 +4,24 @@ module Que
|
|
4
4
|
module View
|
5
5
|
# rubocop: disable Metrics/ClassLength
|
6
6
|
class DSL
|
7
|
-
def fetch_dashboard_stats
|
8
|
-
execute(fetch_dashboard_stats_sql
|
7
|
+
def fetch_dashboard_stats
|
8
|
+
execute(fetch_dashboard_stats_sql)
|
9
|
+
end
|
10
|
+
|
11
|
+
def fetch_queue_names
|
12
|
+
execute(fetch_queue_names_sql).map { |queues_data|
|
13
|
+
["#{queues_data[:queue_name]} (#{queues_data[:count_all]})", queues_data[:queue_name]]
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def fetch_job_names(...)
|
18
|
+
execute(fetch_job_names_sql(...)).map { |jobs_data|
|
19
|
+
["#{jobs_data[:job_name]} (#{jobs_data[:count_all]})", jobs_data[:job_name]]
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def fetch_running_jobs(...)
|
24
|
+
Que.job_states
|
9
25
|
end
|
10
26
|
|
11
27
|
def fetch_failing_jobs(...)
|
@@ -16,6 +32,14 @@ module Que
|
|
16
32
|
execute(fetch_scheduled_jobs_sql(...))
|
17
33
|
end
|
18
34
|
|
35
|
+
def fetch_finished_jobs(...)
|
36
|
+
execute(fetch_finished_jobs_sql(...))
|
37
|
+
end
|
38
|
+
|
39
|
+
def fetch_expired_jobs(...)
|
40
|
+
execute(fetch_expired_jobs_sql(...))
|
41
|
+
end
|
42
|
+
|
19
43
|
def fetch_job(...)
|
20
44
|
execute(fetch_job_sql(...))
|
21
45
|
end
|
@@ -47,46 +71,82 @@ module Que
|
|
47
71
|
private
|
48
72
|
|
49
73
|
# rubocop: disable Metrics/MethodLength
|
50
|
-
def fetch_dashboard_stats_sql
|
74
|
+
def fetch_dashboard_stats_sql
|
51
75
|
<<-SQL.squish
|
52
|
-
SELECT count(*)
|
53
|
-
count(locks.job_id)
|
76
|
+
SELECT count(*) AS total,
|
77
|
+
count(locks.job_id) AS running,
|
54
78
|
coalesce(sum((error_count > 0 AND locks.job_id IS NULL)::int), 0) AS failing,
|
55
|
-
coalesce(sum((error_count = 0 AND locks.job_id IS NULL)::int), 0) AS scheduled
|
79
|
+
coalesce(sum((error_count = 0 AND locks.job_id IS NULL)::int), 0) AS scheduled,
|
80
|
+
coalesce(sum((finished_at IS NOT NULL)::int), 0) AS finished,
|
81
|
+
coalesce(sum((expired_at IS NOT NULL)::int), 0) AS expired
|
56
82
|
FROM que_jobs
|
57
83
|
LEFT JOIN (
|
58
84
|
SELECT (classid::bigint << 32) + objid::bigint AS job_id
|
59
85
|
FROM pg_locks
|
60
86
|
WHERE locktype = 'advisory'
|
61
87
|
) locks ON (que_jobs.id=locks.job_id)
|
62
|
-
WHERE
|
63
|
-
job_class ILIKE ('#{search}')
|
64
|
-
OR que_jobs.args #>> '{0, job_class}' ILIKE ('#{search}')
|
65
88
|
SQL
|
66
89
|
end
|
67
90
|
|
68
|
-
def
|
91
|
+
def fetch_queue_names_sql
|
69
92
|
<<-SQL.squish
|
70
|
-
SELECT
|
93
|
+
SELECT COUNT(*) AS count_all, queue AS queue_name
|
71
94
|
FROM que_jobs
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
95
|
+
GROUP BY queue
|
96
|
+
SQL
|
97
|
+
end
|
98
|
+
|
99
|
+
def fetch_job_names_sql(queue_name)
|
100
|
+
<<-SQL.squish
|
101
|
+
SELECT COUNT(*) AS count_all, args #>> '{0, job_class}' AS job_name
|
102
|
+
FROM que_jobs
|
103
|
+
#{queue_name.present? ? "WHERE queue = '#{queue_name}'" : ""}
|
104
|
+
GROUP BY args #>> '{0, job_class}'
|
105
|
+
SQL
|
106
|
+
end
|
107
|
+
|
108
|
+
def fetch_failing_jobs_sql(per_page, offset, params)
|
109
|
+
where_condition = <<-SQL.squish
|
77
110
|
WHERE locks.job_id IS NULL
|
78
111
|
AND error_count > 0
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
112
|
+
#{search_condition(params)}
|
113
|
+
SQL
|
114
|
+
fetch_jobs_sql(per_page, offset, where_condition)
|
115
|
+
end
|
116
|
+
|
117
|
+
def fetch_scheduled_jobs_sql(per_page, offset, params)
|
118
|
+
where_condition = <<-SQL.squish
|
119
|
+
WHERE locks.job_id IS NULL
|
120
|
+
AND error_count = 0
|
121
|
+
#{search_condition(params)}
|
86
122
|
SQL
|
123
|
+
fetch_jobs_sql(per_page, offset, where_condition)
|
87
124
|
end
|
88
125
|
|
89
|
-
def
|
126
|
+
def fetch_finished_jobs_sql(per_page, offset, params)
|
127
|
+
where_condition = <<-SQL.squish
|
128
|
+
WHERE finished_at IS NOT NULL
|
129
|
+
#{search_condition(params)}
|
130
|
+
SQL
|
131
|
+
fetch_jobs_sql(per_page, offset, where_condition)
|
132
|
+
end
|
133
|
+
|
134
|
+
def fetch_expired_jobs_sql(per_page, offset, params)
|
135
|
+
where_condition = <<-SQL.squish
|
136
|
+
WHERE expired_at IS NOT NULL
|
137
|
+
#{search_condition(params)}
|
138
|
+
SQL
|
139
|
+
fetch_jobs_sql(per_page, offset, where_condition)
|
140
|
+
end
|
141
|
+
|
142
|
+
def search_condition(params)
|
143
|
+
result = ''
|
144
|
+
result += "AND queue = '#{params[:queue_name]}'" if params[:queue_name].present?
|
145
|
+
result += "AND args #>> '{0, job_class}' = ('#{params[:job_name]}')" if params[:job_name].present?
|
146
|
+
result
|
147
|
+
end
|
148
|
+
|
149
|
+
def fetch_jobs_sql(per_page, offset, where_condition)
|
90
150
|
<<-SQL.squish
|
91
151
|
SELECT que_jobs.*
|
92
152
|
FROM que_jobs
|
@@ -95,12 +155,7 @@ module Que
|
|
95
155
|
FROM pg_locks
|
96
156
|
WHERE locktype = 'advisory'
|
97
157
|
) locks ON (que_jobs.id=locks.job_id)
|
98
|
-
|
99
|
-
AND error_count = 0
|
100
|
-
AND (
|
101
|
-
job_class ILIKE ('#{search}')
|
102
|
-
OR que_jobs.args #>> '{0, job_class}' ILIKE ('#{search}')
|
103
|
-
)
|
158
|
+
#{where_condition}
|
104
159
|
ORDER BY run_at, id
|
105
160
|
LIMIT #{per_page}::int
|
106
161
|
OFFSET #{offset}::int
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Que
|
4
|
+
module View
|
5
|
+
class Pagination
|
6
|
+
attr_reader :page, :count, :per_page
|
7
|
+
|
8
|
+
def initialize(params: {})
|
9
|
+
@page = params[:page]
|
10
|
+
@count = params[:count]
|
11
|
+
@per_page = params[:per_page]
|
12
|
+
end
|
13
|
+
|
14
|
+
def offset
|
15
|
+
return 0 if page == 1
|
16
|
+
|
17
|
+
per_page * (page.to_i - 1)
|
18
|
+
end
|
19
|
+
|
20
|
+
def next_page
|
21
|
+
page + 1 unless last_page?
|
22
|
+
end
|
23
|
+
|
24
|
+
def next_page?
|
25
|
+
page < total_pages
|
26
|
+
end
|
27
|
+
|
28
|
+
def previous_page
|
29
|
+
page - 1 unless first_page?
|
30
|
+
end
|
31
|
+
|
32
|
+
def previous_page?
|
33
|
+
page > 1
|
34
|
+
end
|
35
|
+
|
36
|
+
def last_page?
|
37
|
+
page == total_pages
|
38
|
+
end
|
39
|
+
|
40
|
+
def first_page?
|
41
|
+
page == 1
|
42
|
+
end
|
43
|
+
|
44
|
+
def total_pages
|
45
|
+
(count / per_page.to_f).ceil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/que/view/version.rb
CHANGED
data/lib/que/view.rb
CHANGED
@@ -6,13 +6,14 @@ require 'que/view/version'
|
|
6
6
|
require 'que/view/engine'
|
7
7
|
require 'que/view/configuration'
|
8
8
|
require 'que/view/dsl'
|
9
|
+
require 'que/view/pagination'
|
9
10
|
|
10
11
|
module Que
|
11
12
|
module View
|
12
13
|
extend self
|
13
14
|
extend Forwardable
|
14
15
|
|
15
|
-
# Public: Configure
|
16
|
+
# Public: Configure que view.
|
16
17
|
#
|
17
18
|
# Que::View.configure do |config|
|
18
19
|
# end
|
@@ -26,15 +27,17 @@ module Que
|
|
26
27
|
@configuration ||= Configuration.new
|
27
28
|
end
|
28
29
|
|
29
|
-
# Public: Default per thread
|
30
|
+
# Public: Default per thread que view instance if configured.
|
30
31
|
# Returns Que::View::DSL instance.
|
31
32
|
def instance
|
32
33
|
Thread.current[:que_view_instance] ||= DSL.new
|
33
34
|
end
|
34
35
|
|
35
36
|
# Public: All the methods delegated to instance. These should match the interface of Que::View::DSL.
|
36
|
-
def_delegators :instance,
|
37
|
-
:
|
37
|
+
def_delegators :instance, :fetch_dashboard_stats,
|
38
|
+
:fetch_queue_names, :fetch_job_names,
|
39
|
+
:fetch_running_jobs, :fetch_failing_jobs, :fetch_scheduled_jobs, :fetch_finished_jobs,
|
40
|
+
:fetch_expired_jobs, :fetch_job,
|
38
41
|
:delete_failing_jobs, :delete_scheduled_jobs, :delete_job,
|
39
42
|
:reschedule_scheduled_jobs, :reschedule_failing_jobs, :reschedule_job
|
40
43
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: que-view
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bogdanov Anton
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-03-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: que
|
@@ -175,6 +175,7 @@ files:
|
|
175
175
|
- lib/que/view/configuration.rb
|
176
176
|
- lib/que/view/dsl.rb
|
177
177
|
- lib/que/view/engine.rb
|
178
|
+
- lib/que/view/pagination.rb
|
178
179
|
- lib/que/view/version.rb
|
179
180
|
homepage: https://github.com/kortirso/que-view
|
180
181
|
licenses:
|