jobs_dashboard 0.3.4
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 +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
|