jobs_dashboard 0.3.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/gem-push.yml +34 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +60 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +129 -0
- data/LICENSE.txt +21 -0
- data/README.md +133 -0
- data/Rakefile +8 -0
- data/app/assets/stylesheets/jobs_dashboard/application.scss +214 -0
- data/app/assets/stylesheets/jobs_dashboard/modules/buttons.sass +15 -0
- data/app/assets/stylesheets/jobs_dashboard/modules/card.sass +22 -0
- data/app/assets/stylesheets/jobs_dashboard/modules/description.sass +17 -0
- data/app/assets/stylesheets/jobs_dashboard/modules/grid.sass +17 -0
- data/app/assets/stylesheets/jobs_dashboard/modules/inputs.sass +31 -0
- data/app/assets/stylesheets/jobs_dashboard/modules/item-list.sass +10 -0
- data/app/assets/stylesheets/jobs_dashboard/modules/labels.sass +12 -0
- data/app/assets/stylesheets/jobs_dashboard/modules/statistics.sass +15 -0
- data/app/assets/stylesheets/jobs_dashboard/modules/table.sass +38 -0
- data/app/assets/stylesheets/jobs_dashboard/pages/jobs_logs/show.sass +36 -0
- data/app/assets/stylesheets/jobs_dashboard/reset.scss +135 -0
- data/app/assets/stylesheets/jobs_dashboard/variables.sass +6 -0
- data/app/controllers/jobs_dashboard/application_controller.rb +20 -0
- data/app/controllers/jobs_dashboard/dashboard_controller.rb +12 -0
- data/app/controllers/jobs_dashboard/job_logs_controller.rb +17 -0
- data/app/helpers/jobs_dashboard/application_helper.rb +31 -0
- data/app/models/jobs_dashboard/application_record.rb +5 -0
- data/app/models/jobs_dashboard/job_log.rb +32 -0
- data/app/views/jobs_dashboard/dashboard/_job_row.slim +8 -0
- data/app/views/jobs_dashboard/dashboard/index.slim +40 -0
- data/app/views/jobs_dashboard/job_logs/_stats.html.erb +0 -0
- data/app/views/jobs_dashboard/job_logs/index.slim +84 -0
- data/app/views/jobs_dashboard/job_logs/show.slim +89 -0
- data/app/views/kaminari/jobs_dashboard/_first_page.html.erb +11 -0
- data/app/views/kaminari/jobs_dashboard/_gap.html.erb +8 -0
- data/app/views/kaminari/jobs_dashboard/_last_page.html.erb +11 -0
- data/app/views/kaminari/jobs_dashboard/_next_page.html.erb +11 -0
- data/app/views/kaminari/jobs_dashboard/_page.html.erb +12 -0
- data/app/views/kaminari/jobs_dashboard/_paginator.html.erb +25 -0
- data/app/views/kaminari/jobs_dashboard/_prev_page.html.erb +11 -0
- data/app/views/layouts/jobs_dashboard/application.html.erb +15 -0
- data/app/views/shared/_logo.slim +4 -0
- data/app/views/shared/_navigation.slim +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/config/initializers/ransack.rb +11 -0
- data/config/locales/default.fr.yml +219 -0
- data/config/locales/jobs_dashboard.en.yml +33 -0
- data/config/locales/jobs_dashboard.fr.yml +37 -0
- data/config/routes.rb +5 -0
- data/jobs_dashboard.gemspec +38 -0
- data/lib/generators/active_record/job_log_migration_generator.rb +24 -0
- data/lib/generators/active_record/templates/migration.rb +20 -0
- data/lib/jobs_dashboard/client_middleware.rb +56 -0
- data/lib/jobs_dashboard/engine.rb +18 -0
- data/lib/jobs_dashboard/server_middleware.rb +62 -0
- data/lib/jobs_dashboard/storage.rb +54 -0
- data/lib/jobs_dashboard/version.rb +5 -0
- data/lib/jobs_dashboard/worker.rb +25 -0
- data/lib/jobs_dashboard.rb +12 -0
- metadata +161 -0
@@ -0,0 +1,11 @@
|
|
1
|
+
<%# Link to the "Last" page
|
2
|
+
- available local variables
|
3
|
+
url: url to the last page
|
4
|
+
current_page: a page object for the currently displayed page
|
5
|
+
total_pages: total number of pages
|
6
|
+
per_page: number of items to fetch per page
|
7
|
+
remote: data-remote
|
8
|
+
-%>
|
9
|
+
<span class="last">
|
10
|
+
<%= link_to_unless current_page.last?, t('views.pagination.last').html_safe, url, remote: remote %>
|
11
|
+
</span>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<%# Link to the "Next" page
|
2
|
+
- available local variables
|
3
|
+
url: url to the next page
|
4
|
+
current_page: a page object for the currently displayed page
|
5
|
+
total_pages: total number of pages
|
6
|
+
per_page: number of items to fetch per page
|
7
|
+
remote: data-remote
|
8
|
+
-%>
|
9
|
+
<span class="next">
|
10
|
+
<%= link_to_unless current_page.last?, t('views.pagination.next').html_safe, url, rel: 'next', remote: remote %>
|
11
|
+
</span>
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<%# Link showing page number
|
2
|
+
- available local variables
|
3
|
+
page: a page object for "this" page
|
4
|
+
url: url to this page
|
5
|
+
current_page: a page object for the currently displayed page
|
6
|
+
total_pages: total number of pages
|
7
|
+
per_page: number of items to fetch per page
|
8
|
+
remote: data-remote
|
9
|
+
-%>
|
10
|
+
<span class="page<%= ' current' if page.current? %>">
|
11
|
+
<%= link_to_unless page.current?, page, url, {remote: remote, rel: page.rel} %>
|
12
|
+
</span>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<%# The container tag
|
2
|
+
- available local variables
|
3
|
+
current_page: a page object for the currently displayed page
|
4
|
+
total_pages: total number of pages
|
5
|
+
per_page: number of items to fetch per page
|
6
|
+
remote: data-remote
|
7
|
+
paginator: the paginator that renders the pagination tags inside
|
8
|
+
-%>
|
9
|
+
<%= paginator.render do -%>
|
10
|
+
<nav class="pagination" role="navigation" aria-label="pager">
|
11
|
+
<%= first_page_tag unless current_page.first? %>
|
12
|
+
<%= prev_page_tag unless current_page.first? %>
|
13
|
+
<% each_page do |page| -%>
|
14
|
+
<% if page.display_tag? -%>
|
15
|
+
<%= page_tag page %>
|
16
|
+
<% elsif !page.was_truncated? -%>
|
17
|
+
<%= gap_tag %>
|
18
|
+
<% end -%>
|
19
|
+
<% end -%>
|
20
|
+
<% unless current_page.out_of_range? %>
|
21
|
+
<%= next_page_tag unless current_page.last? %>
|
22
|
+
<%= last_page_tag unless current_page.last? %>
|
23
|
+
<% end %>
|
24
|
+
</nav>
|
25
|
+
<% end -%>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<%# Link to the "Previous" page
|
2
|
+
- available local variables
|
3
|
+
url: url to the previous page
|
4
|
+
current_page: a page object for the currently displayed page
|
5
|
+
total_pages: total number of pages
|
6
|
+
per_page: number of items to fetch per page
|
7
|
+
remote: data-remote
|
8
|
+
-%>
|
9
|
+
<span class="prev">
|
10
|
+
<%= link_to_unless current_page.first?, t('views.pagination.previous').html_safe, url, rel: 'prev', remote: remote %>
|
11
|
+
</span>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Jobs dashboard</title>
|
5
|
+
<%= csrf_meta_tags %>
|
6
|
+
<%= csp_meta_tag %>
|
7
|
+
<%= stylesheet_link_tag "jobs_dashboard/application", media: "all" %>
|
8
|
+
</head>
|
9
|
+
<body class="<%= controller_name %>-<%= action_name %>">
|
10
|
+
<%= render partial: 'shared/navigation' %>
|
11
|
+
<div class="layout-content">
|
12
|
+
<%= yield %>
|
13
|
+
</div>
|
14
|
+
</body>
|
15
|
+
</html>
|
@@ -0,0 +1,4 @@
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
2
|
+
<!--! Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><
|
3
|
+
defs><style>.fa-secondary{opacity:.4}</style></>
|
4
|
+
<path class="fa-primary" d="M112 192c0-35.3 28.7-64 64-64H336c35.3 0 64 28.7 64 64V384c0 35.3-28.7 64-64 64H176c-35.3 0-64-28.7-64-64V192zm48 24v80c0 13.3 10.7 24 24 24H328c13.3 0 24-10.7 24-24V216c0-13.3-10.7-24-24-24H184c-13.3 0-24 10.7-24 24zm96 200a32 32 0 1 0 0-64 32 32 0 1 0 0 64z"/><path class="fa-secondary" d="M256 0C114.6 0 0 114.6 0 256V448c0 35.3 28.7 64 64 64h41.4l64.3-64.3C137.3 444.5 112 417.2 112 384V192c0-35.3 28.7-64 64-64H336c35.3 0 64 28.7 64 64V384c0 33.2-25.3 60.5-57.7 63.7L406.6 512H448c35.3 0 64-28.7 64-64V256C512 114.6 397.4 0 256 0zM361.4 512l-64-64H214.6l-64 64H361.4zM160 216v80c0 13.3 10.7 24 24 24H328c13.3 0 24-10.7 24-24V216c0-13.3-10.7-24-24-24H184c-13.3 0-24 10.7-24 24z"/></svg>
|
@@ -0,0 +1,12 @@
|
|
1
|
+
.navbar
|
2
|
+
.nav-wrapper
|
3
|
+
.logo
|
4
|
+
= render 'shared/logo'
|
5
|
+
ul.nav
|
6
|
+
li.nav-item class="#{'active' if current_page?(root_path)}"
|
7
|
+
= link_to t(:'jobs_dashboard.menu.dashboard'), root_path
|
8
|
+
li.nav-item class="#{'active' if params[:controller] == 'jobs_dashboard/job_logs'}"
|
9
|
+
= link_to t(:'jobs_dashboard.menu.logs'), job_logs_path
|
10
|
+
header.title-wrapper
|
11
|
+
h1
|
12
|
+
= @title || t(:"jobs_dashboard.#{controller.controller_name}.#{controller.action_name}.title")
|
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "jobs_dashboard"
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require "irb"
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
Ransack.configure do |config|
|
2
|
+
config.add_predicate 'dategteq',
|
3
|
+
arel_predicate: 'gteq',
|
4
|
+
formatter: proc { |v| v.beginning_of_day },
|
5
|
+
type: :date
|
6
|
+
|
7
|
+
config.add_predicate 'datelteq',
|
8
|
+
arel_predicate: 'lteq',
|
9
|
+
formatter: proc { |v| v.end_of_day },
|
10
|
+
type: :date
|
11
|
+
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
---
|
2
|
+
fr:
|
3
|
+
activerecord:
|
4
|
+
errors:
|
5
|
+
messages:
|
6
|
+
record_invalid: 'La validation a échoué : %{errors}'
|
7
|
+
restrict_dependent_destroy:
|
8
|
+
has_one: Vous ne pouvez pas supprimer l'enregistrement car un(e) %{record}
|
9
|
+
dépendant(e) existe
|
10
|
+
has_many: Vous ne pouvez pas supprimer l'enregistrement parce que les %{record}
|
11
|
+
dépendants existent
|
12
|
+
date:
|
13
|
+
abbr_day_names:
|
14
|
+
- dim
|
15
|
+
- lun
|
16
|
+
- mar
|
17
|
+
- mer
|
18
|
+
- jeu
|
19
|
+
- ven
|
20
|
+
- sam
|
21
|
+
abbr_month_names:
|
22
|
+
-
|
23
|
+
- jan.
|
24
|
+
- fév.
|
25
|
+
- mar.
|
26
|
+
- avr.
|
27
|
+
- mai
|
28
|
+
- juin
|
29
|
+
- juil.
|
30
|
+
- août
|
31
|
+
- sept.
|
32
|
+
- oct.
|
33
|
+
- nov.
|
34
|
+
- déc.
|
35
|
+
day_names:
|
36
|
+
- dimanche
|
37
|
+
- lundi
|
38
|
+
- mardi
|
39
|
+
- mercredi
|
40
|
+
- jeudi
|
41
|
+
- vendredi
|
42
|
+
- samedi
|
43
|
+
formats:
|
44
|
+
default: "%d/%m/%Y"
|
45
|
+
long: "%e %B %Y"
|
46
|
+
short: "%e %b"
|
47
|
+
month_names:
|
48
|
+
-
|
49
|
+
- janvier
|
50
|
+
- février
|
51
|
+
- mars
|
52
|
+
- avril
|
53
|
+
- mai
|
54
|
+
- juin
|
55
|
+
- juillet
|
56
|
+
- août
|
57
|
+
- septembre
|
58
|
+
- octobre
|
59
|
+
- novembre
|
60
|
+
- décembre
|
61
|
+
order:
|
62
|
+
- :day
|
63
|
+
- :month
|
64
|
+
- :year
|
65
|
+
datetime:
|
66
|
+
distance_in_words:
|
67
|
+
about_x_hours:
|
68
|
+
one: environ une heure
|
69
|
+
other: environ %{count} heures
|
70
|
+
about_x_months:
|
71
|
+
one: environ un mois
|
72
|
+
other: environ %{count} mois
|
73
|
+
about_x_years:
|
74
|
+
one: environ un an
|
75
|
+
other: environ %{count} ans
|
76
|
+
almost_x_years:
|
77
|
+
one: presqu'un an
|
78
|
+
other: presque %{count} ans
|
79
|
+
half_a_minute: une demi‑minute
|
80
|
+
less_than_x_seconds:
|
81
|
+
zero: moins d'une seconde
|
82
|
+
one: moins d'une seconde
|
83
|
+
other: moins de %{count} secondes
|
84
|
+
less_than_x_minutes:
|
85
|
+
zero: moins d'une minute
|
86
|
+
one: moins d'une minute
|
87
|
+
other: moins de %{count} minutes
|
88
|
+
over_x_years:
|
89
|
+
one: plus d'un an
|
90
|
+
other: plus de %{count} ans
|
91
|
+
x_seconds:
|
92
|
+
one: 1 seconde
|
93
|
+
other: "%{count} secondes"
|
94
|
+
x_minutes:
|
95
|
+
one: 1 minute
|
96
|
+
other: "%{count} minutes"
|
97
|
+
x_days:
|
98
|
+
one: 1 jour
|
99
|
+
other: "%{count} jours"
|
100
|
+
x_months:
|
101
|
+
one: 1 mois
|
102
|
+
other: "%{count} mois"
|
103
|
+
x_years:
|
104
|
+
one: 1 an
|
105
|
+
other: "%{count} ans"
|
106
|
+
prompts:
|
107
|
+
second: Seconde
|
108
|
+
minute: Minute
|
109
|
+
hour: Heure
|
110
|
+
day: Jour
|
111
|
+
month: Mois
|
112
|
+
year: Année
|
113
|
+
errors:
|
114
|
+
format: "%{attribute} %{message}"
|
115
|
+
messages:
|
116
|
+
accepted: doit être accepté(e)
|
117
|
+
blank: doit être rempli(e)
|
118
|
+
confirmation: ne concorde pas avec %{attribute}
|
119
|
+
empty: doit être rempli(e)
|
120
|
+
equal_to: doit être égal à %{count}
|
121
|
+
even: doit être pair
|
122
|
+
exclusion: n'est pas disponible
|
123
|
+
greater_than: doit être supérieur à %{count}
|
124
|
+
greater_than_or_equal_to: doit être supérieur ou égal à %{count}
|
125
|
+
inclusion: n'est pas inclus(e) dans la liste
|
126
|
+
invalid: n'est pas valide
|
127
|
+
less_than: doit être inférieur à %{count}
|
128
|
+
less_than_or_equal_to: doit être inférieur ou égal à %{count}
|
129
|
+
model_invalid: 'Validation échouée : %{errors}'
|
130
|
+
not_a_number: n'est pas un nombre
|
131
|
+
not_an_integer: doit être un nombre entier
|
132
|
+
odd: doit être impair
|
133
|
+
other_than: doit être différent de %{count}
|
134
|
+
present: doit être vide
|
135
|
+
required: doit exister
|
136
|
+
taken: est déjà utilisé(e)
|
137
|
+
too_long:
|
138
|
+
one: est trop long (pas plus d'un caractère)
|
139
|
+
other: est trop long (pas plus de %{count} caractères)
|
140
|
+
too_short:
|
141
|
+
one: est trop court (au moins un caractère)
|
142
|
+
other: est trop court (au moins %{count} caractères)
|
143
|
+
wrong_length:
|
144
|
+
one: ne fait pas la bonne longueur (doit comporter un seul caractère)
|
145
|
+
other: ne fait pas la bonne longueur (doit comporter %{count} caractères)
|
146
|
+
template:
|
147
|
+
body: 'Veuillez vérifier les champs suivants : '
|
148
|
+
header:
|
149
|
+
one: 'Impossible d''enregistrer ce(tte) %{model} : 1 erreur'
|
150
|
+
other: 'Impossible d''enregistrer ce(tte) %{model} : %{count} erreurs'
|
151
|
+
helpers:
|
152
|
+
select:
|
153
|
+
prompt: Veuillez sélectionner
|
154
|
+
submit:
|
155
|
+
create: Créer un(e) %{model}
|
156
|
+
submit: Enregistrer ce(tte) %{model}
|
157
|
+
update: Modifier ce(tte) %{model}
|
158
|
+
number:
|
159
|
+
currency:
|
160
|
+
format:
|
161
|
+
delimiter: " "
|
162
|
+
format: "%n %u"
|
163
|
+
precision: 2
|
164
|
+
separator: ","
|
165
|
+
significant: false
|
166
|
+
strip_insignificant_zeros: false
|
167
|
+
unit: "€"
|
168
|
+
format:
|
169
|
+
delimiter: " "
|
170
|
+
precision: 3
|
171
|
+
separator: ","
|
172
|
+
significant: false
|
173
|
+
strip_insignificant_zeros: false
|
174
|
+
human:
|
175
|
+
decimal_units:
|
176
|
+
format: "%n %u"
|
177
|
+
units:
|
178
|
+
billion: milliard
|
179
|
+
million: million
|
180
|
+
quadrillion: million de milliards
|
181
|
+
thousand: millier
|
182
|
+
trillion: billion
|
183
|
+
unit: ''
|
184
|
+
format:
|
185
|
+
delimiter: ''
|
186
|
+
precision: 3
|
187
|
+
significant: true
|
188
|
+
strip_insignificant_zeros: true
|
189
|
+
storage_units:
|
190
|
+
format: "%n %u"
|
191
|
+
units:
|
192
|
+
byte:
|
193
|
+
one: octet
|
194
|
+
other: octets
|
195
|
+
eb: Eo
|
196
|
+
gb: Go
|
197
|
+
kb: ko
|
198
|
+
mb: Mo
|
199
|
+
pb: Po
|
200
|
+
tb: To
|
201
|
+
percentage:
|
202
|
+
format:
|
203
|
+
delimiter: ''
|
204
|
+
format: "%n%"
|
205
|
+
precision:
|
206
|
+
format:
|
207
|
+
delimiter: ''
|
208
|
+
support:
|
209
|
+
array:
|
210
|
+
last_word_connector: " et "
|
211
|
+
two_words_connector: " et "
|
212
|
+
words_connector: ", "
|
213
|
+
time:
|
214
|
+
am: am
|
215
|
+
formats:
|
216
|
+
default: "%d %B %Y %Hh %Mmin %Ss"
|
217
|
+
long: "%A %d %B %Y %Hh%M"
|
218
|
+
short: "%d %b %Hh%M"
|
219
|
+
pm: pm
|
@@ -0,0 +1,33 @@
|
|
1
|
+
en:
|
2
|
+
form:
|
3
|
+
select:
|
4
|
+
all: All
|
5
|
+
jobs_dashboard:
|
6
|
+
dashboard:
|
7
|
+
index:
|
8
|
+
title: Dashboard
|
9
|
+
statuses:
|
10
|
+
complete: Complete
|
11
|
+
queued: Queued
|
12
|
+
failed: Failed
|
13
|
+
interrupted: Interrupted
|
14
|
+
retrying: Retrying
|
15
|
+
working: Working
|
16
|
+
menu:
|
17
|
+
dashboard: Dashboard
|
18
|
+
logs: Logs
|
19
|
+
table:
|
20
|
+
headers:
|
21
|
+
finished_at: Finished at
|
22
|
+
created_at: Created at
|
23
|
+
job: Job
|
24
|
+
sidekiq_jid: Sidekiq ID
|
25
|
+
queue: Queue
|
26
|
+
status: Status
|
27
|
+
sentences:
|
28
|
+
clear_filters: Clear filters
|
29
|
+
no_ongoing_jobs: No ongoing jobs
|
30
|
+
no_awaiting_jobs: No awaiting jobs
|
31
|
+
words:
|
32
|
+
to: to
|
33
|
+
submit: 'Apply filters'
|
@@ -0,0 +1,37 @@
|
|
1
|
+
fr:
|
2
|
+
form:
|
3
|
+
select:
|
4
|
+
all: Tout
|
5
|
+
jobs_dashboard:
|
6
|
+
job_logs:
|
7
|
+
index:
|
8
|
+
title: Job logs
|
9
|
+
dashboard:
|
10
|
+
index:
|
11
|
+
title: Dashboard
|
12
|
+
statuses:
|
13
|
+
complete: Terminé
|
14
|
+
queued: En attente
|
15
|
+
failed: Échoué
|
16
|
+
interrupted: Interrompu
|
17
|
+
retrying: Nouvelle tentative
|
18
|
+
working: En cours
|
19
|
+
menu:
|
20
|
+
dashboard: Dashboard
|
21
|
+
logs: Job logs
|
22
|
+
table:
|
23
|
+
headers:
|
24
|
+
finished_at: Terminé le
|
25
|
+
created_at: Créé le
|
26
|
+
item_type: Job
|
27
|
+
job_id: Sidekiq ID
|
28
|
+
sidekiq_jid: Sidekiq ID
|
29
|
+
queue: File
|
30
|
+
status: Statut
|
31
|
+
sentences:
|
32
|
+
clear_filters: Supprimer les filtres
|
33
|
+
no_ongoing_jobs: Aucun job en cours
|
34
|
+
no_awaiting_jobs: Aucun job en attente
|
35
|
+
words:
|
36
|
+
to: au
|
37
|
+
submit: 'Filtrer'
|
data/config/routes.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/jobs_dashboard/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "jobs_dashboard"
|
7
|
+
spec.version = JobsDashboard::VERSION
|
8
|
+
spec.authors = ["Patryk"]
|
9
|
+
spec.email = ["patryk@9troisquarts.com"]
|
10
|
+
|
11
|
+
spec.summary = "Sidekiq jobs dashboard"
|
12
|
+
spec.homepage = "https://github.com/9troisquarts/jobs_dashboard"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
16
|
+
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = "https://github.com/9troisquarts/jobs_dashboard"
|
19
|
+
# spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
20
|
+
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
22
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
24
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
25
|
+
end
|
26
|
+
spec.bindir = "exe"
|
27
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ["lib"]
|
29
|
+
|
30
|
+
# Uncomment to register a new dependency of your gem
|
31
|
+
spec.add_dependency 'sidekiq', '>= 6.0'
|
32
|
+
spec.add_dependency 'kaminari'
|
33
|
+
spec.add_dependency 'slim-rails'
|
34
|
+
spec.add_dependency 'ransack'
|
35
|
+
|
36
|
+
# For more information and examples about making a new gem, checkout our
|
37
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
38
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rails/generators/active_record'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Generators
|
5
|
+
class JobLogMigrationGenerator < Base
|
6
|
+
source_root File.expand_path("../templates", __FILE__)
|
7
|
+
argument :name, :type => :string, :default => "add_job_logs_table"
|
8
|
+
|
9
|
+
def create_migration_file
|
10
|
+
migration_template "migration.rb", "db/migrate/#{file_name}.rb"
|
11
|
+
end
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
def job_log_table_name
|
16
|
+
'jobs_dashboard_job_logs'
|
17
|
+
end
|
18
|
+
|
19
|
+
def migration_version
|
20
|
+
"[#{ActiveRecord::Migration.current_version}]"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
|
2
|
+
def change
|
3
|
+
create_table :<%= job_log_table_name %> do |t|
|
4
|
+
t.string :sidekiq_jid, :null => false
|
5
|
+
t.string :status
|
6
|
+
t.string :item_type
|
7
|
+
t.text :args, limit: 4294967295
|
8
|
+
t.text :logs, limit: 4294967295
|
9
|
+
t.boolean :retry, null: false, default: false
|
10
|
+
t.string :queue
|
11
|
+
t.text :error_message, limit: 4294967295
|
12
|
+
t.text :backtrace, limit: 4294967295
|
13
|
+
t.datetime :finished_at
|
14
|
+
t.timestamps
|
15
|
+
end
|
16
|
+
|
17
|
+
add_index :<%= job_log_table_name %>, :sidekiq_jid, :unique => true
|
18
|
+
add_index :<%= job_log_table_name %>, :updated_at
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'sidekiq/api'
|
2
|
+
require 'jobs_dashboard/storage'
|
3
|
+
|
4
|
+
module JobsDashboard
|
5
|
+
# Should be in the client middleware chain
|
6
|
+
class ClientMiddleware
|
7
|
+
include Storage
|
8
|
+
|
9
|
+
# Parameterized initialization, use it when adding middleware to client chain
|
10
|
+
# chain.add JobsDashboard::ClientMiddleware
|
11
|
+
# @param [Hash] opts middleware initialization options
|
12
|
+
# @option opts [Fixnum] :expiration ttl for complete jobs
|
13
|
+
def initialize(opts = {})
|
14
|
+
@expiration = opts[:expiration]
|
15
|
+
end
|
16
|
+
|
17
|
+
# Uses msg['jid'] id and puts :queued status in the job's Redis hash
|
18
|
+
# @param [Class] worker_class if includes JobsDashboard::Worker, the job gets processed with the plugin
|
19
|
+
# @param [Array] msg job arguments
|
20
|
+
# @param [String] queue the queue's name
|
21
|
+
# @param [ConnectionPool] redis_pool optional redis connection pool
|
22
|
+
def call(worker, msg, queue, redis_pool=nil)
|
23
|
+
unless get_jobs_dashboard_options(msg)[:skip]
|
24
|
+
store_for_id(msg['jid'], {
|
25
|
+
item_type: msg['class'],
|
26
|
+
retry: msg['retry'],
|
27
|
+
queue: msg['queue'],
|
28
|
+
args: msg['args'],
|
29
|
+
status: 'queued'
|
30
|
+
})
|
31
|
+
end
|
32
|
+
|
33
|
+
yield
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
def get_jobs_dashboard_options(msg)
|
39
|
+
klass = msg["args"][0]["job_class"] || msg["class"] rescue msg["class"]
|
40
|
+
job_class = klass.is_a?(Class) ? klass : Module.const_get(klass)
|
41
|
+
job_class.get_sidekiq_options['jobs_dashboard'] || {}
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
# Helper method to easily configure sidekiq-status client middleware
|
47
|
+
# whatever the Sidekiq version is.
|
48
|
+
# @param [Sidekiq] sidekiq_config the Sidekiq config
|
49
|
+
# @param [Hash] client_middleware_options client middleware initialization options
|
50
|
+
# @option client_middleware_options [Fixnum] :expiration ttl for complete jobs
|
51
|
+
def self.configure_client_middleware(sidekiq_config, client_middleware_options = {})
|
52
|
+
sidekiq_config.client_middleware do |chain|
|
53
|
+
chain.add JobsDashboard::ClientMiddleware, client_middleware_options
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module JobsDashboard
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace JobsDashboard
|
4
|
+
initializer "jobs_dashboard.assets.precompile" do |app|
|
5
|
+
app.config.assets.precompile += %w( jobs_dashboard/application.css )
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'slim-rails'
|
9
|
+
end
|
10
|
+
|
11
|
+
self.mattr_accessor :additional_params
|
12
|
+
self.additional_params = []
|
13
|
+
|
14
|
+
def self.setup(&block)
|
15
|
+
yield self
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'jobs_dashboard/storage'
|
2
|
+
module JobsDashboard
|
3
|
+
class ServerMiddleware
|
4
|
+
|
5
|
+
include JobsDashboard::Storage
|
6
|
+
|
7
|
+
# Options can be send in sidekiq.rb initializer
|
8
|
+
# @param [Hash] opts middleware initialization options
|
9
|
+
# @option opts
|
10
|
+
def initialize(opts = {})
|
11
|
+
end
|
12
|
+
|
13
|
+
# Uses sidekiq's internal jid as id
|
14
|
+
# Store worker status in DB
|
15
|
+
# @param [Worker] worker worker instance
|
16
|
+
# @param [Array] msg job args, should have jid format
|
17
|
+
# @param [String] queue name
|
18
|
+
def call(worker, msg, queue)
|
19
|
+
# Determine the actual job class
|
20
|
+
# Bypass if attributes skip is set
|
21
|
+
if get_jobs_dashboard_options(worker)[:skip]
|
22
|
+
yield
|
23
|
+
return
|
24
|
+
end
|
25
|
+
|
26
|
+
update_job_status worker.jid, 'working'
|
27
|
+
yield
|
28
|
+
update_job_status worker.jid, 'complete'
|
29
|
+
#rescue Worker::Stopped
|
30
|
+
# update_job_status worker.jid, 'stopped'
|
31
|
+
rescue SystemExit, Interrupt
|
32
|
+
update_job_status worker.jid, 'interrupted'
|
33
|
+
raise
|
34
|
+
rescue Exception => e
|
35
|
+
status = :failed
|
36
|
+
update_job_status worker.jid, 'failed'
|
37
|
+
store_for_id(worker.jid, {
|
38
|
+
error_message: e.message,
|
39
|
+
backtrace: e.backtrace
|
40
|
+
})
|
41
|
+
raise
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def get_jobs_dashboard_options(worker)
|
46
|
+
worker.class.get_sidekiq_options['jobs_dashboard'] || {}
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
# Helper method to easily configure sidekiq-status server middleware
|
52
|
+
# whatever the Sidekiq version is.
|
53
|
+
# @param [Sidekiq] sidekiq_config the Sidekiq config
|
54
|
+
# @param [Hash] server_middleware_options server middleware initialization options
|
55
|
+
# @option server_middleware_options
|
56
|
+
def self.configure_server_middleware(sidekiq_config, server_middleware_options = {})
|
57
|
+
sidekiq_config.server_middleware do |chain|
|
58
|
+
chain.add JobsDashboard::ServerMiddleware, server_middleware_options
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|