Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,30 @@
# Changelog

## [0.6.0] - 2026-01-20

### Added

- Job Activity Chart on dashboard showing jobs created, completed, and failed over time
- Pure SVG line chart with no external dependencies
- 9 configurable time ranges: 15m, 30m, 1h, 3h, 6h, 12h, 1d, 3d, 1w
- Collapsible chart section with summary totals visible when collapsed
- Interactive tooltips on hover
- Smart empty state handling (hides empty series, shows message when no data)
- Dark theme support with toggle button
- Toggle between light and dark themes
- Respects system preference (`prefers-color-scheme: dark`)
- Persists user preference in localStorage
- True black (#000000) background for OLED displays
- Wider layout (95% width, max 1800px) for better screen utilization
- Navigation active state highlighting current page
- New `ChartDataService` for aggregating job metrics into time buckets
- New `ChartPresenter` for rendering SVG charts

### Improved

- Updated all UI components to use CSS variables for consistent theming
- Enhanced visual hierarchy with improved color contrast in both themes

## [0.5.0] - 2026-01-16

### Added
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
solid_queue_monitor (0.5.0)
solid_queue_monitor (0.6.0)
rails (>= 7.0)
solid_queue (>= 0.1.0)

Expand Down
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ A lightweight, zero-dependency web interface for monitoring Solid Queue backgrou
## Features

- **Dashboard Overview**: Get a quick snapshot of your queue's health with statistics on all job types
- **Job Activity Chart**: Visual line chart showing jobs created, completed, and failed over time with 9 time range options (15m to 1 week)
- **Dark Theme**: Toggle between light and dark themes with system preference detection and localStorage persistence
- **Ready Jobs**: View jobs that are ready to be executed
- **In Progress Jobs**: Monitor jobs currently being processed by workers
- **Scheduled Jobs**: See upcoming jobs scheduled for future execution with ability to execute immediately or reject permanently
Expand All @@ -33,9 +35,13 @@ A lightweight, zero-dependency web interface for monitoring Solid Queue backgrou

## Screenshots

### Dashboard Overview
### Dashboard Overview (Light Theme)

![Dashboard Overview](screenshots/dashboard-3.png)
![Dashboard Overview - Light Theme](screenshots/dashboard-light.png)

### Dashboard Overview (Dark Theme)

![Dashboard Overview - Dark Theme](screenshots/dashboard-dark.png)

### Failed Jobs

Expand All @@ -46,7 +52,7 @@ A lightweight, zero-dependency web interface for monitoring Solid Queue backgrou
Add this line to your application's Gemfile:

```ruby
gem 'solid_queue_monitor', '~> 0.4.0'
gem 'solid_queue_monitor', '~> 0.6.0'
```

Then execute:
Expand Down
48 changes: 48 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Roadmap

This document tracks planned features for solid_queue_monitor, comparing with other solutions like `solid-queue-dashboard` and `mission_control-jobs`.

## High Priority - Core Functionality Gaps

| Feature | solid-queue-dashboard | mission_control-jobs | Impact | Status |
|---------|:---------------------:|:--------------------:|--------|:------:|
| Auto-refresh | ✓ | - | High - Real-time monitoring essential for ops | ✅ Done (v0.4.0) |
| Charts/Visualizations | ✓ | - | High - Visual trends are compelling | ⬚ Planned |
| Pause/Unpause Queues | - | ✓ | High - Critical for production incident response | ✅ Done (v0.5.0) |
| Worker Monitoring | - | ✓ | High - See which workers are processing what | ⬚ Planned |
| Dead Process Detection | ✓ | - | High - Identify stuck/zombie processes | ⬚ Planned |
| Execution History | ✓ | - | Medium - Job audit trail | ⬚ Planned |
| Failure Rate Tracking | ✓ | - | Medium - Trends over time | ⬚ Planned |

## Medium Priority - Power Features

| Feature | Description | Status |
|---------|-------------|:------:|
| Sensitive Argument Masking | Filter passwords/tokens from job arguments display | ⬚ Planned |
| Backtrace Cleaner | Remove framework noise from error backtraces | ⬚ Planned |
| Manual Job Triggering | Enqueue a job directly from the dashboard | ⬚ Planned |
| Cancel Running Jobs | Stop long-running jobs | ⬚ Planned |
| Search/Full-text Search | Better search across all job data | ⬚ Planned |
| Sorting Options | Sort by various columns | ⬚ Planned |
| Job Details Page | Dedicated page for single job with full context | ⬚ Planned |

## Lower Priority - Enterprise Features

| Feature | Description | Status |
|---------|-------------|:------:|
| Multi-app Support | Manage multiple apps from one dashboard | ⬚ Planned |
| Multi-database Support | Connect to different Solid Queue databases | ⬚ Planned |
| Console Helpers | Ruby API for scripting job operations | ⬚ Planned |
| Bulk Operation Throttling | Delay between bulk ops to prevent DB overload | ⬚ Planned |
| Export Jobs (CSV/JSON) | Download job data for analysis | ⬚ Planned |
| Webhooks/Notifications | Alert on failures via Slack/email | ⬚ Planned |
| API Endpoints (JSON) | Return JSON for custom integrations | ⬚ Planned |
| Dark Mode Toggle | User preference for theme | ⬚ Planned |

---

## Legend

- ✅ Done - Feature implemented
- 🚧 In Progress - Currently being worked on
- ⬚ Planned - Not yet started
11 changes: 11 additions & 0 deletions app/controllers/solid_queue_monitor/overview_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module SolidQueueMonitor
class OverviewController < BaseController
def index
@stats = SolidQueueMonitor::StatsCalculator.calculate
@chart_data = SolidQueueMonitor::ChartDataService.new(time_range: time_range_param).calculate

recent_jobs_query = SolidQueue::Job.order(created_at: :desc).limit(100)
@recent_jobs = paginate(filter_jobs(recent_jobs_query))
Expand All @@ -13,10 +14,20 @@ def index
render_page('Overview', generate_overview_content)
end

def chart_data
chart_data = SolidQueueMonitor::ChartDataService.new(time_range: time_range_param).calculate
render json: chart_data
end

private

def time_range_param
params[:time_range] || ChartDataService::DEFAULT_TIME_RANGE
end

def generate_overview_content
SolidQueueMonitor::StatsPresenter.new(@stats).render +
SolidQueueMonitor::ChartPresenter.new(@chart_data).render +
SolidQueueMonitor::JobsPresenter.new(@recent_jobs[:records],
current_page: @recent_jobs[:current_page],
total_pages: @recent_jobs[:total_pages],
Expand Down
100 changes: 100 additions & 0 deletions app/services/solid_queue_monitor/chart_data_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# frozen_string_literal: true

module SolidQueueMonitor
class ChartDataService
TIME_RANGES = {
'15m' => { duration: 15.minutes, buckets: 15, label_format: '%H:%M', label: 'Last 15 minutes' },
'30m' => { duration: 30.minutes, buckets: 15, label_format: '%H:%M', label: 'Last 30 minutes' },
'1h' => { duration: 1.hour, buckets: 12, label_format: '%H:%M', label: 'Last 1 hour' },
'3h' => { duration: 3.hours, buckets: 18, label_format: '%H:%M', label: 'Last 3 hours' },
'6h' => { duration: 6.hours, buckets: 24, label_format: '%H:%M', label: 'Last 6 hours' },
'12h' => { duration: 12.hours, buckets: 24, label_format: '%H:%M', label: 'Last 12 hours' },
'1d' => { duration: 1.day, buckets: 24, label_format: '%H:%M', label: 'Last 24 hours' },
'3d' => { duration: 3.days, buckets: 36, label_format: '%m/%d %H:%M', label: 'Last 3 days' },
'1w' => { duration: 7.days, buckets: 28, label_format: '%m/%d', label: 'Last 7 days' }
}.freeze

DEFAULT_TIME_RANGE = '1d'

def initialize(time_range: DEFAULT_TIME_RANGE)
@time_range = TIME_RANGES.key?(time_range) ? time_range : DEFAULT_TIME_RANGE
@config = TIME_RANGES[@time_range]
end

def calculate
end_time = Time.current
start_time = end_time - @config[:duration]
bucket_duration = @config[:duration] / @config[:buckets]

buckets = build_buckets(start_time, bucket_duration)

created_counts = fetch_created_counts(start_time, end_time)
completed_counts = fetch_completed_counts(start_time, end_time)
failed_counts = fetch_failed_counts(start_time, end_time)

created_data = assign_to_buckets(created_counts, buckets, bucket_duration)
completed_data = assign_to_buckets(completed_counts, buckets, bucket_duration)
failed_data = assign_to_buckets(failed_counts, buckets, bucket_duration)

{
labels: buckets.map { |b| b[:label] }, # rubocop:disable Rails/Pluck
created: created_data,
completed: completed_data,
failed: failed_data,
totals: {
created: created_data.sum,
completed: completed_data.sum,
failed: failed_data.sum
},
time_range: @time_range,
time_range_label: @config[:label],
available_ranges: TIME_RANGES.transform_values { |v| v[:label] }
}
end

private

def build_buckets(start_time, bucket_duration)
@config[:buckets].times.map do |i|
bucket_start = start_time + (i * bucket_duration)
{
start: bucket_start,
end: bucket_start + bucket_duration,
label: bucket_start.strftime(@config[:label_format])
}
end
end

def fetch_created_counts(start_time, end_time)
SolidQueue::Job
.where(created_at: start_time..end_time)
.pluck(:created_at)
end

def fetch_completed_counts(start_time, end_time)
SolidQueue::Job
.where(finished_at: start_time..end_time)
.where.not(finished_at: nil)
.pluck(:finished_at)
end

def fetch_failed_counts(start_time, end_time)
SolidQueue::FailedExecution
.where(created_at: start_time..end_time)
.pluck(:created_at)
end

def assign_to_buckets(timestamps, buckets, _bucket_duration)
counts = Array.new(buckets.size, 0)

timestamps.each do |timestamp|
bucket_index = buckets.find_index do |bucket|
timestamp >= bucket[:start] && timestamp < bucket[:end]
end
counts[bucket_index] += 1 if bucket_index
end

counts
end
end
end
Loading