modern_queue_dashboard 0.3.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 (34) hide show
  1. checksums.yaml +7 -0
  2. data/.npmignore +25 -0
  3. data/.npmrc +2 -0
  4. data/.rubocop.yml +18 -0
  5. data/CHANGELOG.md +37 -0
  6. data/CODE_OF_CONDUCT.md +16 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +168 -0
  9. data/Rakefile +70 -0
  10. data/app/assets/builds/modern_queue_dashboard.css +1 -0
  11. data/app/assets/builds/modern_queue_dashboard.js +19 -0
  12. data/app/assets/javascripts/modern_queue_dashboard.js +18 -0
  13. data/app/assets/stylesheets/modern_queue_dashboard.css +8 -0
  14. data/app/controllers/modern_queue_dashboard/application_controller.rb +7 -0
  15. data/app/controllers/modern_queue_dashboard/dashboard_controller.rb +10 -0
  16. data/app/controllers/modern_queue_dashboard/queues_controller.rb +19 -0
  17. data/app/models/modern_queue_dashboard/metrics.rb +49 -0
  18. data/app/models/modern_queue_dashboard/queue_summary.rb +52 -0
  19. data/app/views/layouts/modern_queue_dashboard/application.html.erb +15 -0
  20. data/app/views/modern_queue_dashboard/dashboard/index.html.erb +51 -0
  21. data/app/views/modern_queue_dashboard/queues/index.html.erb +35 -0
  22. data/app/views/modern_queue_dashboard/queues/show.html.erb +51 -0
  23. data/config/routes.rb +7 -0
  24. data/docs/PLAN.md +201 -0
  25. data/lib/modern_queue_dashboard/engine.rb +24 -0
  26. data/lib/modern_queue_dashboard/metrics.rb +43 -0
  27. data/lib/modern_queue_dashboard/queue_summary.rb +38 -0
  28. data/lib/modern_queue_dashboard/version.rb +5 -0
  29. data/lib/modern_queue_dashboard.rb +35 -0
  30. data/package-lock.json +1506 -0
  31. data/package.json +17 -0
  32. data/sig/modern_queue_dashboard.rbs +4 -0
  33. data/tailwind.config.js +11 -0
  34. metadata +196 -0
@@ -0,0 +1,35 @@
1
+ <div class="container mx-auto p-6 space-y-6">
2
+ <h1 class="text-3xl font-semibold">Modern Queue Dashboard</h1>
3
+
4
+ <h2 class="text-2xl font-semibold">Queues</h2>
5
+
6
+ <!-- Queue Table -->
7
+ <div class="bg-white shadow rounded">
8
+ <table class="min-w-full divide-y divide-gray-200">
9
+ <thead class="bg-gray-50">
10
+ <tr>
11
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Queue</th>
12
+ <th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Pending</th>
13
+ <th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Scheduled</th>
14
+ <th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Running</th>
15
+ <th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Failed</th>
16
+ <th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Latency</th>
17
+ </tr>
18
+ </thead>
19
+ <tbody class="bg-white divide-y divide-gray-200">
20
+ <% @queues.each do |queue| %>
21
+ <tr>
22
+ <td class="px-6 py-4 whitespace-nowrap font-medium text-sky-500">
23
+ <%= link_to queue.name, queue_path(queue.name), data: { turbo_frame: "_top" } %>
24
+ </td>
25
+ <td class="px-6 py-4 text-right"><%= queue.pending %></td>
26
+ <td class="px-6 py-4 text-right"><%= queue.scheduled %></td>
27
+ <td class="px-6 py-4 text-right"><%= queue.running %></td>
28
+ <td class="px-6 py-4 text-right"><%= queue.failed %></td>
29
+ <td class="px-6 py-4 text-right"><%= queue.latency %> ms</td>
30
+ </tr>
31
+ <% end %>
32
+ </tbody>
33
+ </table>
34
+ </div>
35
+ </div>
@@ -0,0 +1,51 @@
1
+ <div class="container mx-auto p-6 space-y-6">
2
+ <h1 class="text-3xl font-semibold">Modern Queue Dashboard</h1>
3
+
4
+ <div class="flex items-center space-x-2 mb-4">
5
+ <%= link_to "← Back to Queues", queues_path, class: "text-sky-500" %>
6
+ </div>
7
+
8
+ <h2 class="text-2xl font-semibold">Queue: <%= @queue_name %></h2>
9
+
10
+ <% if @queue %>
11
+ <!-- Queue Stats -->
12
+ <div class="grid grid-cols-2 md:grid-cols-4 gap-4">
13
+ <div class="bg-white shadow-sm rounded p-4">
14
+ <p class="text-sm text-gray-500">Pending Jobs</p>
15
+ <p class="text-2xl font-bold text-sky-500"><%= @queue.pending %></p>
16
+ </div>
17
+ <div class="bg-white shadow-sm rounded p-4">
18
+ <p class="text-sm text-gray-500">Scheduled Jobs</p>
19
+ <p class="text-2xl font-bold text-sky-500"><%= @queue.scheduled %></p>
20
+ </div>
21
+ <div class="bg-white shadow-sm rounded p-4">
22
+ <p class="text-sm text-gray-500">Running Jobs</p>
23
+ <p class="text-2xl font-bold text-sky-500"><%= @queue.running %></p>
24
+ </div>
25
+ <div class="bg-white shadow-sm rounded p-4">
26
+ <p class="text-sm text-gray-500">Failed Jobs</p>
27
+ <p class="text-2xl font-bold text-sky-500"><%= @queue.failed %></p>
28
+ </div>
29
+ </div>
30
+
31
+ <!-- Queue Details -->
32
+ <div class="bg-white shadow rounded p-6">
33
+ <h3 class="text-xl font-semibold mb-4">Queue Details</h3>
34
+
35
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
36
+ <div>
37
+ <p class="text-sm text-gray-500">Queue Name</p>
38
+ <p class="font-semibold"><%= @queue.name %></p>
39
+ </div>
40
+ <div>
41
+ <p class="text-sm text-gray-500">Average Latency</p>
42
+ <p class="font-semibold"><%= @queue.latency %> ms</p>
43
+ </div>
44
+ </div>
45
+ </div>
46
+ <% else %>
47
+ <div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
48
+ Queue not found.
49
+ </div>
50
+ <% end %>
51
+ </div>
data/config/routes.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ ModernQueueDashboard::Engine.routes.draw do
4
+ root to: "dashboard#index"
5
+
6
+ resources :queues, only: %i[index show]
7
+ end
data/docs/PLAN.md ADDED
@@ -0,0 +1,201 @@
1
+ # Modern Queue Dashboard – Implementation Plan
2
+
3
+ ## 1. Purpose
4
+ Create a mountable Rails engine (`modern_queue_dashboard`) that provides a Hotwire-powered, Tailwind-styled dashboard for monitoring Solid Queue job activity. The gem should be install-and-mount with zero configuration, MIT-licensed, Rails 8+ compatible.
5
+
6
+ ---
7
+
8
+ ## 2. Feature Scope
9
+ 1. **High-level metrics**
10
+ * Total jobs per state: pending, scheduled, running, completed, discarded, failed.
11
+ * Per-queue counts and latency (oldest `run_at` vs now).
12
+ 2. **Queue list** – table of all queues with counts & oldest job age.
13
+ 3. **Job list** – paginated view for a selected queue or global list.
14
+ 4. **Job detail** – arguments, id, state transitions, timestamps, error details for failed jobs.
15
+ 5. **Semi-real-time updates** – Turbo Stream polling every *n* seconds (configurable).
16
+ 6. **No job actions** (read-only).
17
+ 7. **Authentication left to host app** – README shows Devise/warden examples.
18
+
19
+ ---
20
+
21
+ ## 3. Data Sources & Queries
22
+ Solid Queue tables (≥ v1.1):
23
+ * `solid_queue_jobs`
24
+ * `solid_queue_executions`
25
+ * `solid_queue_queues`
26
+ * `solid_queue_failed_jobs`
27
+ * `solid_queue_scheduled_jobs`
28
+ * `solid_queue_workers` (for running jobs)
29
+
30
+ Create PORO query objects under `app/models/modern_queue_dashboard/` to encapsulate:
31
+ * `Metrics.summary` – returns counts/latency.
32
+ * `Queue.with_stats` – returns per-queue stats.
33
+ * `JobFinder` – fetches jobs by queue / state with eager-loaded execution & failure.
34
+ * `JobPresenter` – formats metadata for the UI.
35
+
36
+ ---
37
+
38
+ ## 4. Gem/Engine Skeleton
39
+ ```
40
+ modern_queue_dashboard/
41
+ ├── app/
42
+ │ ├── controllers/modern_queue_dashboard/
43
+ │ ├── views/modern_queue_dashboard/
44
+ │ ├── models/modern_queue_dashboard/
45
+ │ ├── components/modern_queue_dashboard/ (ViewComponent or Phlex?)
46
+ │ ├── assets/
47
+ │ │ ├── stylesheets/modern_queue_dashboard.css
48
+ │ │ └── javascript/modern_queue_dashboard.js
49
+ │ └── helpers/modern_queue_dashboard/
50
+ ├── config/
51
+ │ ├── routes.rb (Engine)
52
+ │ └── initializers/assets.rb (precompile dashboard assets)
53
+ ├── lib/
54
+ │ ├── modern_queue_dashboard.rb (engine loader)
55
+ │ ├── modern_queue_dashboard/engine.rb
56
+ │ └── modern_queue_dashboard/version.rb
57
+ ├── test/
58
+ │ ├── dummy/ (rails new --minimal --edge)
59
+ │ └── ... unit tests (Minitest)
60
+ ├── Rakefile
61
+ └── modern_queue_dashboard.gemspec
62
+ ```
63
+
64
+ ---
65
+
66
+ ## 5. Dependencies
67
+ * `rails ">= 8.0.0"`
68
+ * `turbo-rails`, `stimulus-rails` (already bundled with Rails 8)
69
+ * `tailwindcss-rails`
70
+ * `view_component` (optional – decide in §8)
71
+ * `solid_queue` (runtime, same version constraint as host)
72
+ * Development: `standard`/`rubocop`, `minitest-reporters`, `pry`.
73
+
74
+ ---
75
+
76
+ ## 6. Routes
77
+ Within the engine (`config/routes.rb`):
78
+ ```ruby
79
+ ModernQueueDashboard::Engine.routes.draw do
80
+ root to: "dashboard#index"
81
+ resources :queues, only: [:index, :show] do
82
+ resources :jobs, only: [:index]
83
+ end
84
+ resources :jobs, only: [:show]
85
+ get "metrics", to: "metrics#show" # JSON for Turbo polling
86
+ end
87
+ ```
88
+
89
+ ---
90
+
91
+ ## 7. Controllers
92
+ * `DashboardController#index` – loads Metrics & queues.
93
+ * `QueuesController#index` – list with stats.
94
+ * `QueuesController#show` – queue overview & first page of jobs.
95
+ * `JobsController#index` – filtered list by queue/state.
96
+ * `JobsController#show` – job detail.
97
+ * `MetricsController#show` – returns partial/stream fragment for live refresh.
98
+
99
+ All inherit from `ModernQueueDashboard::ApplicationController`, which defines layout & before_actions.
100
+
101
+ ---
102
+
103
+ ## 8. Views & Components
104
+ * Use **Turbo Frames** to update sections without full-page reload.
105
+ * Tailwind classes for styling; minimal custom CSS.
106
+ * Consider **ViewComponent** (optional) for reusable pieces (metric card, table row).
107
+
108
+ Pages:
109
+ 1. **Dashboard** – metric cards + table of top 10 queues.
110
+ 2. **Queue show** – header (stats) + jobs table.
111
+ 3. **Job show** – read-only details with collapsible JSON args & error.
112
+
113
+ ---
114
+
115
+ ## 9. Hotwire Behaviour
116
+ * Stimulus controller `refresh_controller.js` fetches `/metrics` every N seconds (default 5) and updates via Turbo.
117
+ * Jobs/queue pages optionally auto-refresh lists.
118
+ * Configurable via `ModernQueueDashboard.configuration.refresh_interval`.
119
+
120
+ ---
121
+
122
+ ## 10. Styling / Assets
123
+ * `tailwindcss-rails` build task generates `modern_queue_dashboard.css`.
124
+ * Color palette close to Active Storage Dashboard (gray/indigo).
125
+ * Provide a helper to override Tailwind config via initializer if host app precompiles their own CSS.
126
+ * Ship minified CSS/JS in the gem (`app/assets/builds`).
127
+
128
+ ---
129
+
130
+ ## 11. Configuration API
131
+ ```ruby
132
+ # config/initializers/modern_queue_dashboard.rb
133
+ ModernQueueDashboard.configure do |config|
134
+ config.refresh_interval = 5
135
+ config.time_zone = "UTC"
136
+ end
137
+ ```
138
+
139
+ Defaults live in `ModernQueueDashboard::Configuration`.
140
+
141
+ ---
142
+
143
+ ## 12. Authentication Guidance (README)
144
+ ```ruby
145
+ # config/routes.rb in host app
146
+ authenticate :user, -> { _1.admin? } do
147
+ mount ModernQueueDashboard::Engine, at: "/modern-queue-dashboard"
148
+ end
149
+ ```
150
+ Or rack basic auth example.
151
+
152
+ ---
153
+
154
+ ## 13. Testing Strategy (Minitest)
155
+ * **Models/Queries**: unit tests.
156
+ * **Controllers**: request specs.
157
+ * **Components**: view/component tests.
158
+ * Dummy app under `test/dummy` for engine isolation.
159
+ * No system tests; rely on controller tests + minimal Turbo response assertions.
160
+
161
+ ---
162
+
163
+ ## 14. CI
164
+ * GitHub Actions: run `bundle exec rake test`, Rubocop, `rails test:dummy` against sqlite3.
165
+ * Matrix for latest stable Ruby & JRuby.
166
+
167
+ ---
168
+
169
+ ## 15. Release & Versioning
170
+ * Semantic versioning starting at `0.1.0`.
171
+ * Publish to RubyGems when dashboard feature-complete.
172
+
173
+ ---
174
+
175
+ ## 16. Future Enhancements (out-of-scope for 0.1.0)
176
+ * Job actions (retry, discard, delete).
177
+ * Prometheus/JSON metrics.
178
+ * Live WebSocket updates instead of polling.
179
+ * Scheduler/Recurring task visualizer.
180
+ * Dark mode theme.
181
+ * System tests (Turbo-flavored).
182
+
183
+ ---
184
+
185
+ ## 17. Implementation Roadmap
186
+ 1. **Bootstrap gem** – `bundle gem modern_queue_dashboard --mit --coc --test=minitest`.
187
+ 2. **Create engine, routes, layout**.
188
+ 3. **Add Tailwind & Turbo assets**.
189
+ 4. **Implement Metrics query object**.
190
+ 5. **Build DashboardController & dashboard view**.
191
+ 6. **Queue stats & pages**.
192
+ 7. **Job finder & details page**.
193
+ 8. **Stimulus polling**.
194
+ 9. **Configuration DSL**.
195
+ 10. **Dummy app + tests**.
196
+ 11. **README & screenshots**.
197
+ 12. **Precompile assets & ship builds**.
198
+ 13. **Set up CI**.
199
+ 14. **Tag `v0.1.0` and release.**
200
+
201
+ ---
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/engine"
4
+ require "tailwindcss-rails" if defined?(TailwindCss)
5
+
6
+ module ModernQueueDashboard
7
+ class Engine < ::Rails::Engine
8
+ isolate_namespace ModernQueueDashboard
9
+
10
+ # Precompile CSS & JS builds that ship with the gem
11
+ initializer "modern_queue_dashboard.assets" do |app|
12
+ # Check if the app uses the asset pipeline that requires explicit precompilation
13
+ if app.config.respond_to?(:assets) && app.config.assets.respond_to?(:precompile)
14
+ app.config.assets.precompile += %w[modern_queue_dashboard.css modern_queue_dashboard.js]
15
+ end
16
+
17
+ # Configure paths for Tailwind CSS if available
18
+ if defined?(TailwindCss) && app.config.respond_to?(:tailwindcss)
19
+ app.config.tailwindcss.content_root = root.join("app").to_s
20
+ app.config.tailwindcss.content_includes = [ "./views/**/*.html.erb", "./helpers/**/*.rb" ]
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ModernQueueDashboard
4
+ Metric = Struct.new(:key, :label, :value, keyword_init: true)
5
+
6
+ class Metrics
7
+ def self.summary
8
+ [
9
+ Metric.new(key: :pending, label: "Pending", value: count_pending_jobs),
10
+ Metric.new(key: :scheduled, label: "Scheduled", value: count_scheduled_jobs),
11
+ Metric.new(key: :running, label: "Running", value: count_running_jobs),
12
+ Metric.new(key: :failed, label: "Failed", value: count_failed_jobs),
13
+ Metric.new(key: :completed, label: "Completed", value: count_completed_jobs),
14
+ Metric.new(key: :latency, label: "Latency", value: calculate_latency)
15
+ ]
16
+ end
17
+
18
+ # These methods are placeholders and will need to be implemented using the SolidQueue models
19
+ def self.count_pending_jobs
20
+ 0
21
+ end
22
+
23
+ def self.count_scheduled_jobs
24
+ 0
25
+ end
26
+
27
+ def self.count_running_jobs
28
+ 0
29
+ end
30
+
31
+ def self.count_failed_jobs
32
+ 0
33
+ end
34
+
35
+ def self.count_completed_jobs
36
+ 0
37
+ end
38
+
39
+ def self.calculate_latency
40
+ 0
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ModernQueueDashboard
4
+ QueueStat = Struct.new(:name, :pending, :scheduled, :running, :failed, :latency, keyword_init: true)
5
+
6
+ class QueueSummary
7
+ def self.with_stats
8
+ # This is a placeholder and will need actual implementation using SolidQueue
9
+ results = [
10
+ QueueStat.new(
11
+ name: "default",
12
+ pending: 0,
13
+ scheduled: 0,
14
+ running: 0,
15
+ failed: 0,
16
+ latency: 0
17
+ )
18
+ ]
19
+ QueueStatCollection.new(results)
20
+ end
21
+ end
22
+
23
+ class QueueStatCollection
24
+ include Enumerable
25
+
26
+ def initialize(stats)
27
+ @stats = stats
28
+ end
29
+
30
+ def each(&)
31
+ @stats.each(&)
32
+ end
33
+
34
+ def limit(num)
35
+ QueueStatCollection.new(@stats.take(num))
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ModernQueueDashboard
4
+ VERSION = "0.3.1"
5
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails"
4
+ require "action_controller/railtie"
5
+ require "active_support/dependencies"
6
+ require_relative "modern_queue_dashboard/version"
7
+ require_relative "modern_queue_dashboard/engine"
8
+ require_relative "modern_queue_dashboard/metrics"
9
+ require_relative "modern_queue_dashboard/queue_summary"
10
+
11
+ module ModernQueueDashboard
12
+ class Error < StandardError; end
13
+
14
+ class Configuration
15
+ attr_accessor :refresh_interval, :time_zone
16
+
17
+ def initialize
18
+ @refresh_interval = 5
19
+ @time_zone = "UTC"
20
+ end
21
+ end
22
+
23
+ class << self
24
+ attr_writer :configuration
25
+
26
+ def configuration
27
+ @configuration ||= Configuration.new
28
+ end
29
+
30
+ def configure
31
+ yield(configuration)
32
+ end
33
+ end
34
+ # Your code goes here...
35
+ end