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.
- checksums.yaml +7 -0
- data/.npmignore +25 -0
- data/.npmrc +2 -0
- data/.rubocop.yml +18 -0
- data/CHANGELOG.md +37 -0
- data/CODE_OF_CONDUCT.md +16 -0
- data/LICENSE.txt +21 -0
- data/README.md +168 -0
- data/Rakefile +70 -0
- data/app/assets/builds/modern_queue_dashboard.css +1 -0
- data/app/assets/builds/modern_queue_dashboard.js +19 -0
- data/app/assets/javascripts/modern_queue_dashboard.js +18 -0
- data/app/assets/stylesheets/modern_queue_dashboard.css +8 -0
- data/app/controllers/modern_queue_dashboard/application_controller.rb +7 -0
- data/app/controllers/modern_queue_dashboard/dashboard_controller.rb +10 -0
- data/app/controllers/modern_queue_dashboard/queues_controller.rb +19 -0
- data/app/models/modern_queue_dashboard/metrics.rb +49 -0
- data/app/models/modern_queue_dashboard/queue_summary.rb +52 -0
- data/app/views/layouts/modern_queue_dashboard/application.html.erb +15 -0
- data/app/views/modern_queue_dashboard/dashboard/index.html.erb +51 -0
- data/app/views/modern_queue_dashboard/queues/index.html.erb +35 -0
- data/app/views/modern_queue_dashboard/queues/show.html.erb +51 -0
- data/config/routes.rb +7 -0
- data/docs/PLAN.md +201 -0
- data/lib/modern_queue_dashboard/engine.rb +24 -0
- data/lib/modern_queue_dashboard/metrics.rb +43 -0
- data/lib/modern_queue_dashboard/queue_summary.rb +38 -0
- data/lib/modern_queue_dashboard/version.rb +5 -0
- data/lib/modern_queue_dashboard.rb +35 -0
- data/package-lock.json +1506 -0
- data/package.json +17 -0
- data/sig/modern_queue_dashboard.rbs +4 -0
- data/tailwind.config.js +11 -0
- 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
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,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
|