sidekiq-tasks 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +13 -0
- data/docs/task.png +0 -0
- data/lib/sidekiq/tasks/storage.rb +36 -20
- data/lib/sidekiq/tasks/strategies/rules/enable_with_comment.rb +12 -0
- data/lib/sidekiq/tasks/task.rb +11 -3
- data/lib/sidekiq/tasks/version.rb +1 -1
- data/lib/sidekiq/tasks/web/extension.rb +2 -0
- data/lib/sidekiq/tasks/web/helpers/pagination_helper.rb +1 -12
- data/lib/sidekiq/tasks/web/helpers/tag_helper.rb +24 -0
- data/lib/sidekiq/tasks/web/helpers/task_helper.rb +26 -0
- data/lib/sidekiq/tasks/web.rb +12 -3
- data/web/assets/tasks/css/components/status_badges.css +72 -0
- data/web/assets/tasks/css/components/tables.css +19 -0
- data/web/assets/tasks/css/components/tooltips.css +42 -0
- data/web/assets/tasks/css/ext.css +2 -1
- data/web/assets/tasks/js/tooltips_manager.js +51 -0
- data/web/locales/en.yml +7 -0
- data/web/locales/fr.yml +8 -1
- data/web/views/task.erb +56 -27
- data/web/views/tasks.erb +36 -26
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '012139adfe0354b0f2521146f617c043aa6f940d3841827a5a8b1d68a729a4d5'
|
4
|
+
data.tar.gz: f4d1b0aa4b214a99a92fbad2b26ba39f610b1ded9d7e81524f272e93da281a05
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0ca5fcf42d4c2ec26ea1f67791d428418e610b24746a2599e1805b4440d3af747a21d523ebe19f01d81062f718454791ce5bfeab64073414b680a444557859c0
|
7
|
+
data.tar.gz: bb66768aa135be0b6e82dff713a5425be34a3a3a4945e017006766f6072572d26c7b1197da1fbdbd983b2daf36bb7c194634db2e6eae166e653e0a8c44bbe6a0
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,18 @@
|
|
1
1
|
## Changelog
|
2
2
|
|
3
|
+
### [0.1.6] - 2025-05-11
|
4
|
+
|
5
|
+
- Enable CI workflows on all branches. (#4)
|
6
|
+
- Add description to task list. (#2)
|
7
|
+
- Detect magic comment before multiline desc. (#3)
|
8
|
+
- Fix style tag rendering and plugin registration for Sidekiq Web UI 7.3+ and 8.0+. (#1)
|
9
|
+
- Enable horizontal scroll on tasks table for mobile. (#5)
|
10
|
+
|
11
|
+
### [0.1.5] - 2025-05-04
|
12
|
+
|
13
|
+
- Add duration, status and error reports to history.
|
14
|
+
- Fix Code Climate report by updating CI runner from `ubuntu-20.04` to `ubuntu-22.04`.
|
15
|
+
|
3
16
|
### [0.1.4] - 2025-03-23
|
4
17
|
|
5
18
|
- Fix gem load error by moving the entrypoint to the correct path.
|
data/docs/task.png
CHANGED
Binary file
|
@@ -3,6 +3,7 @@ module Sidekiq
|
|
3
3
|
class Storage
|
4
4
|
JID_PREFIX = "task".freeze
|
5
5
|
HISTORY_LIMIT = 10
|
6
|
+
ERROR_MESSAGE_MAX_LENGTH = 255
|
6
7
|
|
7
8
|
attr_reader :task_name
|
8
9
|
|
@@ -22,60 +23,75 @@ module Sidekiq
|
|
22
23
|
stored_time("last_enqueue_at")
|
23
24
|
end
|
24
25
|
|
25
|
-
def last_execution_at
|
26
|
-
stored_time("last_execution_at")
|
27
|
-
end
|
28
|
-
|
29
26
|
def history
|
30
|
-
|
27
|
+
raw_entries = Sidekiq.redis { |conn| conn.lrange(history_key, 0, -1) }
|
28
|
+
|
29
|
+
return [] unless raw_entries
|
30
|
+
|
31
|
+
raw_entries.map do |raw|
|
31
32
|
entry = Sidekiq.load_json(raw)
|
32
|
-
|
33
|
-
|
33
|
+
%w[enqueued_at executed_at finished_at].each do |key|
|
34
|
+
entry[key] = Time.at(entry[key]) if entry[key]
|
35
|
+
end
|
34
36
|
entry
|
35
37
|
end
|
36
|
-
|
37
|
-
redis_history || []
|
38
38
|
end
|
39
39
|
|
40
40
|
def store_history(jid, task_args, time)
|
41
41
|
Sidekiq.redis do |conn|
|
42
|
-
task_trace = {jid: jid, name: task_name, args: task_args, enqueued_at: time.
|
42
|
+
task_trace = {jid: jid, name: task_name, args: task_args, enqueued_at: time.to_f}
|
43
43
|
conn.lpush(history_key, Sidekiq.dump_json(task_trace))
|
44
44
|
conn.ltrim(history_key, 0, HISTORY_LIMIT - 1)
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
48
|
def store_enqueue(jid, args)
|
49
|
-
time = Time.now
|
49
|
+
time = Time.now.to_f
|
50
50
|
store_time(time, "last_enqueue_at")
|
51
51
|
store_history(jid, args, time)
|
52
52
|
end
|
53
53
|
|
54
|
-
def store_execution(jid)
|
55
|
-
|
56
|
-
|
57
|
-
|
54
|
+
def store_execution(jid, time_key)
|
55
|
+
update_history_entry(jid) do |entry|
|
56
|
+
entry.merge(time_key => Time.now.to_f)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def store_execution_error(jid, error)
|
61
|
+
update_history_entry(jid) do |entry|
|
62
|
+
error_message = truncate_message("#{error.class}: #{error.message}", ERROR_MESSAGE_MAX_LENGTH)
|
63
|
+
entry.merge("error" => error_message)
|
64
|
+
end
|
58
65
|
end
|
59
66
|
|
60
67
|
private
|
61
68
|
|
69
|
+
def truncate_message(message, max_length)
|
70
|
+
return message if message.length <= max_length
|
71
|
+
|
72
|
+
"#{message[0...(max_length - 3)]}..."
|
73
|
+
end
|
74
|
+
|
62
75
|
def store_time(time, time_key)
|
63
|
-
Sidekiq.redis { |conn| conn.hset(jid_key, time_key, time.
|
76
|
+
Sidekiq.redis { |conn| conn.hset(jid_key, time_key, time.to_f) }
|
64
77
|
end
|
65
78
|
|
66
79
|
def stored_time(time_key)
|
67
80
|
timestamp = Sidekiq.redis { |conn| conn.hget(jid_key, time_key) }
|
68
81
|
|
69
|
-
[nil, ""].include?(timestamp) ? nil : Time.at(timestamp.
|
82
|
+
[nil, ""].include?(timestamp) ? nil : Time.at(timestamp.to_f)
|
70
83
|
end
|
71
84
|
|
72
|
-
def
|
85
|
+
def update_history_entry(jid)
|
73
86
|
Sidekiq.redis do |conn|
|
74
|
-
conn.lrange(history_key, 0, -1)
|
87
|
+
entries = conn.lrange(history_key, 0, -1)
|
88
|
+
|
89
|
+
entries.each_with_index do |raw, index|
|
75
90
|
entry = Sidekiq.load_json(raw)
|
76
91
|
next unless entry["jid"] == jid
|
77
92
|
|
78
|
-
|
93
|
+
updated_entry = yield(entry)
|
94
|
+
conn.lset(history_key, index, Sidekiq.dump_json(updated_entry))
|
79
95
|
break
|
80
96
|
end
|
81
97
|
end
|
@@ -12,6 +12,7 @@ module Sidekiq
|
|
12
12
|
return false if lines.nil?
|
13
13
|
|
14
14
|
return true if task_has_magic_comment?(lines, line_number)
|
15
|
+
return true if desc_has_magic_comment?(task.full_comment, lines, line_number)
|
15
16
|
|
16
17
|
namespace_line_index = find_namespace_line_index(lines, task)
|
17
18
|
return false unless namespace_line_index
|
@@ -33,6 +34,17 @@ module Sidekiq
|
|
33
34
|
raise ArgumentError, "File '#{file}' not found"
|
34
35
|
end
|
35
36
|
|
37
|
+
def desc_has_magic_comment?(desc, lines, task_line)
|
38
|
+
return false unless desc&.include?("\n")
|
39
|
+
|
40
|
+
desc_line_index = lines[0...task_line].rindex { |line| line.strip.start_with?("desc") }
|
41
|
+
|
42
|
+
return false unless desc_line_index
|
43
|
+
|
44
|
+
comment_line = lines[desc_line_index - 1]&.strip
|
45
|
+
!!(comment_line =~ magic_comment_regex)
|
46
|
+
end
|
47
|
+
|
36
48
|
def task_has_magic_comment?(lines, task_line)
|
37
49
|
context_range = (task_line - 3..task_line).to_a.select { |i| i >= 0 }
|
38
50
|
context_range.reverse.any? do |i|
|
data/lib/sidekiq/tasks/task.rb
CHANGED
@@ -7,7 +7,7 @@ module Sidekiq
|
|
7
7
|
include Sidekiq::Tasks::Validations
|
8
8
|
|
9
9
|
def_delegators :metadata, :name, :desc, :file, :args
|
10
|
-
def_delegators :storage, :last_enqueue_at, :
|
10
|
+
def_delegators :storage, :last_enqueue_at, :history
|
11
11
|
|
12
12
|
attr_reader :metadata, :strategy
|
13
13
|
|
@@ -29,9 +29,17 @@ module Sidekiq
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def execute(params = {}, jid: nil)
|
32
|
-
|
32
|
+
storage.store_execution(jid, "executed_at")
|
33
33
|
|
34
|
-
|
34
|
+
begin
|
35
|
+
strategy.execute_task(name, params)
|
36
|
+
rescue => e
|
37
|
+
storage.store_execution(jid, "finished_at")
|
38
|
+
storage.store_execution_error(jid, e)
|
39
|
+
raise
|
40
|
+
end
|
41
|
+
|
42
|
+
storage.store_execution(jid, "finished_at")
|
35
43
|
end
|
36
44
|
|
37
45
|
def storage
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require "sidekiq/tasks/web/helpers/application_helper"
|
2
|
+
require "sidekiq/tasks/web/helpers/tag_helper"
|
2
3
|
require "sidekiq/tasks/web/helpers/task_helper"
|
3
4
|
require "sidekiq/tasks/web/helpers/pagination_helper"
|
4
5
|
require "sidekiq/tasks/web/search"
|
@@ -11,6 +12,7 @@ module Sidekiq
|
|
11
12
|
class Extension
|
12
13
|
def self.registered(app)
|
13
14
|
app.helpers(Sidekiq::Tasks::Web::Helpers::ApplicationHelper)
|
15
|
+
app.helpers(Sidekiq::Tasks::Web::Helpers::TagHelper)
|
14
16
|
app.helpers(Sidekiq::Tasks::Web::Helpers::TaskHelper)
|
15
17
|
app.helpers(Sidekiq::Tasks::Web::Helpers::PaginationHelper)
|
16
18
|
|
@@ -4,6 +4,7 @@ module Sidekiq
|
|
4
4
|
module Helpers
|
5
5
|
module PaginationHelper
|
6
6
|
extend self
|
7
|
+
include Sidekiq::Tasks::Web::Helpers::TagHelper
|
7
8
|
|
8
9
|
def pagination_link(root_path, link, search)
|
9
10
|
build_tag(:li, class: "st-page-item") do
|
@@ -18,18 +19,6 @@ module Sidekiq
|
|
18
19
|
|
19
20
|
private
|
20
21
|
|
21
|
-
def build_tag(tag, content = nil, **attributes, &block)
|
22
|
-
attr_string = attributes.map { |key, value| "#{key}=\"#{value}\"" }.join(" ")
|
23
|
-
|
24
|
-
"<#{tag} #{attr_string}>#{block&.call || content}</#{tag}>"
|
25
|
-
end
|
26
|
-
|
27
|
-
def build_classes(*classes, **conditions)
|
28
|
-
condition_classes = conditions.select { |_, value| value }.keys
|
29
|
-
|
30
|
-
(classes + condition_classes).join(" ")
|
31
|
-
end
|
32
|
-
|
33
22
|
def pagination_url(root_path, search, page)
|
34
23
|
"#{root_path}tasks?filter=#{ERB::Util.url_encode(search.filter)}&count=#{search.count}&page=#{page}"
|
35
24
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
module Tasks
|
3
|
+
module Web
|
4
|
+
module Helpers
|
5
|
+
module TagHelper
|
6
|
+
extend self
|
7
|
+
|
8
|
+
def build_tag(tag, content = nil, **attributes, &block)
|
9
|
+
attr_string = attributes.map { |key, value| "#{key}=\"#{value}\"" }.join(" ")
|
10
|
+
attr_string = " #{attr_string}" unless attr_string.empty?
|
11
|
+
|
12
|
+
"<#{tag}#{attr_string}>#{block&.call || content}</#{tag}>"
|
13
|
+
end
|
14
|
+
|
15
|
+
def build_classes(*classes, **conditions)
|
16
|
+
condition_classes = conditions.select { |_, value| value }.keys
|
17
|
+
|
18
|
+
(classes + condition_classes).join(" ")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -4,6 +4,7 @@ module Sidekiq
|
|
4
4
|
module Helpers
|
5
5
|
module TaskHelper
|
6
6
|
extend self
|
7
|
+
include Sidekiq::Tasks::Web::Helpers::TagHelper
|
7
8
|
|
8
9
|
def parameterize_task_name(task_name)
|
9
10
|
task_name.gsub(":", "-")
|
@@ -22,6 +23,31 @@ module Sidekiq
|
|
22
23
|
def task_url(root_path, task)
|
23
24
|
"#{root_path}tasks/#{parameterize_task_name(task.name)}"
|
24
25
|
end
|
26
|
+
|
27
|
+
def task_status(jid_history)
|
28
|
+
if jid_history["error"]
|
29
|
+
:failure
|
30
|
+
elsif jid_history["finished_at"]
|
31
|
+
:success
|
32
|
+
elsif jid_history["executed_at"]
|
33
|
+
:running
|
34
|
+
else
|
35
|
+
:pending
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def format_task_duration(start_time, end_time)
|
40
|
+
return "-" unless start_time && end_time
|
41
|
+
|
42
|
+
duration_in_milliseconds = ((end_time - start_time) * 1000).to_i
|
43
|
+
|
44
|
+
if duration_in_milliseconds >= 1000
|
45
|
+
duration_in_seconds = (duration_in_milliseconds / 1000.0).round
|
46
|
+
"#{[duration_in_seconds, 1].max}s"
|
47
|
+
else
|
48
|
+
"#{[duration_in_milliseconds, 1].max}ms"
|
49
|
+
end
|
50
|
+
end
|
25
51
|
end
|
26
52
|
end
|
27
53
|
end
|
data/lib/sidekiq/tasks/web.rb
CHANGED
@@ -12,17 +12,26 @@ module Sidekiq
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
if Sidekiq::Tasks::Web::
|
15
|
+
if Sidekiq::Tasks::Web::SIDEKIQ_GTE_8_0_0
|
16
16
|
Sidekiq::Web.configure do |config|
|
17
17
|
config.register(
|
18
18
|
Sidekiq::Tasks::Web::Extension,
|
19
19
|
name: "tasks",
|
20
|
-
tab:
|
21
|
-
index:
|
20
|
+
tab: "Tasks",
|
21
|
+
index: "tasks",
|
22
22
|
root_dir: Sidekiq::Tasks::Web::ROOT,
|
23
23
|
asset_paths: ["js", "css"]
|
24
24
|
)
|
25
25
|
end
|
26
|
+
elsif Sidekiq::Tasks::Web::SIDEKIQ_GTE_7_3_0
|
27
|
+
Sidekiq::Web.register(
|
28
|
+
Sidekiq::Tasks::Web::Extension,
|
29
|
+
name: "tasks",
|
30
|
+
tab: "Tasks",
|
31
|
+
index: "tasks",
|
32
|
+
root_dir: Sidekiq::Tasks::Web::ROOT,
|
33
|
+
asset_paths: ["js", "css"]
|
34
|
+
)
|
26
35
|
else
|
27
36
|
Sidekiq::Web.tabs["Tasks"] = "tasks"
|
28
37
|
Sidekiq::Web.register(Sidekiq::Tasks::Web::Extension)
|
@@ -0,0 +1,72 @@
|
|
1
|
+
:root {
|
2
|
+
--badge-success-text: #22713d;
|
3
|
+
--badge-success-bg: #dff5e3;
|
4
|
+
--badge-success-border: #b5e2c4;
|
5
|
+
|
6
|
+
--badge-failure-text: #a82a34;
|
7
|
+
--badge-failure-bg: #fbe3e5;
|
8
|
+
--badge-failure-border: #f5b7bc;
|
9
|
+
|
10
|
+
--badge-running-text: #0b5394;
|
11
|
+
--badge-running-bg: #dceeff;
|
12
|
+
--badge-running-border: #a9d4f2;
|
13
|
+
|
14
|
+
--badge-pending-text: #8a6d1d;
|
15
|
+
--badge-pending-bg: #fff8dc;
|
16
|
+
--badge-pending-border: #f5e6ab;
|
17
|
+
}
|
18
|
+
|
19
|
+
@media (prefers-color-scheme: dark) {
|
20
|
+
:root {
|
21
|
+
--badge-success-text: #b5e2c4;
|
22
|
+
--badge-success-bg: #1a3b28;
|
23
|
+
--badge-success-border: #2f5b3f;
|
24
|
+
|
25
|
+
--badge-failure-text: #f5b7bc;
|
26
|
+
--badge-failure-bg: #3e1b1d;
|
27
|
+
--badge-failure-border: #7a2e35;
|
28
|
+
|
29
|
+
--badge-running-text: #a9d4f2;
|
30
|
+
--badge-running-bg: #1a2c3e;
|
31
|
+
--badge-running-border: #345c7f;
|
32
|
+
|
33
|
+
--badge-pending-text: #f5e6ab;
|
34
|
+
--badge-pending-bg: #3e351a;
|
35
|
+
--badge-pending-border: #7a6a2e;
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
.st-status-badge {
|
40
|
+
display: inline-block;
|
41
|
+
padding: 3px 10px;
|
42
|
+
border-radius: 999px;
|
43
|
+
font-size: 11px;
|
44
|
+
font-weight: 500;
|
45
|
+
cursor: default;
|
46
|
+
border: 1px solid transparent;
|
47
|
+
white-space: nowrap;
|
48
|
+
}
|
49
|
+
|
50
|
+
.st-status-badge.success {
|
51
|
+
color: var(--badge-success-text);
|
52
|
+
background-color: var(--badge-success-bg);
|
53
|
+
border-color: var(--badge-success-border);
|
54
|
+
}
|
55
|
+
|
56
|
+
.st-status-badge.failure {
|
57
|
+
color: var(--badge-failure-text);
|
58
|
+
background-color: var(--badge-failure-bg);
|
59
|
+
border-color: var(--badge-failure-border);
|
60
|
+
}
|
61
|
+
|
62
|
+
.st-status-badge.running {
|
63
|
+
color: var(--badge-running-text);
|
64
|
+
background-color: var(--badge-running-bg);
|
65
|
+
border-color: var(--badge-running-border);
|
66
|
+
}
|
67
|
+
|
68
|
+
.st-status-badge.pending {
|
69
|
+
color: var(--badge-pending-text);
|
70
|
+
background-color: var(--badge-pending-bg);
|
71
|
+
border-color: var(--badge-pending-border);
|
72
|
+
}
|
@@ -5,6 +5,8 @@
|
|
5
5
|
--st-table-odd-bg: #fcfcfc;
|
6
6
|
--st-table-even-bg: transparent;
|
7
7
|
--st-table-header-bg: transparent;
|
8
|
+
--st-table-code-bg: #fcfcfc;
|
9
|
+
--st-table-code-text: #6c6869;
|
8
10
|
}
|
9
11
|
|
10
12
|
@media (prefers-color-scheme: dark) {
|
@@ -18,6 +20,10 @@
|
|
18
20
|
}
|
19
21
|
}
|
20
22
|
|
23
|
+
.st-table-container {
|
24
|
+
overflow: auto;
|
25
|
+
}
|
26
|
+
|
21
27
|
.st-table {
|
22
28
|
background-color: var(--st-table-bg);
|
23
29
|
color: var(--st-table-text);
|
@@ -29,8 +35,13 @@
|
|
29
35
|
.st-table thead > tr > th,
|
30
36
|
.st-table tbody > tr > th,
|
31
37
|
.st-table tbody > tr > td {
|
38
|
+
max-width: 300px;
|
39
|
+
overflow: scroll;
|
40
|
+
display: table-cell;
|
41
|
+
vertical-align: middle;
|
32
42
|
border: 1px solid var(--st-table-border);
|
33
43
|
padding: 7px 10px;
|
44
|
+
white-space: nowrap;
|
34
45
|
}
|
35
46
|
|
36
47
|
.st-table tbody tr:nth-child(odd) {
|
@@ -46,3 +57,11 @@
|
|
46
57
|
background-color: var(--st-table-header-bg);
|
47
58
|
}
|
48
59
|
|
60
|
+
.st-table code {
|
61
|
+
background-color: var(--st-table-code-bg);
|
62
|
+
color: var(--st-table-code-text);
|
63
|
+
box-shadow: 0 0 0 1px var(--st-table-border);
|
64
|
+
border-radius: 2px;
|
65
|
+
padding: 0.2em 0.4em;
|
66
|
+
border-radius: 0.2em;
|
67
|
+
}
|
@@ -0,0 +1,42 @@
|
|
1
|
+
:root {
|
2
|
+
--st-tooltip-bg: #333;
|
3
|
+
--st-tooltip-text: #fff;
|
4
|
+
--st-tooltip-shadow: rgba(0, 0, 0, 0.2);
|
5
|
+
--st-tooltip-arrow: #333;
|
6
|
+
}
|
7
|
+
|
8
|
+
@media (prefers-color-scheme: dark) {
|
9
|
+
:root {
|
10
|
+
--st-tooltip-bg: #eee;
|
11
|
+
--st-tooltip-text: #111;
|
12
|
+
--st-tooltip-shadow: rgba(255, 255, 255, 0.1);
|
13
|
+
--st-tooltip-arrow: #eee;
|
14
|
+
}
|
15
|
+
}
|
16
|
+
|
17
|
+
.st-tooltip {
|
18
|
+
position: absolute;
|
19
|
+
background-color: var(--st-tooltip-bg);
|
20
|
+
color: var(--st-tooltip-text);
|
21
|
+
padding: 6px 10px;
|
22
|
+
border-radius: 4px;
|
23
|
+
font-size: 11px;
|
24
|
+
white-space: pre-wrap;
|
25
|
+
max-width: 300px;
|
26
|
+
z-index: 10000;
|
27
|
+
box-shadow: 0 0 10px var(--st-tooltip-shadow);
|
28
|
+
opacity: 0;
|
29
|
+
pointer-events: none;
|
30
|
+
transition: opacity 0.2s ease;
|
31
|
+
}
|
32
|
+
|
33
|
+
.st-tooltip::after {
|
34
|
+
content: "";
|
35
|
+
position: absolute;
|
36
|
+
top: 100%;
|
37
|
+
left: 50%;
|
38
|
+
transform: translateX(-50%);
|
39
|
+
border-width: 5px;
|
40
|
+
border-style: solid;
|
41
|
+
border-color: var(--st-tooltip-arrow) transparent transparent transparent;
|
42
|
+
}
|
@@ -0,0 +1,51 @@
|
|
1
|
+
class TooltipsManager {
|
2
|
+
constructor(selector = '[data-tooltip]') {
|
3
|
+
this.selector = selector;
|
4
|
+
this.tooltip = null;
|
5
|
+
}
|
6
|
+
|
7
|
+
init() {
|
8
|
+
this.#createTooltipElement();
|
9
|
+
|
10
|
+
document.querySelectorAll(this.selector).forEach(element => {
|
11
|
+
element.addEventListener('mouseenter', this.#showTooltip.bind(this));
|
12
|
+
element.addEventListener('mouseleave', this.#hideTooltip.bind(this));
|
13
|
+
});
|
14
|
+
}
|
15
|
+
|
16
|
+
#createTooltipElement() {
|
17
|
+
this.tooltip = document.createElement('div');
|
18
|
+
this.tooltip.className = 'st-tooltip';
|
19
|
+
document.body.appendChild(this.tooltip);
|
20
|
+
}
|
21
|
+
|
22
|
+
#showTooltip(event) {
|
23
|
+
const target = event.currentTarget;
|
24
|
+
const text = target.getAttribute('data-tooltip');
|
25
|
+
|
26
|
+
if (!text) return;
|
27
|
+
|
28
|
+
this.tooltip.textContent = text;
|
29
|
+
this.tooltip.style.top = '0px';
|
30
|
+
this.tooltip.style.left = '-9999px';
|
31
|
+
this.tooltip.style.opacity = '1';
|
32
|
+
|
33
|
+
requestAnimationFrame(() => {
|
34
|
+
const rect = target.getBoundingClientRect();
|
35
|
+
const tooltipRect = this.tooltip.getBoundingClientRect();
|
36
|
+
const top = rect.top + window.scrollY - tooltipRect.height - 8;
|
37
|
+
const left = rect.left + window.scrollX + rect.width / 2 - tooltipRect.width / 2;
|
38
|
+
|
39
|
+
this.tooltip.style.top = `${top}px`;
|
40
|
+
this.tooltip.style.left = `${left}px`;
|
41
|
+
});
|
42
|
+
}
|
43
|
+
|
44
|
+
#hideTooltip() {
|
45
|
+
this.tooltip.style.opacity = '0';
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
document.addEventListener('DOMContentLoaded', () => {
|
50
|
+
new TooltipsManager().init();
|
51
|
+
});
|
data/web/locales/en.yml
CHANGED
@@ -6,6 +6,7 @@ en:
|
|
6
6
|
filter: "Filter"
|
7
7
|
no_tasks: "No tasks found"
|
8
8
|
name: "Name"
|
9
|
+
description: "Description"
|
9
10
|
last_enqueued: "Last enqueued"
|
10
11
|
history: "History"
|
11
12
|
no_history: "No history"
|
@@ -13,9 +14,15 @@ en:
|
|
13
14
|
args: "Arguments"
|
14
15
|
enqueued: "Enqueued"
|
15
16
|
executed: "Executed"
|
17
|
+
duration: "Duration"
|
16
18
|
task: "Task"
|
17
19
|
desc: "Description"
|
18
20
|
strategy: "Strategy"
|
19
21
|
run_task: "Run task"
|
20
22
|
enqueue: "Enqueue"
|
21
23
|
env_confirmation: "Please enter '%{current_env}' to confirm."
|
24
|
+
pending: "Pending"
|
25
|
+
running: "Running"
|
26
|
+
success: "Success"
|
27
|
+
failure: "Failure"
|
28
|
+
task_time: "%m/%d/%y %H:%M:%S"
|
data/web/locales/fr.yml
CHANGED
@@ -6,16 +6,23 @@ fr:
|
|
6
6
|
filter: "Filtrer"
|
7
7
|
no_tasks: "Aucune tâche trouvée"
|
8
8
|
name: "Nom"
|
9
|
+
description: "Description"
|
9
10
|
last_enqueued: "Dernière mise en file d'attente"
|
10
11
|
history: "Historique"
|
11
12
|
no_history: "Aucun historique"
|
12
13
|
jid: "JID"
|
13
14
|
args: "Arguments"
|
14
15
|
enqueued: "Mise en file d'attente"
|
15
|
-
executed: "
|
16
|
+
executed: "Exécutée"
|
17
|
+
duration: "Durée"
|
16
18
|
task: "Tâche"
|
17
19
|
desc: "Description"
|
18
20
|
strategy: "Stratégie"
|
19
21
|
run_task: "Exécuter la tâche"
|
20
22
|
enqueue: "Mettre en file d'attente"
|
21
23
|
env_confirmation: "Veuillez saisir '%{current_env}' pour confirmer."
|
24
|
+
pending: "En attente"
|
25
|
+
running: "En cours"
|
26
|
+
success: "Succès"
|
27
|
+
failure: "Echec"
|
28
|
+
task_time: "%d/%m/%y %H:%M:%S"
|
data/web/views/task.erb
CHANGED
@@ -1,7 +1,13 @@
|
|
1
|
-
<% if Sidekiq::Tasks::Web::
|
1
|
+
<% if Sidekiq::Tasks::Web::SIDEKIQ_GTE_8_0_0 %>
|
2
2
|
<% style_tag "tasks/css/ext.css" %>
|
3
|
+
<% elsif Sidekiq::Tasks::Web::SIDEKIQ_GTE_7_3_0 %>
|
4
|
+
<% add_to_head do %>
|
5
|
+
<%= style_tag "tasks/css/ext.css" %>
|
6
|
+
<% end %>
|
3
7
|
<% else %>
|
4
|
-
|
8
|
+
<% add_to_head do %>
|
9
|
+
<link href="<%= root_path %>tasks/css/ext.css" media="screen" rel="stylesheet" type="text/css"/>
|
10
|
+
<% end %>
|
5
11
|
<% end %>
|
6
12
|
|
7
13
|
<header class="st-header">
|
@@ -35,30 +41,51 @@
|
|
35
41
|
</div>
|
36
42
|
</header>
|
37
43
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
<
|
43
|
-
|
44
|
-
<
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
<
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
44
|
+
<div class="st-table-container">
|
45
|
+
<% if task.history.empty? %>
|
46
|
+
<p><%= t("no_history") %></p>
|
47
|
+
<% else %>
|
48
|
+
<table class="st-table">
|
49
|
+
<thead>
|
50
|
+
<tr>
|
51
|
+
<th><%= t("jid") %></th>
|
52
|
+
<th><%= t("args") %></th>
|
53
|
+
<th><%= t("enqueued") %></th>
|
54
|
+
<th><%= t("executed") %></th>
|
55
|
+
<th><%= t("duration") %></th>
|
56
|
+
<th><%= t("status") %></th>
|
57
|
+
</tr>
|
58
|
+
</thead>
|
59
|
+
<tbody>
|
60
|
+
<% task.history.each do |jid_history| %>
|
61
|
+
<tr>
|
62
|
+
<td><%= jid_history["jid"] %></td>
|
63
|
+
<td>
|
64
|
+
<code><%= jid_history["args"] %></code>
|
65
|
+
</td>
|
66
|
+
<td>
|
67
|
+
<%= jid_history["enqueued_at"] ? jid_history["enqueued_at"].strftime(t("task_time")) : "-" %>
|
68
|
+
</td>
|
69
|
+
<td>
|
70
|
+
<%= jid_history["executed_at"] ? jid_history["executed_at"].strftime(t("task_time")) : "-" %>
|
71
|
+
</td>
|
72
|
+
<td>
|
73
|
+
<%= format_task_duration(jid_history["enqueued_at"], jid_history["finished_at"]) %>
|
74
|
+
</td>
|
75
|
+
<td>
|
76
|
+
<%= build_tag(
|
77
|
+
:span,
|
78
|
+
t(task_status(jid_history).to_s).capitalize,
|
79
|
+
class: "st-status-badge #{task_status(jid_history)}",
|
80
|
+
"data-tooltip": jid_history["error"],
|
81
|
+
) %>
|
82
|
+
</td>
|
83
|
+
</tr>
|
84
|
+
<% end %>
|
85
|
+
</tbody>
|
86
|
+
</table>
|
87
|
+
<% end %>
|
88
|
+
</div>
|
62
89
|
|
63
90
|
<header class="">
|
64
91
|
<div class="">
|
@@ -85,13 +112,15 @@
|
|
85
112
|
<input type="text" id="envConfirmationInput" class="st-input" name="env_confirmation" data-current-env="<%= current_env %>" required/>
|
86
113
|
</div>
|
87
114
|
|
88
|
-
<button type="submit" class="st-button" id="submitBtn" disabled>
|
115
|
+
<button type="submit" class="st-button" id="submitBtn" disabled>
|
89
116
|
<%= t("enqueue") %>
|
90
117
|
</button>
|
91
118
|
</form>
|
92
119
|
|
93
120
|
<% if Sidekiq::Tasks::Web::SIDEKIQ_GTE_7_3_0 %>
|
94
121
|
<%= script_tag "tasks/js/env_confirmation.js" %>
|
122
|
+
<%= script_tag "tasks/js/tooltips_manager.js" %>
|
95
123
|
<% else %>
|
96
124
|
<script type="text/javascript" src="<%= root_path %>tasks/js/env_confirmation.js"></script>
|
125
|
+
<script type="text/javascript" src="<%= root_path %>tasks/js/tooltips_manager.js"></script>
|
97
126
|
<% end %>
|
data/web/views/tasks.erb
CHANGED
@@ -1,7 +1,13 @@
|
|
1
|
-
<% if Sidekiq::Tasks::Web::
|
1
|
+
<% if Sidekiq::Tasks::Web::SIDEKIQ_GTE_8_0_0 %>
|
2
2
|
<% style_tag "tasks/css/ext.css" %>
|
3
|
+
<% elsif Sidekiq::Tasks::Web::SIDEKIQ_GTE_7_3_0 %>
|
4
|
+
<% add_to_head do %>
|
5
|
+
<%= style_tag "tasks/css/ext.css" %>
|
6
|
+
<% end %>
|
3
7
|
<% else %>
|
4
|
-
|
8
|
+
<% add_to_head do %>
|
9
|
+
<link href="<%= root_path %>tasks/css/ext.css" media="screen" rel="stylesheet" type="text/css"/>
|
10
|
+
<% end %>
|
5
11
|
<% end %>
|
6
12
|
|
7
13
|
<header class="st-header">
|
@@ -22,30 +28,34 @@
|
|
22
28
|
</form>
|
23
29
|
</header>
|
24
30
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
<
|
30
|
-
<
|
31
|
-
<th><%= t("name") %></th>
|
32
|
-
<th><%= t("last_enqueued") %></th>
|
33
|
-
</tr>
|
34
|
-
</thead>
|
35
|
-
<tbody>
|
36
|
-
<% @search.tasks.each do |task| %>
|
31
|
+
<div class="st-table-container">
|
32
|
+
<% if @search.tasks.empty? %>
|
33
|
+
<%= t("no_tasks") %>
|
34
|
+
<% else %>
|
35
|
+
<table class="st-table">
|
36
|
+
<thead>
|
37
37
|
<tr>
|
38
|
-
<
|
39
|
-
|
40
|
-
|
41
|
-
</a>
|
42
|
-
</td>
|
43
|
-
|
44
|
-
<td><%= task.last_enqueue_at ? relative_time(task.last_enqueue_at) : "-" %></td>
|
38
|
+
<th><%= t("name") %></th>
|
39
|
+
<th><%= t("description") %></th>
|
40
|
+
<th><%= t("last_enqueued") %></th>
|
45
41
|
</tr>
|
46
|
-
|
47
|
-
|
48
|
-
|
42
|
+
</thead>
|
43
|
+
<tbody>
|
44
|
+
<% @search.tasks.each do |task| %>
|
45
|
+
<tr>
|
46
|
+
<td>
|
47
|
+
<a href="<%= task_url(root_path, task) %>" class="st-text-primary">
|
48
|
+
<b><%= task.name %></b>
|
49
|
+
</a>
|
50
|
+
</td>
|
49
51
|
|
50
|
-
|
51
|
-
|
52
|
+
<td><%= task.desc %></td>
|
53
|
+
<td><%= task.last_enqueue_at ? relative_time(task.last_enqueue_at) : "-" %></td>
|
54
|
+
</tr>
|
55
|
+
<% end %>
|
56
|
+
</tbody>
|
57
|
+
</table>
|
58
|
+
|
59
|
+
<%= erb(read_view(:_pagination), locals: {search: @search}) %>
|
60
|
+
<% end %>
|
61
|
+
</div>
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sidekiq-tasks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Victor
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date: 2025-
|
10
|
+
date: 2025-05-11 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: rake
|
@@ -261,6 +260,7 @@ files:
|
|
261
260
|
- lib/sidekiq/tasks/web/extension.rb
|
262
261
|
- lib/sidekiq/tasks/web/helpers/application_helper.rb
|
263
262
|
- lib/sidekiq/tasks/web/helpers/pagination_helper.rb
|
263
|
+
- lib/sidekiq/tasks/web/helpers/tag_helper.rb
|
264
264
|
- lib/sidekiq/tasks/web/helpers/task_helper.rb
|
265
265
|
- lib/sidekiq/tasks/web/pagination.rb
|
266
266
|
- lib/sidekiq/tasks/web/params.rb
|
@@ -269,11 +269,14 @@ files:
|
|
269
269
|
- web/assets/tasks/css/components/buttons.css
|
270
270
|
- web/assets/tasks/css/components/forms.css
|
271
271
|
- web/assets/tasks/css/components/pagination.css
|
272
|
+
- web/assets/tasks/css/components/status_badges.css
|
272
273
|
- web/assets/tasks/css/components/tables.css
|
274
|
+
- web/assets/tasks/css/components/tooltips.css
|
273
275
|
- web/assets/tasks/css/ext.css
|
274
276
|
- web/assets/tasks/css/layouts/header.css
|
275
277
|
- web/assets/tasks/css/utilities.css
|
276
278
|
- web/assets/tasks/js/env_confirmation.js
|
279
|
+
- web/assets/tasks/js/tooltips_manager.js
|
277
280
|
- web/locales/en.yml
|
278
281
|
- web/locales/fr.yml
|
279
282
|
- web/views/_pagination.erb
|
@@ -286,7 +289,6 @@ metadata:
|
|
286
289
|
homepage_uri: https://github.com/victorauthiat/sidekiq-tasks
|
287
290
|
source_code_uri: https://github.com/victorauthiat/sidekiq-tasks/blob/master
|
288
291
|
changelog_uri: https://github.com/victorauthiat/sidekiq-tasks/blob/master/CHANGELOG.md
|
289
|
-
post_install_message:
|
290
292
|
rdoc_options: []
|
291
293
|
require_paths:
|
292
294
|
- lib
|
@@ -301,8 +303,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
301
303
|
- !ruby/object:Gem::Version
|
302
304
|
version: '0'
|
303
305
|
requirements: []
|
304
|
-
rubygems_version: 3.
|
305
|
-
signing_key:
|
306
|
+
rubygems_version: 3.6.2
|
306
307
|
specification_version: 4
|
307
308
|
summary: Sidekiq extension for launching tasks.
|
308
309
|
test_files: []
|