sidekiq 7.3.9 → 8.1.7

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.
Files changed (155) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +225 -0
  3. data/README.md +34 -14
  4. data/bin/kiq +17 -0
  5. data/bin/lint-herb +13 -0
  6. data/bin/sidekiqload +12 -10
  7. data/bin/webload +69 -0
  8. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +107 -58
  9. data/lib/generators/sidekiq/job_generator.rb +15 -3
  10. data/lib/generators/sidekiq/templates/job.rb.erb +1 -1
  11. data/lib/sidekiq/api.rb +310 -83
  12. data/lib/sidekiq/capsule.rb +10 -7
  13. data/lib/sidekiq/cli.rb +31 -24
  14. data/lib/sidekiq/client.rb +36 -18
  15. data/lib/sidekiq/component.rb +45 -3
  16. data/lib/sidekiq/config.rb +35 -30
  17. data/lib/sidekiq/embedded.rb +2 -1
  18. data/lib/sidekiq/fetch.rb +1 -0
  19. data/lib/sidekiq/iterable_job.rb +1 -0
  20. data/lib/sidekiq/job/iterable.rb +46 -18
  21. data/lib/sidekiq/job.rb +18 -3
  22. data/lib/sidekiq/job_logger.rb +8 -6
  23. data/lib/sidekiq/job_retry.rb +45 -18
  24. data/lib/sidekiq/job_util.rb +5 -1
  25. data/lib/sidekiq/launcher.rb +22 -12
  26. data/lib/sidekiq/loader.rb +57 -0
  27. data/lib/sidekiq/logger.rb +25 -69
  28. data/lib/sidekiq/manager.rb +2 -2
  29. data/lib/sidekiq/metrics/query.rb +77 -46
  30. data/lib/sidekiq/metrics/shared.rb +8 -5
  31. data/lib/sidekiq/metrics/tracking.rb +12 -7
  32. data/lib/sidekiq/middleware/current_attributes.rb +11 -19
  33. data/lib/sidekiq/middleware/i18n.rb +2 -0
  34. data/lib/sidekiq/monitor.rb +4 -8
  35. data/lib/sidekiq/paginator.rb +18 -2
  36. data/lib/sidekiq/processor.rb +26 -18
  37. data/lib/sidekiq/profiler.rb +73 -0
  38. data/lib/sidekiq/rails.rb +46 -67
  39. data/lib/sidekiq/redis_client_adapter.rb +6 -3
  40. data/lib/sidekiq/redis_connection.rb +16 -5
  41. data/lib/sidekiq/ring_buffer.rb +1 -0
  42. data/lib/sidekiq/scheduled.rb +6 -7
  43. data/lib/sidekiq/test_api.rb +331 -0
  44. data/lib/sidekiq/testing/inline.rb +2 -30
  45. data/lib/sidekiq/testing.rb +2 -333
  46. data/lib/sidekiq/transaction_aware_client.rb +13 -5
  47. data/lib/sidekiq/tui/controls.rb +53 -0
  48. data/lib/sidekiq/tui/filtering.rb +53 -0
  49. data/lib/sidekiq/tui/tabs/base_tab.rb +230 -0
  50. data/lib/sidekiq/tui/tabs/busy.rb +104 -0
  51. data/lib/sidekiq/tui/tabs/dead.rb +14 -0
  52. data/lib/sidekiq/tui/tabs/home.rb +115 -0
  53. data/lib/sidekiq/tui/tabs/metrics.rb +114 -0
  54. data/lib/sidekiq/tui/tabs/queues.rb +83 -0
  55. data/lib/sidekiq/tui/tabs/retries.rb +14 -0
  56. data/lib/sidekiq/tui/tabs/scheduled.rb +14 -0
  57. data/lib/sidekiq/tui/tabs/set_tab.rb +91 -0
  58. data/lib/sidekiq/tui/tabs.rb +15 -0
  59. data/lib/sidekiq/tui.rb +390 -0
  60. data/lib/sidekiq/version.rb +2 -2
  61. data/lib/sidekiq/web/action.rb +146 -83
  62. data/lib/sidekiq/web/application.rb +372 -328
  63. data/lib/sidekiq/web/config.rb +117 -0
  64. data/lib/sidekiq/web/helpers.rb +102 -44
  65. data/lib/sidekiq/web/router.rb +61 -77
  66. data/lib/sidekiq/web.rb +65 -151
  67. data/lib/sidekiq.rb +13 -1
  68. data/sidekiq.gemspec +9 -8
  69. data/web/assets/images/logo.png +0 -0
  70. data/web/assets/images/status.png +0 -0
  71. data/web/assets/javascripts/application.js +43 -27
  72. data/web/assets/javascripts/base-charts.js +30 -16
  73. data/web/assets/javascripts/chartjs-adapter-date-fns.min.js +7 -0
  74. data/web/assets/javascripts/dashboard.js +1 -1
  75. data/web/assets/javascripts/metrics.js +16 -34
  76. data/web/assets/stylesheets/style.css +776 -0
  77. data/web/locales/ar.yml +3 -1
  78. data/web/locales/cs.yml +2 -0
  79. data/web/locales/da.yml +2 -0
  80. data/web/locales/de.yml +2 -0
  81. data/web/locales/el.yml +2 -0
  82. data/web/locales/en.yml +8 -0
  83. data/web/locales/es.yml +25 -2
  84. data/web/locales/fa.yml +3 -1
  85. data/web/locales/fr.yml +3 -1
  86. data/web/locales/gd.yml +13 -1
  87. data/web/locales/he.yml +3 -1
  88. data/web/locales/hi.yml +36 -0
  89. data/web/locales/it.yml +9 -0
  90. data/web/locales/ja.yml +2 -0
  91. data/web/locales/ko.yml +2 -0
  92. data/web/locales/lt.yml +2 -0
  93. data/web/locales/nb.yml +2 -0
  94. data/web/locales/nl.yml +2 -0
  95. data/web/locales/pl.yml +2 -0
  96. data/web/locales/{pt-br.yml → pt-BR.yml} +3 -1
  97. data/web/locales/pt.yml +2 -0
  98. data/web/locales/ru.yml +2 -0
  99. data/web/locales/sv.yml +2 -0
  100. data/web/locales/ta.yml +83 -47
  101. data/web/locales/tr.yml +2 -0
  102. data/web/locales/uk.yml +7 -5
  103. data/web/locales/ur.yml +3 -1
  104. data/web/locales/vi.yml +2 -0
  105. data/web/locales/{zh-cn.yml → zh-CN.yml} +86 -73
  106. data/web/locales/{zh-tw.yml → zh-TW.yml} +3 -1
  107. data/web/views/_footer.html.erb +32 -0
  108. data/web/views/_job_info.html.erb +115 -0
  109. data/web/views/_metrics_period_select.html.erb +15 -0
  110. data/web/views/_nav.html.erb +45 -0
  111. data/web/views/_paging.html.erb +26 -0
  112. data/web/views/_poll_link.html.erb +4 -0
  113. data/web/views/{_summary.erb → _summary.html.erb} +16 -16
  114. data/web/views/busy.html.erb +155 -0
  115. data/web/views/dashboard.html.erb +104 -0
  116. data/web/views/dead.html.erb +38 -0
  117. data/web/views/filtering.html.erb +6 -0
  118. data/web/views/layout.html.erb +26 -0
  119. data/web/views/metrics.html.erb +85 -0
  120. data/web/views/metrics_for_job.html.erb +58 -0
  121. data/web/views/morgue.html.erb +69 -0
  122. data/web/views/profiles.html.erb +43 -0
  123. data/web/views/queue.html.erb +57 -0
  124. data/web/views/queues.html.erb +46 -0
  125. data/web/views/retries.html.erb +77 -0
  126. data/web/views/retry.html.erb +39 -0
  127. data/web/views/scheduled.html.erb +64 -0
  128. data/web/views/{scheduled_job_info.erb → scheduled_job_info.html.erb} +3 -3
  129. metadata +65 -47
  130. data/lib/sidekiq/web/csrf_protection.rb +0 -183
  131. data/web/assets/stylesheets/application-dark.css +0 -147
  132. data/web/assets/stylesheets/application-rtl.css +0 -163
  133. data/web/assets/stylesheets/application.css +0 -759
  134. data/web/assets/stylesheets/bootstrap-rtl.min.css +0 -9
  135. data/web/assets/stylesheets/bootstrap.css +0 -5
  136. data/web/views/_footer.erb +0 -34
  137. data/web/views/_job_info.erb +0 -105
  138. data/web/views/_metrics_period_select.erb +0 -12
  139. data/web/views/_nav.erb +0 -52
  140. data/web/views/_paging.erb +0 -25
  141. data/web/views/_poll_link.erb +0 -4
  142. data/web/views/_status.erb +0 -4
  143. data/web/views/busy.erb +0 -148
  144. data/web/views/dashboard.erb +0 -108
  145. data/web/views/dead.erb +0 -34
  146. data/web/views/filtering.erb +0 -6
  147. data/web/views/layout.erb +0 -42
  148. data/web/views/metrics.erb +0 -90
  149. data/web/views/metrics_for_job.erb +0 -59
  150. data/web/views/morgue.erb +0 -74
  151. data/web/views/queue.erb +0 -55
  152. data/web/views/queues.erb +0 -44
  153. data/web/views/retries.erb +0 -79
  154. data/web/views/retry.erb +0 -34
  155. data/web/views/scheduled.erb +0 -56
data/lib/sidekiq/web.rb CHANGED
@@ -2,27 +2,18 @@
2
2
 
3
3
  require "erb"
4
4
  require "securerandom"
5
-
6
- require "sidekiq"
7
- require "sidekiq/api"
8
- require "sidekiq/paginator"
9
- require "sidekiq/web/helpers"
10
-
11
- require "sidekiq/web/router"
12
- require "sidekiq/web/action"
13
- require "sidekiq/web/application"
14
- require "sidekiq/web/csrf_protection"
15
-
16
- require "rack/content_length"
17
5
  require "rack/builder"
18
6
  require "rack/static"
7
+ require "sidekiq"
8
+ require "sidekiq/api"
9
+ require "sidekiq/web/config"
19
10
 
20
11
  module Sidekiq
21
12
  class Web
22
13
  ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../web")
23
14
  VIEWS = "#{ROOT}/views"
24
15
  LOCALES = ["#{ROOT}/locales"]
25
- LAYOUT = "#{VIEWS}/layout.erb"
16
+ LAYOUT = "#{VIEWS}/layout.html.erb"
26
17
  ASSETS = "#{ROOT}/assets"
27
18
 
28
19
  DEFAULT_TABS = {
@@ -32,195 +23,118 @@ module Sidekiq
32
23
  "Retries" => "retries",
33
24
  "Scheduled" => "scheduled",
34
25
  "Dead" => "morgue",
35
- "Metrics" => "metrics"
26
+ "Metrics" => "metrics",
27
+ "Profiles" => "profiles"
36
28
  }
37
29
 
38
- if Gem::Version.new(Rack::RELEASE) < Gem::Version.new("3")
39
- CONTENT_LANGUAGE = "Content-Language"
40
- CONTENT_SECURITY_POLICY = "Content-Security-Policy"
41
- LOCATION = "Location"
42
- X_CASCADE = "X-Cascade"
43
- X_CONTENT_TYPE_OPTIONS = "X-Content-Type-Options"
44
- else
45
- CONTENT_LANGUAGE = "content-language"
46
- CONTENT_SECURITY_POLICY = "content-security-policy"
47
- LOCATION = "location"
48
- X_CASCADE = "x-cascade"
49
- X_CONTENT_TYPE_OPTIONS = "x-content-type-options"
50
- end
30
+ @@config = Sidekiq::Web::Config.new
51
31
 
52
32
  class << self
53
- # Forward compatibility with 8.0
54
33
  def configure
55
- yield self
34
+ if block_given?
35
+ yield @@config
36
+ else
37
+ @@config
38
+ end
56
39
  end
57
40
 
58
- def settings
59
- self
41
+ def app_url=(url)
42
+ @@config.app_url = url
60
43
  end
61
44
 
62
- def default_tabs
63
- DEFAULT_TABS
45
+ def assets_path=(path)
46
+ @@config.assets_path = path
64
47
  end
65
48
 
66
- def custom_tabs
67
- @custom_tabs ||= {}
68
- end
69
- alias_method :tabs, :custom_tabs
49
+ def assets_path = @@config.assets_path
70
50
 
71
- def custom_job_info_rows
72
- @custom_job_info_rows ||= []
73
- end
74
-
75
- def locales
76
- @locales ||= LOCALES
77
- end
51
+ def tabs = @@config.tabs
78
52
 
79
- def views
80
- @views ||= VIEWS
81
- end
53
+ def locales = @@config.locales
82
54
 
83
- def enable(*opts)
84
- opts.each { |key| set(key, true) }
85
- end
55
+ def views = @@config.views
86
56
 
87
- def disable(*opts)
88
- opts.each { |key| set(key, false) }
89
- end
57
+ def custom_job_info_rows = @@config.custom_job_info_rows
90
58
 
91
- def middlewares
92
- @middlewares ||= []
59
+ def redis_pool
60
+ @pool || Sidekiq.default_configuration.redis_pool
93
61
  end
94
62
 
95
- def use(*args, &block)
96
- middlewares << [args, block]
63
+ def redis_pool=(pool)
64
+ @pool = pool
97
65
  end
98
66
 
99
- def set(attribute, value)
100
- send(:"#{attribute}=", value)
101
- end
67
+ def middlewares = @@config.middlewares
102
68
 
103
- attr_accessor :app_url, :redis_pool
104
- attr_writer :locales, :views
105
- end
69
+ def use(*args, &block) = @@config.middlewares << [args, block]
106
70
 
107
- def self.inherited(child)
108
- child.app_url = app_url
109
- child.redis_pool = redis_pool
110
- end
111
-
112
- def settings
113
- self.class.settings
71
+ def register(*args, **kw, &block)
72
+ Sidekiq.logger.warn { "`Sidekiq::Web.register` is deprecated, use `Sidekiq::Web.configure {|cfg| cfg.register(...) }`" }
73
+ @@config.register(*args, **kw, &block)
74
+ end
114
75
  end
115
76
 
116
- def middlewares
117
- @middlewares ||= self.class.middlewares
77
+ # Allow user to say
78
+ # run Sidekiq::Web
79
+ # rather than:
80
+ # run Sidekiq::Web.new
81
+ def self.call(env)
82
+ @inst ||= new
83
+ @inst.call(env)
118
84
  end
119
85
 
120
- def use(*args, &block)
121
- middlewares << [args, block]
86
+ # testing, internal use only
87
+ def self.reset!
88
+ @@config.reset!
89
+ @inst = nil
122
90
  end
123
91
 
124
92
  def call(env)
125
- env[:csp_nonce] = SecureRandom.base64(16)
126
- app.call(env)
127
- end
128
-
129
- def self.call(env)
130
- @app ||= new
131
- @app.call(env)
93
+ env[:web_config] = Sidekiq::Web.configure
94
+ env[:csp_nonce] = SecureRandom.hex(8)
95
+ env[:redis_pool] = self.class.redis_pool
96
+ safe_request?(env) ? app.call(env) : deny(env)
132
97
  end
133
98
 
134
99
  def app
135
- @app ||= build
100
+ @app ||= build(@@config)
136
101
  end
137
102
 
138
- def enable(*opts)
139
- opts.each { |key| set(key, true) }
103
+ def safe_methods?(env)
104
+ %w[GET HEAD OPTIONS TRACE].include? env["REQUEST_METHOD"]
140
105
  end
141
106
 
142
- def disable(*opts)
143
- opts.each { |key| set(key, false) }
107
+ def safe_request?(env)
108
+ return true if safe_methods?(env)
109
+ env["HTTP_SEC_FETCH_SITE"] == "same-origin"
144
110
  end
145
111
 
146
- def set(attribute, value)
147
- send(:"#{attribute}=", value)
148
- end
149
-
150
- # Register a class as a Sidekiq Web UI extension. The class should
151
- # provide one or more tabs which map to an index route. Options:
152
- #
153
- # @param extension [Class] Class which contains the HTTP actions, required
154
- # @param name [String] the name of the extension, used to namespace assets
155
- # @param tab [String | Array] labels(s) of the UI tabs
156
- # @param index [String | Array] index route(s) for each tab
157
- # @param root_dir [String] directory location to find assets, locales and views, typically `web/` within the gemfile
158
- # @param asset_paths [Array] one or more directories under {root}/assets/{name} to be publicly served, e.g. ["js", "css", "img"]
159
- # @param cache_for [Integer] amount of time to cache assets, default one day
160
- #
161
- # TODO name, tab and index will be mandatory in 8.0
162
- #
163
- # Web extensions will have a root `web/` directory with `locales/`, `assets/`
164
- # and `views/` subdirectories.
165
- def self.register(extension, name: nil, tab: nil, index: nil, root_dir: nil, cache_for: 86400, asset_paths: nil)
166
- tab = Array(tab)
167
- index = Array(index)
168
- tab.zip(index).each do |tab, index|
169
- tabs[tab] = index
170
- end
171
- if root_dir
172
- locdir = File.join(root_dir, "locales")
173
- locales << locdir if File.directory?(locdir)
174
-
175
- if asset_paths && name
176
- # if you have {root}/assets/{name}/js/scripts.js
177
- # and {root}/assets/{name}/css/styles.css
178
- # you would pass in:
179
- # asset_paths: ["js", "css"]
180
- # See script_tag and style_tag in web/helpers.rb
181
- assdir = File.join(root_dir, "assets")
182
- assurls = Array(asset_paths).map { |x| "/#{name}/#{x}" }
183
- assetprops = {
184
- urls: assurls,
185
- root: assdir,
186
- cascade: true
187
- }
188
- assetprops[:header_rules] = [[:all, {Rack::CACHE_CONTROL => "private, max-age=#{cache_for.to_i}"}]] if cache_for
189
- middlewares << [[Rack::Static, assetprops], nil]
190
- end
191
- end
192
-
193
- yield self if block_given?
194
- extension.registered(WebApplication)
112
+ def deny(env)
113
+ Sidekiq.logger.warn "attack prevented by #{self.class}"
114
+ [403, {Rack::CONTENT_TYPE => "text/plain"}, ["Forbidden"]]
195
115
  end
196
116
 
197
117
  private
198
118
 
199
- def build
200
- klass = self.class
201
- m = middlewares
119
+ def build(cfg)
120
+ cfg.freeze
121
+ m = cfg.middlewares
202
122
 
203
123
  rules = []
204
- rules = [[:all, {Rack::CACHE_CONTROL => "private, max-age=86400"}]] unless ENV["SIDEKIQ_WEB_TESTING"]
124
+ rules = [[:all, {"cache-control" => "private, max-age=86400"}]] unless ENV["SIDEKIQ_WEB_TESTING"]
205
125
 
206
126
  ::Rack::Builder.new do
207
127
  use Rack::Static, urls: ["/stylesheets", "/images", "/javascripts"],
208
- root: ASSETS,
128
+ root: cfg.assets_path,
209
129
  cascade: true,
210
130
  header_rules: rules
211
131
  m.each { |middleware, block| use(*middleware, &block) }
212
- use Sidekiq::Web::CsrfProtection unless $TESTING
213
- run WebApplication.new(klass)
132
+ run Sidekiq::Web::Application.new(self.class)
214
133
  end
215
134
  end
216
135
  end
217
-
218
- Sidekiq::WebApplication.helpers WebHelpers
219
- Sidekiq::WebApplication.helpers Sidekiq::Paginator
220
-
221
- Sidekiq::WebAction.class_eval <<-RUBY, Web::LAYOUT, -1 # standard:disable Style/EvalWithLocation
222
- def _render
223
- #{ERB.new(File.read(Web::LAYOUT)).src}
224
- end
225
- RUBY
226
136
  end
137
+
138
+ require "sidekiq/web/router"
139
+ require "sidekiq/web/action"
140
+ require "sidekiq/web/application"
data/lib/sidekiq.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "sidekiq/version"
4
- fail "Sidekiq #{Sidekiq::VERSION} does not support Ruby versions below 2.7.0." if RUBY_PLATFORM != "java" && Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.7.0")
4
+ fail "Sidekiq #{Sidekiq::VERSION} does not support Ruby versions below 3.2.0." if RUBY_PLATFORM != "java" && Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.2.0")
5
5
 
6
6
  begin
7
7
  require "sidekiq-ent/version"
@@ -29,6 +29,7 @@ end
29
29
 
30
30
  require "sidekiq/config"
31
31
  require "sidekiq/logger"
32
+ require "sidekiq/loader"
32
33
  require "sidekiq/client"
33
34
  require "sidekiq/transaction_aware_client"
34
35
  require "sidekiq/job"
@@ -46,6 +47,13 @@ module Sidekiq
46
47
  puts "Take a deep breath and count to ten..."
47
48
  end
48
49
 
50
+ def self.testing!(mode = :fake, &block)
51
+ raise "Unknown testing mode: #{mode}" unless %i[fake disable inline].include?(mode)
52
+
53
+ require "sidekiq/test_api"
54
+ Sidekiq::Testing.__set_test_mode(mode, &block)
55
+ end
56
+
49
57
  def self.server?
50
58
  defined?(Sidekiq::CLI)
51
59
  end
@@ -94,6 +102,10 @@ module Sidekiq
94
102
  default_configuration.logger
95
103
  end
96
104
 
105
+ def self.loader
106
+ @loader ||= Loader.new
107
+ end
108
+
97
109
  def self.configure_server(&block)
98
110
  (@config_blocks ||= []) << block
99
111
  yield default_configuration if server?
data/sidekiq.gemspec CHANGED
@@ -8,11 +8,11 @@ Gem::Specification.new do |gem|
8
8
  gem.homepage = "https://sidekiq.org"
9
9
  gem.license = "LGPL-3.0"
10
10
 
11
- gem.executables = ["sidekiq", "sidekiqmon"]
11
+ gem.executables = ["sidekiq", "sidekiqmon", "kiq"]
12
12
  gem.files = %w[sidekiq.gemspec README.md Changes.md LICENSE.txt] + `git ls-files | grep -E '^(bin|lib|web)'`.split("\n")
13
13
  gem.name = "sidekiq"
14
14
  gem.version = Sidekiq::VERSION
15
- gem.required_ruby_version = ">= 2.7.0"
15
+ gem.required_ruby_version = ">= 3.2.0"
16
16
 
17
17
  gem.metadata = {
18
18
  "homepage_uri" => "https://sidekiq.org",
@@ -20,12 +20,13 @@ Gem::Specification.new do |gem|
20
20
  "documentation_uri" => "https://github.com/sidekiq/sidekiq/wiki",
21
21
  "changelog_uri" => "https://github.com/sidekiq/sidekiq/blob/main/Changes.md",
22
22
  "source_code_uri" => "https://github.com/sidekiq/sidekiq",
23
- "rubygems_mfa_required" => "true"
23
+ "rubygems_mfa_required" => "true",
24
+ "mailing_list_uri" => "https://www.rubyforum.org/tag/sidekiq"
24
25
  }
25
26
 
26
- gem.add_dependency "redis-client", ">= 0.22.2"
27
- gem.add_dependency "connection_pool", ">= 2.3.0"
28
- gem.add_dependency "rack", ">= 2.2.4"
29
- gem.add_dependency "logger"
30
- gem.add_dependency "base64"
27
+ gem.add_dependency "redis-client", ">= 0.29.0"
28
+ gem.add_dependency "connection_pool", ">= 3.0.0"
29
+ gem.add_dependency "rack", ">= 3.2.0"
30
+ gem.add_dependency "json", ">= 2.16.0"
31
+ gem.add_dependency "logger", ">= 1.7.0"
31
32
  end
File without changes
File without changes
@@ -18,20 +18,12 @@ function addListeners() {
18
18
  })
19
19
  });
20
20
 
21
- document.querySelectorAll("input[data-confirm]").forEach(node => {
22
- node.addEventListener("click", event => {
23
- if (!window.confirm(node.getAttribute("data-confirm"))) {
24
- event.preventDefault();
25
- event.stopPropagation();
26
- }
27
- })
28
- })
29
-
30
21
  document.querySelectorAll("[data-toggle]").forEach(node => {
31
22
  node.addEventListener("click", addDataToggleListeners)
32
23
  })
33
24
 
34
- addShiftClickListeners()
25
+ initializeBulkToggle();
26
+ addShiftClickListeners();
35
27
  updateFuzzyTimes();
36
28
  updateNumbers();
37
29
  updateProgressBars();
@@ -67,13 +59,24 @@ function addPollingListeners(_event) {
67
59
 
68
60
  function addDataToggleListeners(event) {
69
61
  var source = event.target || event.srcElement;
70
- var targName = source.getAttribute("data-toggle");
62
+ var targName = source.dataset.toggle;
71
63
  var full = document.getElementById(targName);
72
- if (full.style.display == "block") {
73
- full.style.display = 'none';
74
- } else {
75
- full.style.display = 'block';
76
- }
64
+ full.classList.toggle("is-open");
65
+ }
66
+
67
+ function toggleBulkButtons() {
68
+ const checkboxes = document.querySelectorAll('.select-item-checkbox, .check-all-items');
69
+ const anyChecked = Array.from(checkboxes).some(cb => cb.checked);
70
+ const buttons = document.querySelectorAll('.bulk-action-buttons');
71
+ buttons.forEach(btn => {
72
+ btn.style.display = anyChecked ? 'none' : 'block';
73
+ });
74
+ }
75
+
76
+ function initializeBulkToggle(){
77
+ document.querySelectorAll('.check-all-items, .select-item-checkbox').forEach(cb => {
78
+ cb.addEventListener('change', toggleBulkButtons);
79
+ });
77
80
  }
78
81
 
79
82
  function addShiftClickListeners() {
@@ -94,7 +97,7 @@ function addShiftClickListeners() {
94
97
  }
95
98
 
96
99
  function updateFuzzyTimes() {
97
- var locale = document.body.getAttribute("data-locale");
100
+ var locale = document.body.dataset.locale;
98
101
  var parts = locale.split('-');
99
102
  if (typeof parts[1] !== 'undefined') {
100
103
  parts[1] = parts[1].toUpperCase();
@@ -109,7 +112,7 @@ function updateFuzzyTimes() {
109
112
  function updateNumbers() {
110
113
  document.querySelectorAll("[data-nwp]").forEach(node => {
111
114
  let number = parseFloat(node.textContent);
112
- let precision = parseInt(node.dataset["nwp"] || 0);
115
+ let precision = parseInt(node.dataset.nwp || 0);
113
116
  if (typeof number === "number") {
114
117
  let formatted = number.toLocaleString(undefined, {
115
118
  minimumFractionDigits: precision,
@@ -130,11 +133,11 @@ function setLivePollFromUrl() {
130
133
 
131
134
  function updateLivePollButton() {
132
135
  if (localStorage.sidekiqLivePoll == "enabled") {
133
- document.querySelectorAll('.live-poll-stop').forEach(box => { box.style.display = "inline-block" })
134
- document.querySelectorAll('.live-poll-start').forEach(box => { box.style.display = "none" })
136
+ document.querySelectorAll('.live-poll-stop').forEach(box => { box.classList.add("active") })
137
+ document.querySelectorAll('.live-poll-start').forEach(box => { box.classList.remove("active") })
135
138
  } else {
136
- document.querySelectorAll('.live-poll-start').forEach(box => { box.style.display = "inline-block" })
137
- document.querySelectorAll('.live-poll-stop').forEach(box => { box.style.display = "none" })
139
+ document.querySelectorAll('.live-poll-start').forEach(box => { box.classList.add("active") })
140
+ document.querySelectorAll('.live-poll-stop').forEach(box => { box.classList.remove("active") })
138
141
  }
139
142
  }
140
143
 
@@ -151,7 +154,7 @@ function livePollCallback() {
151
154
 
152
155
  function checkResponse(resp) {
153
156
  if (!resp.ok) {
154
- throw response.error();
157
+ throw resp.error();
155
158
  }
156
159
  return resp
157
160
  }
@@ -169,9 +172,6 @@ function replacePage(text) {
169
172
  var page = doc.querySelector('#page')
170
173
  document.querySelector("#page").replaceWith(page)
171
174
 
172
- var header_status = doc.querySelector('.status')
173
- document.querySelector('.status').replaceWith(header_status)
174
-
175
175
  addListeners();
176
176
  }
177
177
 
@@ -185,4 +185,20 @@ function updateLocale(event) {
185
185
 
186
186
  function updateProgressBars() {
187
187
  document.querySelectorAll('.progress-bar').forEach(bar => { bar.style.width = bar.dataset.width + "%"})
188
- }
188
+ }
189
+
190
+ function handleConfirmDialog (event) {
191
+ const target = event.target
192
+
193
+ if (target.localName !== "input" && target.localName !== "button" ) { return }
194
+ const confirmMessage = target.dataset.confirm
195
+
196
+ if (confirmMessage === undefined) { return }
197
+
198
+ if (!window.confirm(confirmMessage)) {
199
+ event.preventDefault()
200
+ event.stopPropagation()
201
+ }
202
+ }
203
+
204
+ document.addEventListener("click", handleConfirmDialog)
@@ -1,27 +1,35 @@
1
1
  if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
2
2
  Chart.defaults.borderColor = "#333";
3
3
  Chart.defaults.color = "#aaa";
4
+ // Chart.defaults.borderColor = "oklch(22% 0.01 256)";
5
+ // Chart.defaults.color = "oklch(65% 0.01 256)";
4
6
  }
5
7
 
6
8
  class Colors {
7
9
  constructor() {
8
10
  this.assignments = {};
9
- this.success = "#006f68";
10
- this.failure = "#af0014";
11
- this.fallback = "#999";
12
- this.primary = "#537bc4";
11
+ if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
12
+ this.light = "65%";
13
+ this.chroma = "0.15";
14
+ } else {
15
+ this.light = "48%";
16
+ this.chroma = "0.2";
17
+ }
18
+ this.success = "oklch(" + this.light + " " + this.chroma + " 179)";
19
+ this.failure = "oklch(" + this.light + " " + this.chroma + " 29)";
20
+ this.fallback = "oklch(" + this.light + " 0.02 269)";
21
+ this.primary = "oklch(" + this.light + " " + this.chroma + " 269)";
13
22
  this.available = [
14
- // Colors taken from https://www.chartjs.org/docs/latest/samples/utils.html
15
- "#537bc4",
16
- "#4dc9f6",
17
- "#f67019",
18
- "#f53794",
19
- "#acc236",
20
- "#166a8f",
21
- "#00a950",
22
- "#58595b",
23
- "#8549ba",
24
- "#991b1b",
23
+ "oklch(" + this.light + " " + this.chroma + " 256)",
24
+ "oklch(" + this.light + " " + this.chroma + " 196)",
25
+ "oklch(" + this.light + " " + this.chroma + " 46)",
26
+ "oklch(" + this.light + " " + this.chroma + " 316)",
27
+ "oklch(" + this.light + " " + this.chroma + " 106)",
28
+ "oklch(" + this.light + " " + this.chroma + " 226)",
29
+ "oklch(" + this.light + " " + this.chroma + " 136)",
30
+ "oklch(" + this.light + " 0.02 269)",
31
+ "oklch(" + this.light + " " + this.chroma + " 286)",
32
+ "oklch(" + this.light + " " + this.chroma + " 16)",
25
33
  ];
26
34
  }
27
35
 
@@ -90,12 +98,18 @@ class BaseChart {
90
98
  };
91
99
 
92
100
  if (this.options.marks) {
101
+ if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
102
+ this.Borderlight = "30%";
103
+ } else {
104
+ this.Borderlight = "65%";
105
+ }
106
+
93
107
  this.options.marks.forEach(([bucket, label], i) => {
94
108
  chartOptions.plugins.annotation.annotations[`deploy-${i}`] = {
95
109
  type: "line",
96
110
  xMin: bucket,
97
111
  xMax: bucket,
98
- borderColor: "rgba(220, 38, 38, 0.4)",
112
+ borderColor: "oklch(" + this.Borderlight + " 0.01 256)",
99
113
  borderWidth: 2,
100
114
  };
101
115
  });