sidekiq 7.3.10 → 8.0.10
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/Changes.md +135 -4
- data/README.md +16 -13
- data/bin/sidekiqload +10 -10
- data/bin/webload +69 -0
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +104 -58
- data/lib/generators/sidekiq/templates/job.rb.erb +1 -1
- data/lib/sidekiq/api.rb +152 -44
- data/lib/sidekiq/capsule.rb +10 -6
- data/lib/sidekiq/cli.rb +30 -23
- data/lib/sidekiq/client.rb +28 -17
- data/lib/sidekiq/component.rb +42 -3
- data/lib/sidekiq/config.rb +32 -22
- data/lib/sidekiq/embedded.rb +2 -1
- data/lib/sidekiq/iterable_job.rb +1 -0
- data/lib/sidekiq/job/iterable.rb +46 -18
- data/lib/sidekiq/job.rb +2 -2
- data/lib/sidekiq/job_logger.rb +8 -6
- data/lib/sidekiq/job_retry.rb +33 -10
- data/lib/sidekiq/job_util.rb +5 -1
- data/lib/sidekiq/launcher.rb +20 -10
- data/lib/sidekiq/loader.rb +57 -0
- data/lib/sidekiq/logger.rb +25 -69
- data/lib/sidekiq/manager.rb +0 -1
- data/lib/sidekiq/metrics/query.rb +71 -45
- data/lib/sidekiq/metrics/shared.rb +8 -5
- data/lib/sidekiq/metrics/tracking.rb +12 -7
- data/lib/sidekiq/middleware/current_attributes.rb +11 -19
- data/lib/sidekiq/monitor.rb +4 -8
- data/lib/sidekiq/paginator.rb +8 -1
- data/lib/sidekiq/processor.rb +21 -14
- data/lib/sidekiq/profiler.rb +73 -0
- data/lib/sidekiq/rails.rb +46 -67
- data/lib/sidekiq/redis_client_adapter.rb +0 -1
- data/lib/sidekiq/redis_connection.rb +2 -2
- data/lib/sidekiq/scheduled.rb +4 -4
- data/lib/sidekiq/testing.rb +3 -3
- data/lib/sidekiq/version.rb +2 -2
- data/lib/sidekiq/web/action.rb +146 -83
- data/lib/sidekiq/web/application.rb +361 -330
- data/lib/sidekiq/web/config.rb +120 -0
- data/lib/sidekiq/web/helpers.rb +58 -36
- data/lib/sidekiq/web/router.rb +60 -76
- data/lib/sidekiq/web.rb +51 -156
- data/lib/sidekiq.rb +6 -1
- data/sidekiq.gemspec +6 -6
- data/web/assets/images/logo.png +0 -0
- data/web/assets/images/status.png +0 -0
- data/web/assets/javascripts/application.js +42 -26
- data/web/assets/javascripts/base-charts.js +30 -16
- data/web/assets/javascripts/chartjs-adapter-date-fns.min.js +7 -0
- data/web/assets/javascripts/dashboard.js +1 -1
- data/web/assets/javascripts/metrics.js +16 -34
- data/web/assets/stylesheets/style.css +776 -0
- data/web/locales/ar.yml +2 -0
- data/web/locales/cs.yml +2 -0
- data/web/locales/da.yml +2 -0
- data/web/locales/de.yml +2 -0
- data/web/locales/el.yml +2 -0
- data/web/locales/en.yml +7 -0
- data/web/locales/es.yml +25 -2
- data/web/locales/fa.yml +2 -0
- data/web/locales/fr.yml +3 -1
- data/web/locales/gd.yml +2 -0
- data/web/locales/he.yml +2 -0
- data/web/locales/hi.yml +2 -0
- data/web/locales/it.yml +9 -0
- data/web/locales/ja.yml +2 -0
- data/web/locales/ko.yml +2 -0
- data/web/locales/lt.yml +2 -0
- data/web/locales/nb.yml +2 -0
- data/web/locales/nl.yml +2 -0
- data/web/locales/pl.yml +2 -0
- data/web/locales/{pt-br.yml → pt-BR.yml} +3 -1
- data/web/locales/pt.yml +2 -0
- data/web/locales/ru.yml +2 -0
- data/web/locales/sv.yml +2 -0
- data/web/locales/ta.yml +2 -0
- data/web/locales/tr.yml +2 -0
- data/web/locales/uk.yml +7 -5
- data/web/locales/ur.yml +2 -0
- data/web/locales/vi.yml +2 -0
- data/web/locales/{zh-cn.yml → zh-CN.yml} +86 -73
- data/web/locales/{zh-tw.yml → zh-TW.yml} +3 -1
- data/web/views/_footer.erb +31 -33
- data/web/views/_job_info.erb +91 -89
- data/web/views/_metrics_period_select.erb +13 -10
- data/web/views/_nav.erb +14 -21
- data/web/views/_paging.erb +22 -21
- data/web/views/_poll_link.erb +2 -2
- data/web/views/_summary.erb +16 -16
- data/web/views/busy.erb +123 -125
- data/web/views/dashboard.erb +62 -66
- data/web/views/dead.erb +31 -27
- data/web/views/filtering.erb +4 -4
- data/web/views/layout.erb +12 -28
- data/web/views/metrics.erb +76 -82
- data/web/views/metrics_for_job.erb +45 -46
- data/web/views/morgue.erb +65 -70
- data/web/views/profiles.erb +43 -0
- data/web/views/queue.erb +54 -52
- data/web/views/queues.erb +43 -41
- data/web/views/retries.erb +70 -75
- data/web/views/retry.erb +32 -27
- data/web/views/scheduled.erb +63 -55
- data/web/views/scheduled_job_info.erb +3 -3
- metadata +24 -41
- data/web/assets/stylesheets/application-dark.css +0 -147
- data/web/assets/stylesheets/application-rtl.css +0 -163
- data/web/assets/stylesheets/application.css +0 -759
- data/web/assets/stylesheets/bootstrap-rtl.min.css +0 -9
- data/web/assets/stylesheets/bootstrap.css +0 -5
- data/web/views/_status.erb +0 -4
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
require "fileutils"
|
|
2
|
+
require "sidekiq/component"
|
|
3
|
+
|
|
4
|
+
module Sidekiq
|
|
5
|
+
# Allows the user to profile jobs running in production.
|
|
6
|
+
# See details in the Profiling wiki page.
|
|
7
|
+
class Profiler
|
|
8
|
+
EXPIRY = 86400 # 1 day
|
|
9
|
+
DEFAULT_OPTIONS = {
|
|
10
|
+
mode: :wall
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
include Sidekiq::Component
|
|
14
|
+
|
|
15
|
+
def initialize(config)
|
|
16
|
+
@config = config
|
|
17
|
+
@vernier_output_dir = ENV.fetch("VERNIER_OUTPUT_DIR") { Dir.tmpdir }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def call(job, &block)
|
|
21
|
+
return yield unless job["profile"]
|
|
22
|
+
|
|
23
|
+
token = job["profile"]
|
|
24
|
+
type = job["class"]
|
|
25
|
+
jid = job["jid"]
|
|
26
|
+
started_at = Time.now
|
|
27
|
+
|
|
28
|
+
rundata = {
|
|
29
|
+
started_at: started_at.to_i,
|
|
30
|
+
token: token,
|
|
31
|
+
type: type,
|
|
32
|
+
jid: jid,
|
|
33
|
+
# .gz extension tells Vernier to compress the data
|
|
34
|
+
filename: File.join(
|
|
35
|
+
@vernier_output_dir,
|
|
36
|
+
"#{token}-#{type}-#{jid}-#{started_at.strftime("%Y%m%d-%H%M%S")}.json.gz"
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
profiler_options = profiler_options(job, rundata)
|
|
40
|
+
|
|
41
|
+
require "vernier"
|
|
42
|
+
begin
|
|
43
|
+
a = Time.now
|
|
44
|
+
rc = Vernier.profile(**profiler_options, &block)
|
|
45
|
+
b = Time.now
|
|
46
|
+
|
|
47
|
+
# Failed jobs will raise an exception on previous line and skip this
|
|
48
|
+
# block. Only successful jobs will persist profile data to Redis.
|
|
49
|
+
key = "#{token}-#{jid}"
|
|
50
|
+
data = File.read(rundata[:filename])
|
|
51
|
+
redis do |conn|
|
|
52
|
+
conn.multi do |m|
|
|
53
|
+
m.zadd("profiles", Time.now.to_f + EXPIRY, key)
|
|
54
|
+
m.hset(key, rundata.merge(elapsed: (b - a), data: data, size: data.bytesize))
|
|
55
|
+
m.expire(key, EXPIRY)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
rc
|
|
59
|
+
ensure
|
|
60
|
+
FileUtils.rm_f(rundata[:filename])
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def profiler_options(job, rundata)
|
|
67
|
+
profiler_options = (job["profiler_options"] || {}).transform_keys(&:to_sym)
|
|
68
|
+
profiler_options[:mode] = profiler_options[:mode].to_sym if profiler_options[:mode]
|
|
69
|
+
|
|
70
|
+
DEFAULT_OPTIONS.merge(profiler_options, {out: rundata[:filename]})
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
data/lib/sidekiq/rails.rb
CHANGED
|
@@ -1,84 +1,63 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "sidekiq/job"
|
|
4
|
-
require "rails"
|
|
5
|
-
|
|
6
3
|
module Sidekiq
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class Rails < ::Rails::Engine
|
|
19
|
-
class Reloader
|
|
20
|
-
def initialize(app = ::Rails.application)
|
|
21
|
-
@app = app
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def call
|
|
25
|
-
params = (::Rails::VERSION::STRING >= "7.1") ? {source: "job.sidekiq"} : {}
|
|
26
|
-
@app.reloader.wrap(**params) do
|
|
27
|
-
yield
|
|
4
|
+
begin
|
|
5
|
+
gem "railties", ">= 7.0"
|
|
6
|
+
require "rails"
|
|
7
|
+
require "sidekiq/job"
|
|
8
|
+
require_relative "../active_job/queue_adapters/sidekiq_adapter"
|
|
9
|
+
|
|
10
|
+
class Rails < ::Rails::Engine
|
|
11
|
+
class Reloader
|
|
12
|
+
def initialize(app = ::Rails.application)
|
|
13
|
+
@app = app
|
|
28
14
|
end
|
|
29
|
-
end
|
|
30
15
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
16
|
+
def call
|
|
17
|
+
params = (::Rails::VERSION::STRING >= "7.1") ? {source: "job.sidekiq"} : {}
|
|
18
|
+
@app.reloader.wrap(**params) do
|
|
19
|
+
yield
|
|
20
|
+
end
|
|
21
|
+
end
|
|
34
22
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
end
|
|
23
|
+
def inspect
|
|
24
|
+
"#<Sidekiq::Rails::Reloader @app=#{@app.class.name}>"
|
|
25
|
+
end
|
|
39
26
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
# manually retried, don't automatically die, etc.
|
|
44
|
-
#
|
|
45
|
-
# class SomeJob < ActiveJob::Base
|
|
46
|
-
# queue_as :default
|
|
47
|
-
# sidekiq_options retry: 3, backtrace: 10
|
|
48
|
-
# def perform
|
|
49
|
-
# end
|
|
50
|
-
# end
|
|
51
|
-
initializer "sidekiq.active_job_integration" do
|
|
52
|
-
ActiveSupport.on_load(:active_job) do
|
|
53
|
-
require_relative "../active_job/queue_adapters/sidekiq_adapter"
|
|
54
|
-
include ::Sidekiq::Job::Options unless respond_to?(:sidekiq_options)
|
|
27
|
+
def to_hash
|
|
28
|
+
{app: @app.class.name}
|
|
29
|
+
end
|
|
55
30
|
end
|
|
56
|
-
end
|
|
57
31
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
32
|
+
initializer "sidekiq.backtrace_cleaner" do
|
|
33
|
+
Sidekiq.configure_server do |config|
|
|
34
|
+
config[:backtrace_cleaner] = ->(backtrace) { ::Rails.backtrace_cleaner.clean(backtrace) }
|
|
35
|
+
end
|
|
61
36
|
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
# This hook happens after all initializers are run, just before returning
|
|
65
|
-
# from config/environment.rb back to sidekiq/cli.rb.
|
|
66
|
-
#
|
|
67
|
-
# None of this matters on the client-side, only within the Sidekiq process itself.
|
|
68
|
-
config.after_initialize do
|
|
69
|
-
Sidekiq.configure_server do |config|
|
|
70
|
-
config[:reloader] = Sidekiq::Rails::Reloader.new
|
|
71
37
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
38
|
+
# This hook happens after all initializers are run, just before returning
|
|
39
|
+
# from config/environment.rb back to sidekiq/cli.rb.
|
|
40
|
+
#
|
|
41
|
+
# None of this matters on the client-side, only within the Sidekiq process itself.
|
|
42
|
+
config.after_initialize do
|
|
43
|
+
Sidekiq.configure_server do |config|
|
|
44
|
+
config[:reloader] = Sidekiq::Rails::Reloader.new
|
|
45
|
+
|
|
46
|
+
# This is the integration code necessary so that if a job uses `Rails.logger.info "Hello"`,
|
|
47
|
+
# it will appear in the Sidekiq console with all of the job context.
|
|
48
|
+
unless ::Rails.logger == config.logger || ::ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, $stdout)
|
|
49
|
+
if ::Rails.logger.respond_to?(:broadcast_to)
|
|
50
|
+
::Rails.logger.broadcast_to(config.logger)
|
|
51
|
+
elsif ::ActiveSupport::Logger.respond_to?(:broadcast)
|
|
52
|
+
::Rails.logger.extend(::ActiveSupport::Logger.broadcast(config.logger))
|
|
53
|
+
else
|
|
54
|
+
::Rails.logger = ::ActiveSupport::BroadcastLogger.new(::Rails.logger, config.logger)
|
|
55
|
+
end
|
|
79
56
|
end
|
|
80
57
|
end
|
|
81
58
|
end
|
|
82
59
|
end
|
|
60
|
+
rescue Gem::LoadError
|
|
61
|
+
# Rails not available or version requirement not met
|
|
83
62
|
end
|
|
84
63
|
end
|
|
@@ -23,7 +23,7 @@ module Sidekiq
|
|
|
23
23
|
|
|
24
24
|
size = symbolized_options.delete(:size) || 5
|
|
25
25
|
pool_timeout = symbolized_options.delete(:pool_timeout) || 1
|
|
26
|
-
|
|
26
|
+
symbolized_options.delete(:pool_name)
|
|
27
27
|
|
|
28
28
|
# Default timeout in redis-client is 1 second, which can be too aggressive
|
|
29
29
|
# if the Sidekiq process is CPU-bound. With 10-15 threads and a thread quantum of 100ms,
|
|
@@ -33,7 +33,7 @@ module Sidekiq
|
|
|
33
33
|
symbolized_options[:timeout] ||= 3
|
|
34
34
|
|
|
35
35
|
redis_config = Sidekiq::RedisClientAdapter.new(symbolized_options)
|
|
36
|
-
ConnectionPool.new(timeout: pool_timeout, size: size
|
|
36
|
+
ConnectionPool.new(timeout: pool_timeout, size: size) do
|
|
37
37
|
redis_config.new_client
|
|
38
38
|
end
|
|
39
39
|
end
|
data/lib/sidekiq/scheduled.rb
CHANGED
|
@@ -115,9 +115,9 @@ module Sidekiq
|
|
|
115
115
|
private
|
|
116
116
|
|
|
117
117
|
def wait
|
|
118
|
-
@sleeper.pop(random_poll_interval)
|
|
118
|
+
@sleeper.pop(timeout: random_poll_interval)
|
|
119
119
|
rescue Timeout::Error
|
|
120
|
-
#
|
|
120
|
+
# TODO move to exception: false
|
|
121
121
|
rescue => ex
|
|
122
122
|
# if poll_interval_average hasn't been calculated yet, we can
|
|
123
123
|
# raise an error trying to reach Redis.
|
|
@@ -155,7 +155,7 @@ module Sidekiq
|
|
|
155
155
|
else
|
|
156
156
|
# With 10+ processes, we should have enough randomness to get decent polling
|
|
157
157
|
# across the entire timespan
|
|
158
|
-
interval * rand
|
|
158
|
+
interval * rand * 2
|
|
159
159
|
end
|
|
160
160
|
end
|
|
161
161
|
|
|
@@ -223,7 +223,7 @@ module Sidekiq
|
|
|
223
223
|
total += INITIAL_WAIT unless @config[:poll_interval_average]
|
|
224
224
|
total += (5 * rand)
|
|
225
225
|
|
|
226
|
-
@sleeper.pop(total)
|
|
226
|
+
@sleeper.pop(timeout: total)
|
|
227
227
|
rescue Timeout::Error
|
|
228
228
|
ensure
|
|
229
229
|
# periodically clean out the `processes` set in Redis which can collect
|
data/lib/sidekiq/testing.rb
CHANGED
|
@@ -83,11 +83,11 @@ module Sidekiq
|
|
|
83
83
|
class EmptyQueueError < RuntimeError; end
|
|
84
84
|
|
|
85
85
|
module TestingClient
|
|
86
|
-
def atomic_push(conn, payloads)
|
|
86
|
+
private def atomic_push(conn, payloads)
|
|
87
87
|
if Sidekiq::Testing.fake?
|
|
88
88
|
payloads.each do |job|
|
|
89
89
|
job = Sidekiq.load_json(Sidekiq.dump_json(job))
|
|
90
|
-
job["enqueued_at"] =
|
|
90
|
+
job["enqueued_at"] = ::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond) unless job["at"]
|
|
91
91
|
Queues.push(job["queue"], job["class"], job)
|
|
92
92
|
end
|
|
93
93
|
true
|
|
@@ -329,6 +329,6 @@ module Sidekiq
|
|
|
329
329
|
end
|
|
330
330
|
end
|
|
331
331
|
|
|
332
|
-
if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test? && !$TESTING
|
|
332
|
+
if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test? && !$TESTING # rubocop:disable Style/GlobalVars
|
|
333
333
|
warn("⛔️ WARNING: Sidekiq testing API enabled, but this is not the test environment. Your jobs will not go to Redis.", uplevel: 1)
|
|
334
334
|
end
|
data/lib/sidekiq/version.rb
CHANGED
data/lib/sidekiq/web/action.rb
CHANGED
|
@@ -1,115 +1,178 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "erb"
|
|
4
|
+
|
|
3
5
|
module Sidekiq
|
|
4
|
-
class
|
|
5
|
-
|
|
6
|
+
class Web
|
|
7
|
+
##
|
|
8
|
+
# These instance methods are available to all executing ERB
|
|
9
|
+
# templates.
|
|
10
|
+
class Action
|
|
11
|
+
attr_accessor :env, :block
|
|
12
|
+
|
|
13
|
+
def initialize(env, block)
|
|
14
|
+
@_erb = false
|
|
15
|
+
@env = env
|
|
16
|
+
@block = block
|
|
17
|
+
end
|
|
6
18
|
|
|
7
|
-
|
|
19
|
+
def config
|
|
20
|
+
env[:web_config]
|
|
21
|
+
end
|
|
8
22
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
23
|
+
def request
|
|
24
|
+
@request ||= ::Rack::Request.new(env)
|
|
25
|
+
end
|
|
12
26
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
27
|
+
def halt(res)
|
|
28
|
+
throw :halt, [res, {"content-type" => "text/plain"}, [res.to_s]]
|
|
29
|
+
end
|
|
16
30
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
31
|
+
# external redirect
|
|
32
|
+
def redirect_to(url)
|
|
33
|
+
throw :halt, [302, {"location" => url}, []]
|
|
34
|
+
end
|
|
20
35
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
36
|
+
def header(key, value)
|
|
37
|
+
env["response_headers"][key] = value.to_s
|
|
38
|
+
end
|
|
24
39
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
40
|
+
# internal redirect
|
|
41
|
+
def redirect(location)
|
|
42
|
+
throw :halt, [302, {"location" => "#{request.base_url}#{location}"}, []]
|
|
43
|
+
end
|
|
29
44
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
45
|
+
def reload_page
|
|
46
|
+
current_location = request.referer.gsub(request.base_url, "")
|
|
47
|
+
redirect current_location
|
|
48
|
+
end
|
|
33
49
|
|
|
34
|
-
|
|
35
|
-
|
|
50
|
+
# stuff after ? or form input
|
|
51
|
+
# uses String keys, no Symbols!
|
|
52
|
+
def url_params(key)
|
|
53
|
+
warn { "URL parameter `#{key}` should be accessed via String, not Symbol (at #{caller(3..3).first})" } if key.is_a?(Symbol)
|
|
54
|
+
request.params[key.to_s]
|
|
55
|
+
end
|
|
36
56
|
|
|
37
|
-
|
|
38
|
-
|
|
57
|
+
# variables embedded in path, `/metrics/:name`
|
|
58
|
+
# uses Symbol keys, no Strings!
|
|
59
|
+
def route_params(key)
|
|
60
|
+
warn { "Route parameter `#{key}` should be accessed via Symbol, not String (at #{caller(3..3).first})" } if key.is_a?(String)
|
|
61
|
+
env["rack.route_params"][key.to_sym]
|
|
62
|
+
end
|
|
39
63
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
64
|
+
def params
|
|
65
|
+
warn { "Direct access to Rack parameters is discouraged, use `url_params` or `route_params` (at #{caller(3..3).first})" }
|
|
66
|
+
request.params
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def session
|
|
70
|
+
env["rack.session"] || fail(<<~EOM)
|
|
71
|
+
Sidekiq::Web needs a valid Rack session. If this is a Rails app, make
|
|
72
|
+
sure you mount Sidekiq::Web *inside* your application routes:
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
Rails.application.routes.draw do
|
|
76
|
+
mount Sidekiq::Web => "/sidekiq"
|
|
77
|
+
....
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
If this is a Rails app in API mode, you need to enable sessions.
|
|
82
|
+
|
|
83
|
+
https://guides.rubyonrails.org/api_app.html#using-session-middlewares
|
|
44
84
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
85
|
+
If this is a bare Rack app, use a session middleware before Sidekiq::Web:
|
|
86
|
+
|
|
87
|
+
# first, use IRB to create a shared secret key for sessions and commit it
|
|
88
|
+
require 'securerandom'; File.open(".session.key", "w") {|f| f.write(SecureRandom.hex(32)) }
|
|
89
|
+
|
|
90
|
+
# now use the secret with a session cookie middleware
|
|
91
|
+
use Rack::Session::Cookie, secret: File.read(".session.key"), same_site: true, max_age: 86400
|
|
92
|
+
run Sidekiq::Web
|
|
93
|
+
|
|
94
|
+
EOM
|
|
52
95
|
end
|
|
53
|
-
end
|
|
54
96
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
97
|
+
def logger
|
|
98
|
+
Sidekiq.logger
|
|
99
|
+
end
|
|
58
100
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
src = ERB.new(File.read(filename)).src
|
|
65
|
-
|
|
66
|
-
# Need to use lineno less by 1 because erb generates a
|
|
67
|
-
# comment before the source code.
|
|
68
|
-
WebAction.class_eval <<-RUBY, filename, -1 # standard:disable Style/EvalWithLocation
|
|
69
|
-
def _erb_#{content}
|
|
70
|
-
#{src}
|
|
71
|
-
end
|
|
72
|
-
RUBY
|
|
73
|
-
end
|
|
101
|
+
# flash { "Some message to show on redirect" }
|
|
102
|
+
def flash
|
|
103
|
+
msg = yield
|
|
104
|
+
logger.info msg
|
|
105
|
+
session[:skq_flash] = msg
|
|
74
106
|
end
|
|
75
107
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
@_erb = true
|
|
80
|
-
content = _erb(content, options[:locals])
|
|
108
|
+
def flash?
|
|
109
|
+
session&.[](:skq_flash)
|
|
110
|
+
end
|
|
81
111
|
|
|
82
|
-
|
|
112
|
+
def get_flash
|
|
113
|
+
@flash ||= session.delete(:skq_flash)
|
|
83
114
|
end
|
|
84
|
-
end
|
|
85
115
|
|
|
86
|
-
|
|
87
|
-
|
|
116
|
+
def erb(content, options = {})
|
|
117
|
+
if content.is_a? Symbol
|
|
118
|
+
unless respond_to?(:"_erb_#{content}")
|
|
119
|
+
views = options[:views] || Web.views
|
|
120
|
+
filename = "#{views}/#{content}.erb"
|
|
121
|
+
src = ERB.new(File.read(filename)).src
|
|
122
|
+
|
|
123
|
+
# Need to use lineno less by 1 because erb generates a
|
|
124
|
+
# comment before the source code.
|
|
125
|
+
Action.class_eval <<-RUBY, filename, -1 # standard:disable Style/EvalWithLocation
|
|
126
|
+
def _erb_#{content}
|
|
127
|
+
#{src}
|
|
128
|
+
end
|
|
129
|
+
RUBY
|
|
130
|
+
end
|
|
131
|
+
end
|
|
88
132
|
|
|
89
|
-
|
|
90
|
-
|
|
133
|
+
if @_erb
|
|
134
|
+
_erb(content, options[:locals])
|
|
135
|
+
else
|
|
136
|
+
@_erb = true
|
|
137
|
+
content = _erb(content, options[:locals])
|
|
91
138
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
139
|
+
_render { content }
|
|
140
|
+
end
|
|
141
|
+
end
|
|
95
142
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
143
|
+
def render(engine, content, options = {})
|
|
144
|
+
raise "Only erb templates are supported" if engine != :erb
|
|
145
|
+
|
|
146
|
+
erb(content, options)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def json(payload)
|
|
150
|
+
[200,
|
|
151
|
+
{"content-type" => "application/json", "cache-control" => "private, no-store"},
|
|
152
|
+
[Sidekiq.dump_json(payload)]]
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
private
|
|
102
156
|
|
|
103
|
-
|
|
157
|
+
def warn
|
|
158
|
+
Sidekiq.logger.warn yield
|
|
159
|
+
end
|
|
104
160
|
|
|
105
|
-
|
|
106
|
-
|
|
161
|
+
def _erb(file, locals)
|
|
162
|
+
locals&.each { |k, v| define_singleton_method(k) { v } unless singleton_methods.include? k }
|
|
107
163
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
164
|
+
if file.is_a?(String)
|
|
165
|
+
ERB.new(file).result(binding)
|
|
166
|
+
else
|
|
167
|
+
send(:"_erb_#{file}")
|
|
168
|
+
end
|
|
112
169
|
end
|
|
170
|
+
|
|
171
|
+
class_eval <<-RUBY, ::Sidekiq::Web::LAYOUT, -1 # standard:disable Style/EvalWithLocation
|
|
172
|
+
def _render
|
|
173
|
+
#{ERB.new(File.read(::Sidekiq::Web::LAYOUT)).src}
|
|
174
|
+
end
|
|
175
|
+
RUBY
|
|
113
176
|
end
|
|
114
177
|
end
|
|
115
178
|
end
|