mini-clean-lib 0.0.1

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 (151) hide show
  1. checksums.yaml +7 -0
  2. data/mini-clean-lib.gemspec +12 -0
  3. data/sidekiq-8.1.6/Changes.md +2515 -0
  4. data/sidekiq-8.1.6/LICENSE.txt +9 -0
  5. data/sidekiq-8.1.6/README.md +124 -0
  6. data/sidekiq-8.1.6/bin/kiq +17 -0
  7. data/sidekiq-8.1.6/bin/lint-herb +13 -0
  8. data/sidekiq-8.1.6/bin/multi_queue_bench +271 -0
  9. data/sidekiq-8.1.6/bin/sidekiq +37 -0
  10. data/sidekiq-8.1.6/bin/sidekiqload +256 -0
  11. data/sidekiq-8.1.6/bin/sidekiqmon +11 -0
  12. data/sidekiq-8.1.6/bin/webload +69 -0
  13. data/sidekiq-8.1.6/lib/active_job/queue_adapters/sidekiq_adapter.rb +124 -0
  14. data/sidekiq-8.1.6/lib/generators/sidekiq/job_generator.rb +71 -0
  15. data/sidekiq-8.1.6/lib/generators/sidekiq/templates/job.rb.erb +9 -0
  16. data/sidekiq-8.1.6/lib/generators/sidekiq/templates/job_spec.rb.erb +6 -0
  17. data/sidekiq-8.1.6/lib/generators/sidekiq/templates/job_test.rb.erb +8 -0
  18. data/sidekiq-8.1.6/lib/sidekiq/api.rb +1466 -0
  19. data/sidekiq-8.1.6/lib/sidekiq/capsule.rb +135 -0
  20. data/sidekiq-8.1.6/lib/sidekiq/cli.rb +433 -0
  21. data/sidekiq-8.1.6/lib/sidekiq/client.rb +306 -0
  22. data/sidekiq-8.1.6/lib/sidekiq/component.rb +132 -0
  23. data/sidekiq-8.1.6/lib/sidekiq/config.rb +316 -0
  24. data/sidekiq-8.1.6/lib/sidekiq/deploy.rb +64 -0
  25. data/sidekiq-8.1.6/lib/sidekiq/embedded.rb +64 -0
  26. data/sidekiq-8.1.6/lib/sidekiq/fetch.rb +89 -0
  27. data/sidekiq-8.1.6/lib/sidekiq/iterable_job.rb +56 -0
  28. data/sidekiq-8.1.6/lib/sidekiq/job/interrupt_handler.rb +24 -0
  29. data/sidekiq-8.1.6/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
  30. data/sidekiq-8.1.6/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
  31. data/sidekiq-8.1.6/lib/sidekiq/job/iterable/enumerators.rb +135 -0
  32. data/sidekiq-8.1.6/lib/sidekiq/job/iterable.rb +322 -0
  33. data/sidekiq-8.1.6/lib/sidekiq/job.rb +400 -0
  34. data/sidekiq-8.1.6/lib/sidekiq/job_logger.rb +54 -0
  35. data/sidekiq-8.1.6/lib/sidekiq/job_retry.rb +332 -0
  36. data/sidekiq-8.1.6/lib/sidekiq/job_util.rb +113 -0
  37. data/sidekiq-8.1.6/lib/sidekiq/launcher.rb +283 -0
  38. data/sidekiq-8.1.6/lib/sidekiq/loader.rb +57 -0
  39. data/sidekiq-8.1.6/lib/sidekiq/logger.rb +87 -0
  40. data/sidekiq-8.1.6/lib/sidekiq/manager.rb +134 -0
  41. data/sidekiq-8.1.6/lib/sidekiq/metrics/query.rb +184 -0
  42. data/sidekiq-8.1.6/lib/sidekiq/metrics/shared.rb +109 -0
  43. data/sidekiq-8.1.6/lib/sidekiq/metrics/tracking.rb +153 -0
  44. data/sidekiq-8.1.6/lib/sidekiq/middleware/chain.rb +207 -0
  45. data/sidekiq-8.1.6/lib/sidekiq/middleware/current_attributes.rb +120 -0
  46. data/sidekiq-8.1.6/lib/sidekiq/middleware/i18n.rb +44 -0
  47. data/sidekiq-8.1.6/lib/sidekiq/middleware/modules.rb +23 -0
  48. data/sidekiq-8.1.6/lib/sidekiq/monitor.rb +143 -0
  49. data/sidekiq-8.1.6/lib/sidekiq/paginator.rb +74 -0
  50. data/sidekiq-8.1.6/lib/sidekiq/processor.rb +298 -0
  51. data/sidekiq-8.1.6/lib/sidekiq/profiler.rb +73 -0
  52. data/sidekiq-8.1.6/lib/sidekiq/rails.rb +63 -0
  53. data/sidekiq-8.1.6/lib/sidekiq/redis_client_adapter.rb +117 -0
  54. data/sidekiq-8.1.6/lib/sidekiq/redis_connection.rb +115 -0
  55. data/sidekiq-8.1.6/lib/sidekiq/ring_buffer.rb +32 -0
  56. data/sidekiq-8.1.6/lib/sidekiq/scheduled.rb +235 -0
  57. data/sidekiq-8.1.6/lib/sidekiq/sd_notify.rb +149 -0
  58. data/sidekiq-8.1.6/lib/sidekiq/systemd.rb +26 -0
  59. data/sidekiq-8.1.6/lib/sidekiq/test_api.rb +331 -0
  60. data/sidekiq-8.1.6/lib/sidekiq/testing/inline.rb +2 -0
  61. data/sidekiq-8.1.6/lib/sidekiq/testing.rb +3 -0
  62. data/sidekiq-8.1.6/lib/sidekiq/transaction_aware_client.rb +59 -0
  63. data/sidekiq-8.1.6/lib/sidekiq/tui/controls.rb +53 -0
  64. data/sidekiq-8.1.6/lib/sidekiq/tui/filtering.rb +53 -0
  65. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/base_tab.rb +204 -0
  66. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/busy.rb +118 -0
  67. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/dead.rb +19 -0
  68. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/home.rb +144 -0
  69. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/metrics.rb +131 -0
  70. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/queues.rb +95 -0
  71. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/retries.rb +19 -0
  72. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/scheduled.rb +19 -0
  73. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/set_tab.rb +96 -0
  74. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs.rb +15 -0
  75. data/sidekiq-8.1.6/lib/sidekiq/tui.rb +382 -0
  76. data/sidekiq-8.1.6/lib/sidekiq/version.rb +10 -0
  77. data/sidekiq-8.1.6/lib/sidekiq/web/action.rb +178 -0
  78. data/sidekiq-8.1.6/lib/sidekiq/web/application.rb +480 -0
  79. data/sidekiq-8.1.6/lib/sidekiq/web/config.rb +117 -0
  80. data/sidekiq-8.1.6/lib/sidekiq/web/helpers.rb +474 -0
  81. data/sidekiq-8.1.6/lib/sidekiq/web/router.rb +91 -0
  82. data/sidekiq-8.1.6/lib/sidekiq/web.rb +140 -0
  83. data/sidekiq-8.1.6/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  84. data/sidekiq-8.1.6/lib/sidekiq.rb +161 -0
  85. data/sidekiq-8.1.6/sidekiq.gemspec +31 -0
  86. data/sidekiq-8.1.6/web/assets/images/apple-touch-icon.png +0 -0
  87. data/sidekiq-8.1.6/web/assets/images/favicon.ico +0 -0
  88. data/sidekiq-8.1.6/web/assets/images/logo.png +0 -0
  89. data/sidekiq-8.1.6/web/assets/images/status.png +0 -0
  90. data/sidekiq-8.1.6/web/assets/javascripts/application.js +204 -0
  91. data/sidekiq-8.1.6/web/assets/javascripts/base-charts.js +120 -0
  92. data/sidekiq-8.1.6/web/assets/javascripts/chart.min.js +13 -0
  93. data/sidekiq-8.1.6/web/assets/javascripts/chartjs-adapter-date-fns.min.js +7 -0
  94. data/sidekiq-8.1.6/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  95. data/sidekiq-8.1.6/web/assets/javascripts/dashboard-charts.js +194 -0
  96. data/sidekiq-8.1.6/web/assets/javascripts/dashboard.js +63 -0
  97. data/sidekiq-8.1.6/web/assets/javascripts/metrics.js +280 -0
  98. data/sidekiq-8.1.6/web/assets/stylesheets/style.css +776 -0
  99. data/sidekiq-8.1.6/web/locales/ar.yml +89 -0
  100. data/sidekiq-8.1.6/web/locales/cs.yml +80 -0
  101. data/sidekiq-8.1.6/web/locales/da.yml +77 -0
  102. data/sidekiq-8.1.6/web/locales/de.yml +83 -0
  103. data/sidekiq-8.1.6/web/locales/el.yml +89 -0
  104. data/sidekiq-8.1.6/web/locales/en.yml +110 -0
  105. data/sidekiq-8.1.6/web/locales/es.yml +109 -0
  106. data/sidekiq-8.1.6/web/locales/fa.yml +82 -0
  107. data/sidekiq-8.1.6/web/locales/fr.yml +100 -0
  108. data/sidekiq-8.1.6/web/locales/gd.yml +110 -0
  109. data/sidekiq-8.1.6/web/locales/he.yml +82 -0
  110. data/sidekiq-8.1.6/web/locales/hi.yml +77 -0
  111. data/sidekiq-8.1.6/web/locales/it.yml +109 -0
  112. data/sidekiq-8.1.6/web/locales/ja.yml +92 -0
  113. data/sidekiq-8.1.6/web/locales/ko.yml +70 -0
  114. data/sidekiq-8.1.6/web/locales/lt.yml +85 -0
  115. data/sidekiq-8.1.6/web/locales/nb.yml +79 -0
  116. data/sidekiq-8.1.6/web/locales/nl.yml +70 -0
  117. data/sidekiq-8.1.6/web/locales/pl.yml +61 -0
  118. data/sidekiq-8.1.6/web/locales/pt-BR.yml +97 -0
  119. data/sidekiq-8.1.6/web/locales/pt.yml +69 -0
  120. data/sidekiq-8.1.6/web/locales/ru.yml +85 -0
  121. data/sidekiq-8.1.6/web/locales/sv.yml +70 -0
  122. data/sidekiq-8.1.6/web/locales/ta.yml +77 -0
  123. data/sidekiq-8.1.6/web/locales/tr.yml +102 -0
  124. data/sidekiq-8.1.6/web/locales/uk.yml +102 -0
  125. data/sidekiq-8.1.6/web/locales/ur.yml +82 -0
  126. data/sidekiq-8.1.6/web/locales/vi.yml +85 -0
  127. data/sidekiq-8.1.6/web/locales/zh-CN.yml +107 -0
  128. data/sidekiq-8.1.6/web/locales/zh-TW.yml +103 -0
  129. data/sidekiq-8.1.6/web/views/_footer.html.erb +32 -0
  130. data/sidekiq-8.1.6/web/views/_job_info.html.erb +115 -0
  131. data/sidekiq-8.1.6/web/views/_metrics_period_select.html.erb +15 -0
  132. data/sidekiq-8.1.6/web/views/_nav.html.erb +45 -0
  133. data/sidekiq-8.1.6/web/views/_paging.html.erb +26 -0
  134. data/sidekiq-8.1.6/web/views/_poll_link.html.erb +4 -0
  135. data/sidekiq-8.1.6/web/views/_summary.html.erb +40 -0
  136. data/sidekiq-8.1.6/web/views/busy.html.erb +155 -0
  137. data/sidekiq-8.1.6/web/views/dashboard.html.erb +104 -0
  138. data/sidekiq-8.1.6/web/views/dead.html.erb +38 -0
  139. data/sidekiq-8.1.6/web/views/filtering.html.erb +6 -0
  140. data/sidekiq-8.1.6/web/views/layout.html.erb +26 -0
  141. data/sidekiq-8.1.6/web/views/metrics.html.erb +85 -0
  142. data/sidekiq-8.1.6/web/views/metrics_for_job.html.erb +58 -0
  143. data/sidekiq-8.1.6/web/views/morgue.html.erb +69 -0
  144. data/sidekiq-8.1.6/web/views/profiles.html.erb +43 -0
  145. data/sidekiq-8.1.6/web/views/queue.html.erb +57 -0
  146. data/sidekiq-8.1.6/web/views/queues.html.erb +46 -0
  147. data/sidekiq-8.1.6/web/views/retries.html.erb +77 -0
  148. data/sidekiq-8.1.6/web/views/retry.html.erb +39 -0
  149. data/sidekiq-8.1.6/web/views/scheduled.html.erb +64 -0
  150. data/sidekiq-8.1.6/web/views/scheduled_job_info.html.erb +8 -0
  151. metadata +190 -0
@@ -0,0 +1,95 @@
1
+ require_relative "base_tab"
2
+
3
+ module Sidekiq
4
+ class TUI
5
+ module Tabs
6
+ class Queues < BaseTab
7
+ def features
8
+ %i[selectable]
9
+ end
10
+
11
+ def controls
12
+ @controls ||= super + [
13
+ {code: "D", modifiers: ["shift"], display: "D", description: "Delete",
14
+ action: ->(tui, tab) { tab.delete_queue! }, refresh: true},
15
+ {code: "p", description: "Pause/Unpause Queue",
16
+ action: ->(tui, tab) { tab.toggle_pause_queue! }}
17
+ ]
18
+ end
19
+
20
+ def delete_queue!
21
+ each_selection do |qname|
22
+ Sidekiq::Queue.new(qname).clear
23
+ end
24
+ end
25
+
26
+ def toggle_pause_queue!
27
+ return unless Sidekiq.pro?
28
+
29
+ each_selection do |qname|
30
+ queue = Sidekiq::Queue.new(qname)
31
+ if queue.paused?
32
+ queue.unpause!
33
+ else
34
+ queue.pause!
35
+ end
36
+ end
37
+ end
38
+
39
+ def refresh_data
40
+ refresh_data_for_stats
41
+
42
+ queue_summaries = Sidekiq::Stats.new.queue_summaries.sort_by(&:name)
43
+
44
+ selected = Array(@data[:selected])
45
+ queues = queue_summaries.map { |queue_summary|
46
+ row_cells = [
47
+ selected.index(queue_summary.name) ? "✅" : "",
48
+ queue_summary.name,
49
+ queue_summary.size.to_s,
50
+ number_with_delimiter(queue_summary.latency, {precision: 2})
51
+ ]
52
+ row_cells << (queue_summary.paused? ? "✅" : "") if Sidekiq.pro?
53
+ row_cells
54
+ }
55
+
56
+ table_row_ids = queue_summaries.map(&:name)
57
+
58
+ @data[:queues] = queues
59
+ @data[:table] = {row_ids: table_row_ids}
60
+ end
61
+
62
+ def render(tui, frame, area)
63
+ header = ["☑️", "Queue", "Size", "Latency"].map { |x| t(x) }
64
+ header << "Paused?" if Sidekiq.pro?
65
+
66
+ chunks = tui.layout_split(
67
+ area,
68
+ direction: :vertical,
69
+ constraints: [
70
+ tui.constraint_length(4), # Stats
71
+ tui.constraint_fill(1) # Table
72
+ ]
73
+ )
74
+
75
+ render_stats_section(tui, frame, chunks[0])
76
+ render_table(tui, frame, chunks[1]) do
77
+ {
78
+ title: t(name),
79
+ header:,
80
+ widths: header.map.with_index { |_, idx|
81
+ tui.constraint_length((idx == 1) ? 60 : 10)
82
+ },
83
+ rows: @data[:queues].map.with_index { |cells, idx|
84
+ tui.table_row(
85
+ cells:,
86
+ style: idx.even? ? nil : tui.style(bg: :dark_gray)
87
+ )
88
+ }
89
+ }
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,19 @@
1
+ require_relative "base_tab"
2
+ require_relative "set_tab"
3
+
4
+ module Sidekiq
5
+ class TUI
6
+ module Tabs
7
+ class Retries < BaseTab
8
+ include SetTab
9
+
10
+ def set_class = Sidekiq::RetrySet
11
+
12
+ def refresh_data
13
+ refresh_data_for_stats
14
+ refresh_data_for_set
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ require_relative "base_tab"
2
+ require_relative "set_tab"
3
+
4
+ module Sidekiq
5
+ class TUI
6
+ module Tabs
7
+ class Scheduled < BaseTab
8
+ include SetTab
9
+
10
+ def set_class = Sidekiq::ScheduledSet
11
+
12
+ def refresh_data
13
+ refresh_data_for_stats
14
+ refresh_data_for_set
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,96 @@
1
+ module Sidekiq
2
+ class TUI
3
+ module Tabs
4
+ module SetTab
5
+ include Sidekiq::Paginator
6
+ include Filtering
7
+
8
+ def features
9
+ %i[selectable pageable filterable]
10
+ end
11
+
12
+ def controls
13
+ @controls ||= super + [{code: "D", modifiers: ["shift"], display: "D", description: "Delete",
14
+ action: ->(tui, tab) { tab.alter_rows!(:delete) }, refresh: true},
15
+ {code: "R", modifiers: ["shift"], display: "R", description: "Retry",
16
+ action: ->(tui, tab) { tab.alter_rows!(:retry) }, refresh: true},
17
+ {code: "E", modifiers: ["shift"], display: "E", description: "Enqueue",
18
+ action: ->(tui, tab) { tab.alter_rows!(:add_to_queue) }, refresh: true},
19
+ {code: "K", modifiers: ["shift"], display: "K", description: "Kill",
20
+ action: ->(tui, tab) { tab.alter_rows!(:kill) }, refresh: true}]
21
+ end
22
+
23
+ def alter_rows!(action)
24
+ # log(to_s, @data[:selected])
25
+ set = set_class.new
26
+ each_selection do |id|
27
+ score, jid = id.split("|")
28
+ item = set.fetch(score, jid)&.first
29
+ item&.send(action)
30
+ end
31
+ end
32
+
33
+ def refresh_data_for_set
34
+ set = set_class.new
35
+ f = current_filter
36
+ pager, rows, current, total = if f && f.size > 2
37
+ rows = set.scan(f).to_a
38
+ sz = rows.size
39
+ [Sidekiq::TUI::PageOptions.new(1, sz), rows, 1, sz]
40
+ else
41
+ pager = @data.dig(:table, :pager) || Sidekiq::TUI::PageOptions.new(1, 25)
42
+ current, total, items = page(set.name, pager.page, pager.size)
43
+ rows = items.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
44
+ [pager, rows, current, total]
45
+ end
46
+
47
+ @data.merge!(
48
+ table: {pager:, rows:, current_page: current, total:,
49
+ next_page: (current * pager.size < total) ? pager.page + 1 : nil,
50
+ row_ids: rows.map { |job| [job.score, job["jid"]].join("|") }}
51
+ )
52
+ end
53
+
54
+ def render(tui, frame, area)
55
+ chunks = tui.layout_split(
56
+ area,
57
+ direction: :vertical,
58
+ constraints: [
59
+ tui.constraint_length(4), # Stats
60
+ tui.constraint_fill(1) # Table
61
+ ]
62
+ )
63
+
64
+ render_stats_section(tui, frame, chunks[0])
65
+ render_table(tui, frame, chunks[1]) do
66
+ {
67
+ title: t(name),
68
+ header: ["☑️", "When", "Queue", "Job", "Arguments"].map { |x| t(x) },
69
+ widths: [
70
+ tui.constraint_length(5),
71
+ tui.constraint_length(24),
72
+ tui.constraint_length(20),
73
+ tui.constraint_length(30),
74
+ tui.constraint_fill(1)
75
+ ]
76
+ }.tap do |h|
77
+ rows = @data[:table][:rows].map.with_index { |entry, idx|
78
+ tui.table_row(
79
+ cells: [
80
+ selected?(entry) ? "✅" : "",
81
+ entry.at,
82
+ entry.queue,
83
+ entry.display_class,
84
+ entry.display_args
85
+ ],
86
+ style: idx.even? ? nil : tui.style(bg: :dark_gray)
87
+ )
88
+ }
89
+ h[:rows] = rows
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,15 @@
1
+ require_relative "tabs/home"
2
+ require_relative "tabs/busy"
3
+ require_relative "tabs/queues"
4
+ require_relative "tabs/scheduled"
5
+ require_relative "tabs/retries"
6
+ require_relative "tabs/dead"
7
+ require_relative "tabs/metrics"
8
+
9
+ module Sidekiq
10
+ class TUI
11
+ module Tabs
12
+ All = Set.new([Home, Busy, Queues, Scheduled, Retries, Dead, Metrics])
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,382 @@
1
+ # https://sr.ht/~kerrick/ratatui_ruby/
2
+ # https://git.sr.ht/~kerrick/ratatui_ruby/tree/stable/item/examples/
3
+ gem "ratatui_ruby", ">=1.4.0"
4
+ require "ratatui_ruby"
5
+
6
+ RatatuiRuby.debug_mode! if !!ENV["DEBUG"]
7
+
8
+ require "sidekiq/api"
9
+ require "sidekiq/paginator"
10
+
11
+ require_relative "tui/filtering"
12
+ require_relative "tui/controls"
13
+ require_relative "tui/tabs"
14
+
15
+ module Sidekiq
16
+ class TUI
17
+ include Sidekiq::Component
18
+
19
+ PageOptions = Data.define(:page, :size)
20
+
21
+ REFRESH_INTERVAL_SECONDS = 2
22
+ LOCALE_DIRECTORIES = [File.expand_path("#{File.dirname(__FILE__)}/../../web/locales")]
23
+
24
+ attr_reader :lang
25
+
26
+ # language is meant to be a locale code, e.g.
27
+ # LANG=en_US.utf-8
28
+ def initialize(cfg, language: ENV["LANG"] || "en")
29
+ @lang = language
30
+ @config = cfg
31
+ @base_style = nil
32
+ @last_refresh = Time.at(0)
33
+ @fps = Array.new(2) { 0 }
34
+ @previous_fps = 0
35
+ @showing = :main
36
+ end
37
+
38
+ def prepare(tui)
39
+ load_locale
40
+
41
+ @tui = tui
42
+ @highlight_style = @tui.style(fg: :light_red, modifiers: [:underlined])
43
+ @hotkey_style = @tui.style(modifiers: [:bold, :underlined])
44
+ # eager load tabs
45
+ all
46
+ end
47
+
48
+ def run_loop
49
+ # Must log to a file, terminal is now controlled by Ratatui
50
+ config.logger = Logger.new("tui.log")
51
+
52
+ loop do
53
+ refresh_data if should_refresh?
54
+ render
55
+ break if handle_input == :quit
56
+ end
57
+ end
58
+
59
+ def render
60
+ track_fps do
61
+ if @showing == :main
62
+ @tui.draw do |frame|
63
+ main_area, controls_area = @tui.layout_split(
64
+ frame.area,
65
+ direction: :vertical,
66
+ constraints: [
67
+ @tui.constraint_fill(1),
68
+ @tui.constraint_length(5)
69
+ ]
70
+ )
71
+
72
+ # Split main area into tabs and content
73
+ tabs_area, content_area = @tui.layout_split(
74
+ main_area,
75
+ direction: :vertical,
76
+ constraints: [
77
+ @tui.constraint_length(3),
78
+ @tui.constraint_fill(1)
79
+ ]
80
+ )
81
+
82
+ all_tabs = all
83
+ tabs = @tui.tabs(
84
+ titles: all_tabs.map { |tab| t(tab.name) },
85
+ selected_index: all_tabs.index(current_tab),
86
+ block: @tui.block(title: " #{Sidekiq::NAME}", borders: [:all], title_style: @tui.style(fg: :light_red, modifiers: [:bold])),
87
+ divider: " | ",
88
+ highlight_style: @highlight_style,
89
+ style: @base_style
90
+ )
91
+ frame.render_widget(tabs, tabs_area)
92
+
93
+ render_content_area(frame, content_area)
94
+ render_controls(frame, controls_area)
95
+ end
96
+ end
97
+
98
+ if @showing == :help
99
+ @tui.draw do |frame|
100
+ main_area, controls_area = @tui.layout_split(
101
+ frame.area,
102
+ direction: :vertical,
103
+ constraints: [
104
+ @tui.constraint_fill(1),
105
+ @tui.constraint_length(4)
106
+ ]
107
+ )
108
+ content = @tui.block(
109
+ title: " #{Sidekiq::NAME} ",
110
+ borders: [:all],
111
+ title_style: @tui.style(fg: :light_red, modifiers: [:bold]),
112
+ children: [
113
+ # TODO convert to table
114
+ @tui.paragraph(
115
+ text: [
116
+ @tui.text_line(spans: ["Welcome to the Sidekiq Terminal UI"], alignment: :center),
117
+ @tui.text_line(spans: [
118
+ @tui.text_span(content: "Global hotkeys")
119
+ ]),
120
+ @tui.text_line(spans: []),
121
+ @tui.text_line(spans: [
122
+ @tui.text_span(content: "Esc", style: @hotkey_style),
123
+ @tui.text_span(content: ": Close this window")
124
+ ]),
125
+ @tui.text_line(spans: [
126
+ @tui.text_span(content: "←/→", style: @hotkey_style),
127
+ @tui.text_span(content: ": Move between tabs")
128
+ ]),
129
+ @tui.text_line(spans: [
130
+ @tui.text_span(content: "h/l", style: @hotkey_style),
131
+ @tui.text_span(content: ": Move to prev/next page of data")
132
+ ]),
133
+ @tui.text_line(spans: [
134
+ @tui.text_span(content: "j/k", style: @hotkey_style),
135
+ @tui.text_span(content: ": Move to prev/next row in current page")
136
+ ]),
137
+ @tui.text_line(spans: [
138
+ @tui.text_span(content: "x", style: @hotkey_style),
139
+ @tui.text_span(content: ": Select/deselect current row")
140
+ ]),
141
+ @tui.text_line(spans: [
142
+ @tui.text_span(content: "A", style: @hotkey_style),
143
+ @tui.text_span(content: ": Select/deselect All rows in current page")
144
+ ]),
145
+ @tui.text_line(spans: [
146
+ @tui.text_span(content: "q", style: @hotkey_style),
147
+ @tui.text_span(content: ": Quit")
148
+ ])
149
+ ]
150
+ )
151
+ ]
152
+ )
153
+ frame.render_widget(content, main_area)
154
+ controls = @tui.block(
155
+ title: t("Controls"),
156
+ borders: [:all],
157
+ children: [
158
+ @tui.paragraph(
159
+ text: [
160
+ @tui.text_line(spans: [
161
+ @tui.text_span(content: "Esc", style: @hotkey_style),
162
+ @tui.text_span(content: ": Close ")
163
+ ])
164
+ ]
165
+ )
166
+ ]
167
+ )
168
+ frame.render_widget(controls, controls_area)
169
+ end
170
+ end
171
+ end
172
+ end
173
+
174
+ def render_content_area(frame, content_area)
175
+ return render_error(frame, content_area, current_tab.error) if current_tab.error
176
+
177
+ current_tab.render(@tui, frame, content_area)
178
+ end
179
+
180
+ def render_controls(frame, area)
181
+ active_keys = current_tab.controls.filter { |hash| hash[:description] }
182
+
183
+ # Split controls into two lines, 8 is arbitrary
184
+ # TODO Dynamically split based on term width?
185
+ first = active_keys[...8]
186
+ lines = []
187
+ lines << @tui.text_line(spans: first.map { |hash|
188
+ [
189
+ @tui.text_span(content: hash[:display] || hash[:code], style: @hotkey_style),
190
+ @tui.text_span(content: ": #{t(hash[:description])} ")
191
+ ]
192
+ }.flatten)
193
+
194
+ last = active_keys[8...]
195
+ lines << if last && last.size > 0
196
+ @tui.text_line(spans: last.map { |hash|
197
+ [
198
+ @tui.text_span(content: hash[:display] || hash[:code], style: @hotkey_style),
199
+ @tui.text_span(content: ": #{t(hash[:description])} ")
200
+ ]
201
+ }.flatten)
202
+ else
203
+ @tui.text_line(spans: [])
204
+ end
205
+
206
+ footer = [
207
+ @tui.text_span(content: "Redis: #{redis_url} "),
208
+ @tui.text_span(content: "#{t("Now")}: #{Time.now.utc} "),
209
+ @tui.text_span(content: "#{t("Locale")}: #{@lang}")
210
+ ]
211
+
212
+ if current_tab.data[:filter]
213
+ @filter_style = @tui.style(fg: :white, bg: :dark_gray)
214
+ footer += [
215
+ @tui.text_span(content: " #{t("Filter")}: ", style: @filter_style),
216
+ @tui.text_span(content: current_tab.data[:filter], style: @filter_style),
217
+ @tui.text_span(content: "_", style: @tui.style(fg: :white, bg: :dark_gray, modifiers: [:slow_blink]))
218
+ ]
219
+ end
220
+ footer << @tui.text_span(content: " FPS: #{previous_fps}") if debugging?
221
+ lines << @tui.text_line(spans: footer)
222
+
223
+ controls = @tui.block(title: t("Controls"), borders: [:all],
224
+ children: [@tui.paragraph(text: lines)])
225
+ frame.render_widget(controls, area)
226
+ end
227
+
228
+ def handle_input
229
+ # We shouldn't need more than 10 FPS for a data-oriented app.
230
+ # This throttles down our CPU usage. Default is 60 FPS.
231
+ case @tui.poll_event(timeout: 0.1)
232
+ in {type: :key, code: "esc"} if @showing == :help
233
+ @showing = :main
234
+ in {type: :key, code: code} if current_tab.filtering? && code.length == 1
235
+ current_tab.append_to_filter(code)
236
+ current_tab.refresh_data
237
+ in {type: :key, code:, modifiers:}
238
+ control = current_tab.controls.find { |ctrl|
239
+ ctrl[:code] == code &&
240
+ (ctrl[:modifiers] || []) == (modifiers || [])
241
+ }
242
+ return unless control
243
+ control[:action].call(self, current_tab).tap {
244
+ refresh_data if control[:refresh]
245
+ }
246
+ else
247
+ # Ignore other events
248
+ end
249
+ rescue => ex
250
+ logger.error { [ex.message, ex.backtrace] }
251
+ end
252
+
253
+ def redis_url
254
+ Sidekiq.redis do |conn|
255
+ conn.config.server_url
256
+ end
257
+ rescue
258
+ "N/A"
259
+ end
260
+
261
+ def should_refresh?
262
+ Time.now - @last_refresh >= REFRESH_INTERVAL_SECONDS
263
+ end
264
+
265
+ def refresh_data
266
+ # logger.info GC.stat
267
+ current_tab.refresh_data
268
+ @last_refresh = Time.now
269
+ rescue => e
270
+ handle_exception(e)
271
+ current_tab.error = e
272
+ end
273
+
274
+ def render_error(frame, area, err)
275
+ header = [@tui.text_line(
276
+ spans: [@tui.text_span(content: err.message, style: @tui.style(modifiers: [:bold]))],
277
+ alignment: :center
278
+ )]
279
+ lines = Array(err.backtrace).map { |line| @tui.text_line(spans: [@tui.text_span(content: line)]) }
280
+
281
+ frame.render_widget(
282
+ @tui.paragraph(
283
+ text: header + lines,
284
+ alignment: :left,
285
+ block: @tui.block(title: t("Error"), borders: [:all], border_style: @tui.style(fg: :light_red))
286
+ ),
287
+ area
288
+ )
289
+ end
290
+
291
+ def show_help
292
+ @showing = :help
293
+ end
294
+
295
+ def all
296
+ @all ||= Tabs::All.map { |kls| kls.new(self) }
297
+ end
298
+
299
+ def current_tab
300
+ @current ||= @all.first
301
+ end
302
+
303
+ # Navigate tabs to the left or right.
304
+ # @param direction [Symbol] :left or :right
305
+ def navigate(direction)
306
+ index_change = (direction == :right) ? 1 : -1
307
+ @current = @all[(@all.index(current_tab) + index_change) % @all.size]
308
+ @current.reset_data
309
+ end
310
+
311
+ public def t(msg, options = nil)
312
+ string = @strings[msg] || msg
313
+ if options.nil?
314
+ string
315
+ else
316
+ string % options
317
+ end
318
+ end
319
+
320
+ def load_strings(lang)
321
+ {}.tap do |all|
322
+ find_locale_files(lang).each do |file|
323
+ strs = YAML.safe_load_file(file)
324
+ all.merge! strs[lang]
325
+ end
326
+ end
327
+ end
328
+
329
+ def locale_files
330
+ @@locale_files ||= LOCALE_DIRECTORIES.flat_map { |path|
331
+ Dir["#{path}/*.yml"]
332
+ }
333
+ end
334
+
335
+ def available_locales
336
+ @@available_locales ||= Set.new(locale_files.map { |path| File.basename(path, ".yml") })
337
+ end
338
+
339
+ def find_locale_files(lang)
340
+ locale_files.select { |file| file =~ /\/#{lang}\.yml$/ }
341
+ end
342
+
343
+ def load_locale
344
+ require "yaml"
345
+ lang = @lang.split(".").first # "en_US"
346
+ while lang.size > 0
347
+ hash = load_strings(lang)
348
+ if hash.size > 0
349
+ # found a working language dataset
350
+ @lang = lang
351
+ @strings = hash
352
+ Sidekiq.logger.debug { "using the #{lang} locale" }
353
+ break
354
+ end
355
+ # Try "en_US", "en_U", "en_", "en"
356
+ # It's ugly and bruteforce but it works
357
+ lang = lang[..-2]
358
+ end
359
+ end
360
+
361
+ def track_fps
362
+ # We hold two fps buckets: one for current second, one for previous second
363
+ idx = Time.now.to_i % 2
364
+ @fps[idx] += 1
365
+ yield
366
+ end
367
+
368
+ def previous_fps
369
+ curidx = Time.now.to_i % 2
370
+ prev = (curidx == 1) ? 0 : 1
371
+ if (val = @fps[prev]) != 0
372
+ @previous_fps = val
373
+ @fps[prev] = 0
374
+ end
375
+ @previous_fps
376
+ end
377
+
378
+ def debugging?
379
+ !!ENV["DEBUG"]
380
+ end
381
+ end
382
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ VERSION = "8.1.6"
5
+ MAJOR = 8
6
+
7
+ def self.gem_version
8
+ Gem::Version.new(VERSION)
9
+ end
10
+ end