sidekiq_queue_manager 1.0.0

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.
@@ -0,0 +1,208 @@
1
+ <!-- Theme Toggle Button -->
2
+ <button id="sqm-theme-toggle" class="sqm-theme-toggle" aria-label="Toggle theme" title="Toggle dark/light mode">
3
+ <svg class="sqm-theme-icon-light" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" style="width: 1rem; height: 1rem;">
4
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636a9 9 0 1011.314 0z" />
5
+ </svg>
6
+ <svg class="sqm-theme-icon-dark sqm-hidden" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" style="width: 1rem; height: 1rem;">
7
+ <path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z" />
8
+ </svg>
9
+ </button>
10
+
11
+ <!-- Global Statistics -->
12
+ <section class="sqm-stats-section" aria-label="Global Statistics">
13
+ <div class="sqm-stats-grid">
14
+ <div class="sqm-stat-card">
15
+ <div class="sqm-stat-label">Processed Jobs</div>
16
+ <div class="sqm-stat-value" id="sqm-processed">0</div>
17
+ </div>
18
+
19
+ <div class="sqm-stat-card">
20
+ <div class="sqm-stat-label">Failed Jobs</div>
21
+ <div class="sqm-stat-value" id="sqm-failed">0</div>
22
+ </div>
23
+
24
+ <div class="sqm-stat-card">
25
+ <div class="sqm-stat-label">Active Workers</div>
26
+ <div class="sqm-stat-value" id="sqm-busy">0</div>
27
+ </div>
28
+
29
+ <div class="sqm-stat-card">
30
+ <div class="sqm-stat-label">Enqueued Jobs</div>
31
+ <div class="sqm-stat-value" id="sqm-enqueued">0</div>
32
+ </div>
33
+ </div>
34
+ </section>
35
+
36
+ <!-- Controls Panel -->
37
+ <section class="sqm-controls-section" aria-label="Queue Controls">
38
+ <div class="sqm-controls">
39
+ <div class="sqm-controls-left">
40
+ <!-- Live Pull Toggle -->
41
+ <div class="sqm-live-pull-container">
42
+ <div class="sqm-live-pull-toggle">
43
+ <svg class="sqm-live-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
44
+ <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z" />
45
+ </svg>
46
+ <span class="sqm-toggle-text">Live Pull (5s)</span>
47
+ <div class="sqm-live-status">
48
+ <div class="sqm-status-dot"></div>
49
+ <span class="sqm-status-text">OFF</span>
50
+ </div>
51
+ </div>
52
+ <button id="sqm-live-toggle-btn" class="sqm-toggle-switch" data-enabled="false" aria-label="Toggle live updates">
53
+ <div class="sqm-toggle-thumb">
54
+ <svg class="sqm-toggle-icon sqm-toggle-icon-off" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
55
+ <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
56
+ </svg>
57
+ <svg class="sqm-toggle-icon sqm-toggle-icon-on" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
58
+ <path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" />
59
+ </svg>
60
+ </div>
61
+ </button>
62
+ </div>
63
+
64
+ <!-- Manual Refresh -->
65
+ <div class="sqm-refresh-container">
66
+ <button id="sqm-refresh-btn" class="sqm-refresh-button" title="Manual refresh" aria-label="Manually refresh queue data">
67
+ <svg class="sqm-refresh-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
68
+ <path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" />
69
+ </svg>
70
+ <span>Refresh</span>
71
+ </button>
72
+ </div>
73
+ </div>
74
+
75
+ <div class="sqm-controls-right">
76
+ <!-- Bulk Actions -->
77
+ <button id="sqm-pause-all-btn" class="sqm-btn sqm-btn-warning" title="Pause all non-critical queues">
78
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" style="width: 1rem; height: 1rem;">
79
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25v13.5m-7.5-13.5v13.5" />
80
+ </svg>
81
+ Pause All
82
+ </button>
83
+
84
+ <button id="sqm-resume-all-btn" class="sqm-btn sqm-btn-success" title="Resume all paused queues">
85
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" style="width: 1rem; height: 1rem;">
86
+ <path stroke-linecap="round" stroke-linejoin="round" d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.348a1.125 1.125 0 010 1.971l-11.54 6.347a1.125 1.125 0 01-1.667-.985V5.653z" />
87
+ </svg>
88
+ Resume All
89
+ </button>
90
+ </div>
91
+ </div>
92
+ </section>
93
+
94
+ <!-- Queue Statistics Summary -->
95
+ <section class="sqm-queue-summary" aria-label="Queue Summary">
96
+ <div class="sqm-stats-grid" style="grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));">
97
+ <div class="sqm-stat-card">
98
+ <div class="sqm-stat-label">Total Queues</div>
99
+ <div class="sqm-stat-value" id="sqm-total-queues">0</div>
100
+ </div>
101
+
102
+ <div class="sqm-stat-card">
103
+ <div class="sqm-stat-label">Paused Queues</div>
104
+ <div class="sqm-stat-value" id="sqm-paused-queues">0</div>
105
+ </div>
106
+
107
+ <div class="sqm-stat-card">
108
+ <div class="sqm-stat-label">Total Jobs</div>
109
+ <div class="sqm-stat-value" id="sqm-total-jobs">0</div>
110
+ </div>
111
+ </div>
112
+ </section>
113
+
114
+ <!-- Loading State -->
115
+ <div id="sqm-loading" class="sqm-loading">
116
+ <div class="sqm-loading-spinner" aria-hidden="true"></div>
117
+ <div class="sqm-loading-text">Loading queue data...</div>
118
+ </div>
119
+
120
+ <!-- Error State -->
121
+ <div id="sqm-error" class="sqm-error sqm-hidden">
122
+ <svg class="sqm-error-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
123
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" />
124
+ </svg>
125
+ <h2 class="sqm-error-title">Failed to Load Queue Data</h2>
126
+ <p class="sqm-error-message" id="sqm-error-message">An error occurred while loading the queue information.</p>
127
+ <button id="sqm-retry-btn" class="sqm-btn sqm-btn-primary">
128
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" style="width: 1rem; height: 1rem;">
129
+ <path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" />
130
+ </svg>
131
+ Retry
132
+ </button>
133
+ </div>
134
+
135
+ <!-- Main Content -->
136
+ <div id="sqm-content" class="sqm-hidden">
137
+ <!-- Queues Table -->
138
+ <section class="sqm-queues-section" aria-label="Queue Details">
139
+ <div class="sqm-table-container">
140
+ <table class="sqm-table" id="sqm-queues-table">
141
+ <thead>
142
+ <tr>
143
+ <th scope="col">Queue Name</th>
144
+ <th scope="col" style="text-align: right;">Size</th>
145
+ <th scope="col" style="text-align: right;">Workers/Limits</th>
146
+ <th scope="col" style="text-align: right;">Latency</th>
147
+ <th scope="col" style="text-align: right;">Actions</th>
148
+ </tr>
149
+ </thead>
150
+ <tbody id="sqm-table-body">
151
+ <!-- Queue rows will be inserted here by JavaScript -->
152
+ </tbody>
153
+ </table>
154
+ </div>
155
+ </section>
156
+ </div>
157
+
158
+ <!-- Empty State (when no queues) -->
159
+ <div id="sqm-empty-state" class="sqm-hidden" style="text-align: center; padding: 3rem 2rem; color: var(--sqm-muted-foreground);">
160
+ <svg style="width: 4rem; height: 4rem; margin-bottom: 1rem; color: var(--sqm-muted-foreground);" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
161
+ <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5M12 17.25h8.25M3.75 17.25h.01v.01h-.01v-.01ZM7.5 17.25h.01v.01H7.5v-.01ZM11.25 17.25h.01v.01h-.01v-.01Z" />
162
+ </svg>
163
+ <h2 style="font-size: 1.25rem; font-weight: 600; color: var(--sqm-muted-foreground); margin-bottom: 1rem;">No Queues Found</h2>
164
+ <p style="font-size: 0.875rem; margin-bottom: 2rem; line-height: 1.5;">
165
+ No Sidekiq queues were discovered. Make sure Sidekiq is properly configured and has processed jobs.
166
+ </p>
167
+ <button id="sqm-refresh-empty" class="sqm-btn sqm-btn-primary">
168
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" style="width: 1rem; height: 1rem;">
169
+ <path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" />
170
+ </svg>
171
+ Check Again
172
+ </button>
173
+ </div>
174
+
175
+ <!-- Accessibility Announcements -->
176
+ <div id="sqm-announcements" class="sqm-sr-only" aria-live="polite" aria-atomic="true"></div>
177
+
178
+ <script type="application/ld+json">
179
+ {
180
+ "@context": "https://schema.org",
181
+ "@type": "WebApplication",
182
+ "name": "Sidekiq Queue Manager",
183
+ "description": "Professional Sidekiq queue monitoring and management interface",
184
+ "applicationCategory": "DeveloperApplication",
185
+ "operatingSystem": "Web Browser",
186
+ "offers": {
187
+ "@type": "Offer",
188
+ "price": "0",
189
+ "priceCurrency": "USD"
190
+ }
191
+ }
192
+ </script>
193
+
194
+ <!-- Initial Data Script -->
195
+ <% if @initial_metrics %>
196
+ <script type="text/javascript">
197
+ // Provide initial data to reduce initial loading time
198
+ window.SidekiqQueueManagerInitialData = <%= @initial_metrics.to_json.html_safe %>;
199
+ </script>
200
+ <% end %>
201
+
202
+ <!-- Configuration Display (for debugging in development) -->
203
+ <% if Rails.env.development? %>
204
+ <details style="margin-top: 2rem; padding: 1rem; background: var(--sqm-card); border: 1px solid var(--sqm-border); border-radius: var(--sqm-radius); font-family: var(--sqm-font-mono); font-size: 0.75rem;">
205
+ <summary style="cursor: pointer; font-weight: 600; color: var(--sqm-muted-foreground);">🔧 Development Configuration</summary>
206
+ <pre style="margin-top: 1rem; white-space: pre-wrap; color: var(--sqm-muted-foreground); font-size: 0.6875rem;"><%= @configuration.to_yaml if @configuration %></pre>
207
+ </details>
208
+ <% end %>
data/config/routes.rb ADDED
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ SidekiqQueueManager::Engine.routes.draw do
4
+ # Asset serving routes (for apps without asset pipeline)
5
+ get '/assets/sidekiq_queue_manager/application.css', to: 'assets#css'
6
+ get '/assets/sidekiq_queue_manager/application.js', to: 'assets#js'
7
+ get '/assets/sidekiq_queue_manager/modals.css', to: 'assets#modals_css'
8
+ get '/stylesheets/sidekiq_queue_manager/application.css', to: 'assets#css'
9
+ get '/stylesheets/sidekiq_queue_manager/modals.css', to: 'assets#modals_css'
10
+ get '/javascripts/sidekiq_queue_manager/application.js', to: 'assets#js'
11
+ get '/sidekiq_queue_manager/application.css', to: 'assets#css'
12
+ get '/sidekiq_queue_manager/application.js', to: 'assets#js'
13
+ get '/sidekiq_queue_manager/modals.css', to: 'assets#modals_css'
14
+
15
+ # Main queue manager interface
16
+ root 'dashboard#index'
17
+
18
+ # Dashboard and metrics
19
+ get '/', to: 'dashboard#index', as: :dashboard
20
+ get '/metrics', to: 'dashboard#metrics', as: :metrics
21
+
22
+ # Bulk operations (collection actions)
23
+ post '/queues/pause_all', to: 'dashboard#pause_all', as: :pause_all_queues
24
+ post '/queues/resume_all', to: 'dashboard#resume_all', as: :resume_all_queues
25
+ get '/queues/summary', to: 'dashboard#summary', as: :queues_summary
26
+
27
+ # Individual queue operations
28
+ post '/queues/:name/pause', to: 'dashboard#pause_queue', as: :pause_queue
29
+ post '/queues/:name/resume', to: 'dashboard#resume_queue', as: :resume_queue
30
+ get '/queues/:name/status', to: 'dashboard#queue_status', as: :queue_status
31
+ get '/queues/:name/jobs', to: 'dashboard#jobs', as: :queue_jobs
32
+ delete '/queues/:name/delete_job', to: 'dashboard#delete_job', as: :delete_queue_job
33
+ post '/queues/:name/clear', to: 'dashboard#clear', as: :clear_queue
34
+
35
+ # Queue limits and configuration
36
+ post '/queues/:name/set_limit', to: 'dashboard#set_limit', as: :set_queue_limit
37
+ delete '/queues/:name/remove_limit', to: 'dashboard#remove_limit', as: :remove_queue_limit
38
+ post '/queues/:name/set_process_limit', to: 'dashboard#set_process_limit', as: :set_queue_process_limit
39
+ delete '/queues/:name/remove_process_limit', to: 'dashboard#remove_process_limit', as: :remove_queue_process_limit
40
+
41
+ # Advanced queue operations
42
+ post '/queues/:name/block', to: 'dashboard#block', as: :block_queue
43
+ post '/queues/:name/unblock', to: 'dashboard#unblock', as: :unblock_queue
44
+
45
+ # Real-time updates via Server-Sent Events
46
+ get '/live', to: 'dashboard#live_stream', as: :live_stream
47
+ end
48
+
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqQueueManager
4
+ # Configuration class for customizing SidekiqQueueManager behavior
5
+ # Focus on essential user-configurable options only
6
+ class Configuration
7
+ # Essential user-configurable options
8
+ attr_accessor :authentication_method, :critical_queues, :theme
9
+ attr_accessor :basic_auth_enabled, :basic_auth_username, :basic_auth_password
10
+
11
+ # Advanced options (rarely changed, but available)
12
+ attr_accessor :refresh_interval, :enable_logging, :enable_caching, :default_queue_priorities
13
+
14
+ # Internal options (sensible defaults, not typically user-configured)
15
+ attr_reader :redis_key_prefix, :redis_timeout, :log_level, :enable_csp, :cache_ttl
16
+
17
+ def initialize
18
+ # Essential defaults (what most users care about)
19
+ @authentication_method = nil # Custom auth method (optional)
20
+ @critical_queues = [] # No protected queues by default
21
+ @theme = 'auto' # Auto light/dark theme
22
+
23
+ # Basic HTTP Authentication (professional standard - explicit configuration required)
24
+ @basic_auth_enabled = true # Secure by default like Sidekiq Web UI
25
+ @basic_auth_username = 'admin' # Standard admin username
26
+ @basic_auth_password = nil # MUST be explicitly set by user
27
+
28
+ # Advanced defaults (rarely changed)
29
+ @refresh_interval = 5000 # 5 second UI refresh
30
+ @enable_logging = true # Log queue operations
31
+ @enable_caching = true # Cache queue stats for performance
32
+ @default_queue_priorities = {} # User-defined queue priorities
33
+
34
+ # Internal defaults (best practices, rarely modified)
35
+ @redis_key_prefix = 'sidekiq_queue_manager' # Namespace Redis keys
36
+ @redis_timeout = 5 # 5 second Redis timeout
37
+ @log_level = :info # Standard logging level
38
+ @enable_csp = true # Security headers enabled
39
+ @cache_ttl = 300 # 5 minute cache TTL
40
+ end
41
+
42
+ # Validate essential user-provided configuration
43
+ def validate!
44
+ validate_basic_settings!
45
+ validate_authentication!
46
+ self # Return self for method chaining (Ruby idiom)
47
+ end
48
+
49
+ # Check if configuration is valid without raising (Ruby's truthiness approach)
50
+ def valid?
51
+ validate!
52
+ true
53
+ rescue ConfigurationError
54
+ false
55
+ end
56
+
57
+ # Export configuration as hash (for JavaScript and internal use)
58
+ def to_h
59
+ {
60
+ # Essential user options
61
+ authentication_method: @authentication_method,
62
+ critical_queues: @critical_queues,
63
+ theme: @theme,
64
+ basic_auth_enabled: @basic_auth_enabled,
65
+ basic_auth_username: @basic_auth_username,
66
+ # NOTE: basic_auth_password excluded for security
67
+
68
+ # Advanced options
69
+ refresh_interval: @refresh_interval,
70
+ enable_logging: @enable_logging,
71
+ enable_caching: @enable_caching,
72
+ default_queue_priorities: @default_queue_priorities,
73
+
74
+ # Internal options (included for completeness)
75
+ redis_key_prefix: @redis_key_prefix,
76
+ redis_timeout: @redis_timeout,
77
+ log_level: @log_level,
78
+ enable_csp: @enable_csp,
79
+ cache_ttl: @cache_ttl
80
+ }
81
+ end
82
+
83
+ # Essential configuration summary (for debugging) - more Ruby-like
84
+ def summary
85
+ auth_status = if basic_auth_enabled?
86
+ "basic auth (#{basic_auth_username})"
87
+ elsif authentication_method
88
+ "custom (#{authentication_method})"
89
+ else
90
+ 'disabled'
91
+ end
92
+
93
+ {
94
+ critical_queues: critical_queues.presence || 'none',
95
+ theme: theme,
96
+ authentication: auth_status,
97
+ caching: enable_caching? ? 'enabled' : 'disabled'
98
+ }
99
+ end
100
+
101
+ # Predicate methods (Ruby convention for boolean checks)
102
+ def basic_auth_enabled? = @basic_auth_enabled
103
+ def enable_logging? = @enable_logging
104
+ def enable_caching? = @enable_caching
105
+ def enable_csp? = @enable_csp
106
+ def custom_authentication? = @authentication_method.present?
107
+
108
+ # Check if queue is critical (more Ruby-like with inclusion check)
109
+ def critical_queue?(queue_name)
110
+ critical_queues.include?(queue_name.to_s)
111
+ end
112
+
113
+ # Get queue priority with Ruby's Hash#fetch for elegant defaults
114
+ def queue_priority(queue_name)
115
+ default_queue_priorities.fetch(queue_name.to_s, 1)
116
+ end
117
+
118
+ private
119
+
120
+ # Validate basic configuration settings
121
+ def validate_basic_settings!
122
+ # Use Ruby's case statements for more readable validation
123
+ case refresh_interval
124
+ when Integer
125
+ raise ConfigurationError, 'refresh_interval must be positive' unless refresh_interval.positive?
126
+ else
127
+ raise ConfigurationError, 'refresh_interval must be a positive Integer'
128
+ end
129
+
130
+ raise ConfigurationError, 'critical_queues must be an Array' unless critical_queues.is_a?(Array)
131
+
132
+ raise ConfigurationError, 'theme must be one of: auto, light, dark' unless %w[auto light dark].include?(theme)
133
+
134
+ self
135
+ end
136
+
137
+ # Validate authentication configuration (explicit, professional standard)
138
+ def validate_authentication!
139
+ # Only validate if basic auth is enabled (explicit choice)
140
+ return self unless basic_auth_enabled?
141
+
142
+ # Use Ruby's presence for more elegant nil/empty checks
143
+ if basic_auth_password.blank?
144
+ raise ConfigurationError, 'basic_auth_password must be set when basic_auth_enabled is true. ' \
145
+ 'Set config.basic_auth_password in your initializer or disable with ' \
146
+ 'basic_auth_enabled = false'
147
+ end
148
+
149
+ if basic_auth_username.blank?
150
+ raise ConfigurationError, 'basic_auth_username cannot be empty when basic_auth_enabled is true'
151
+ end
152
+
153
+ self
154
+ end
155
+ end
156
+ end
157
+
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/engine'
4
+
5
+ module SidekiqQueueManager
6
+ # Rails Engine for integrating SidekiqQueueManager with Rails applications
7
+ #
8
+ # This engine automatically:
9
+ # - Adds gem routes to the Rails application
10
+ # - Registers assets (CSS, JavaScript) with the asset pipeline
11
+ # - Sets up middleware and initializers
12
+ # - Provides mountable functionality
13
+ #
14
+ class Engine < ::Rails::Engine
15
+ isolate_namespace SidekiqQueueManager
16
+
17
+ # Engine configuration using Ruby's expressive syntax
18
+ config.generators do |g|
19
+ g.test_framework :rspec
20
+ g.fixture_replacement :factory_bot
21
+ g.assets false
22
+ g.helper false
23
+ end
24
+
25
+ # Initialize the engine with Ruby's elegant error handling
26
+ initializer 'sidekiq_queue_manager.assets' do |app|
27
+ configure_assets(app).tap do |configured|
28
+ Rails.logger.info asset_configuration_message(configured)
29
+ end
30
+ end
31
+
32
+ # Validate dependencies and configuration on engine load
33
+ initializer 'sidekiq_queue_manager.validate_dependencies_and_config' do
34
+ validate_and_configure!
35
+ rescue SidekiqQueueManager::ConfigurationError => e
36
+ handle_configuration_error(e)
37
+ rescue StandardError => e
38
+ handle_dependency_error(e)
39
+ end
40
+
41
+ initializer 'sidekiq_queue_manager.configure_limit_fetch', before: 'sidekiq' do
42
+ configure_sidekiq_limit_fetch
43
+ end
44
+
45
+ # Set up middleware for security headers and logging
46
+ initializer 'sidekiq_queue_manager.middleware' do |app|
47
+ configure_middleware(app) if SidekiqQueueManager.enable_logging?
48
+ rescue StandardError => e
49
+ Rails.logger.warn "[SidekiqQueueManager] Could not configure middleware: #{e.message}"
50
+ end
51
+
52
+ # Configure eager loading paths
53
+ config.eager_load_paths << File.expand_path('../../app', __dir__)
54
+
55
+ private
56
+
57
+ # Asset configuration with Ruby's functional approach
58
+ def configure_assets(app)
59
+ return false unless asset_pipeline_available?(app)
60
+
61
+ configure_asset_pipeline(app)
62
+ rescue StandardError => e
63
+ Rails.logger.warn "[SidekiqQueueManager] Asset pipeline configuration failed: #{e.message}"
64
+ false
65
+ end
66
+
67
+ def asset_pipeline_available?(app)
68
+ app.config.respond_to?(:assets) && defined?(Sprockets)
69
+ end
70
+
71
+ def configure_asset_pipeline(app)
72
+ app.config.assets.precompile += %w[
73
+ sidekiq_queue_manager/application.js
74
+ sidekiq_queue_manager/application.css
75
+ sidekiq_queue_manager/modals.css
76
+ ]
77
+ true
78
+ end
79
+
80
+ def asset_configuration_message(configured)
81
+ if configured
82
+ '[SidekiqQueueManager] Assets registered with asset pipeline'
83
+ else
84
+ '[SidekiqQueueManager] Asset pipeline not available - using direct asset serving via /assets/* routes'
85
+ end
86
+ end
87
+
88
+ # Dependency validation with Ruby's case pattern matching
89
+ def validate_and_configure!
90
+ SidekiqQueueManager.validate_dependencies!
91
+ SidekiqQueueManager.configuration.validate!
92
+ Rails.logger.info '[SidekiqQueueManager] Configuration validated successfully'
93
+ end
94
+
95
+ def handle_configuration_error(error)
96
+ Rails.logger.error "[SidekiqQueueManager] Configuration error: #{error.message}"
97
+ Rails.logger.error '[SidekiqQueueManager] Please check your config/initializers/sidekiq_queue_manager.rb'
98
+ # Don't raise in production to prevent app startup failures, but log clearly
99
+ raise error unless Rails.env.production?
100
+ end
101
+
102
+ def handle_dependency_error(error)
103
+ Rails.logger.error "[SidekiqQueueManager] Dependency validation failed: #{error.message}"
104
+ # Don't raise in production to prevent app startup failures
105
+ raise error unless Rails.env.production?
106
+ end
107
+
108
+ # Sidekiq limit fetch configuration using Ruby's method chaining
109
+ def configure_sidekiq_limit_fetch
110
+ return configure_existing_limit_fetch if sidekiq_limit_fetch_available?
111
+
112
+ attempt_to_load_limit_fetch
113
+ end
114
+
115
+ def sidekiq_limit_fetch_available?
116
+ defined?(Sidekiq::LimitFetch)
117
+ end
118
+
119
+ def configure_existing_limit_fetch
120
+ configure_sidekiq_server_options
121
+ Rails.logger.info '[SidekiqQueueManager] Configured sidekiq-limit_fetch for advanced queue management'
122
+ end
123
+
124
+ def attempt_to_load_limit_fetch
125
+ require 'sidekiq-limit_fetch'
126
+
127
+ if defined?(Sidekiq::LimitFetch)
128
+ configure_sidekiq_server_options
129
+ Rails.logger.info '[SidekiqQueueManager] Loaded and configured sidekiq-limit_fetch'
130
+ end
131
+ rescue LoadError => e
132
+ Rails.logger.warn "[SidekiqQueueManager] sidekiq-limit_fetch not available: #{e.message}"
133
+ Rails.logger.warn '[SidekiqQueueManager] Some advanced queue features may not work'
134
+ end
135
+
136
+ def configure_sidekiq_server_options
137
+ Sidekiq.configure_server do |config|
138
+ config.options[:fetch] = Sidekiq::LimitFetch
139
+ end
140
+ end
141
+
142
+ # Middleware configuration using Ruby's guard clauses
143
+ def configure_middleware(app)
144
+ return unless SidekiqQueueManager.configuration.enable_logging?
145
+
146
+ app.middleware.use SidekiqQueueManager::LoggingMiddleware
147
+ Rails.logger.info '[SidekiqQueueManager] Logging middleware enabled'
148
+ end
149
+ end
150
+ end
151
+
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqQueueManager
4
+ # Simple logging middleware for SidekiqQueueManager requests
5
+ class LoggingMiddleware
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ # Only log requests to our gem's paths
12
+ if env['PATH_INFO'].start_with?('/sidekiq_dashboard')
13
+ start_time = Time.current
14
+
15
+ status, headers, body = @app.call(env)
16
+
17
+ end_time = Time.current
18
+ duration = (end_time - start_time) * 1000 # milliseconds
19
+
20
+ Rails.logger.info "[SidekiqQueueManager] #{env['REQUEST_METHOD']} #{env['PATH_INFO']} - #{status} (#{duration.round(2)}ms)"
21
+
22
+ [status, headers, body]
23
+ else
24
+ @app.call(env)
25
+ end
26
+ end
27
+ end
28
+ end
29
+
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqQueueManager
4
+ # Semantic versioning for the Sidekiq Queue Manager gem
5
+ #
6
+ # Version format: MAJOR.MINOR.PATCH
7
+ # - MAJOR: Incompatible API changes
8
+ # - MINOR: Add functionality in backward compatible manner
9
+ # - PATCH: Backward compatible bug fixes
10
+ VERSION = '1.0.0'
11
+ end
12
+