diff --git a/CHANGELOG.md b/CHANGELOG.md index abd8bfc..bceac14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Dashboard quick actions — "Retry All Failed" and "Discard All Blocked" buttons appear as cards on the dashboard when the respective count is non-zero; each includes a confirm dialog and redirects back to the dashboard with a count notice; cards are hidden when everything is healthy - Dark mode — a ☽/☀ toggle button in the header switches between light and dark themes; preference persists to `localStorage` and falls back to the OS `prefers-color-scheme` on first visit; implemented via a `[data-theme="dark"]` attribute on `` so all CSS custom properties inherit the new palette automatically; badge and flash hardcoded hex colors get explicit dark-mode overrides in a new `_12_dark_mode.css` partial; powered by a new Stimulus `theme_controller` - Configurable settings via `SolidQueueWeb.configure` — `page_size` (Pagy limit, default 25), `dashboard_refresh_interval` (ms, default 5 000), `default_refresh_interval` (ms, default 10 000 — applies to jobs, processes, and history pages), `search_results_limit` (max results per status in global search, default 25); all settings have safe defaults so zero host-app configuration is required diff --git a/README.md b/README.md index 160da32..6f6f566 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ SolidQueueWeb surfaces all of this in a browser UI available at any route you ch - **Targeted bulk actions** — checkboxes on the jobs and failed jobs lists for selecting individual rows; selection bar shows count and action buttons ("Discard Selected" for jobs, "Retry Selected" / "Discard Selected" for failed jobs); select-all checkbox in the table header - **Job history** — browsable list of all finished jobs with class name, queue, duration, and finished timestamp; filterable by period (1h / 24h / 7d), queue, and class name search; Done (1h) / Done (24h) dashboard cards link directly to the filtered history view; auto-refreshes every 10 seconds - **Dark mode** — ☽/☀ toggle in the header; preference persists to `localStorage` and defaults to the OS `prefers-color-scheme` on first visit; zero extra dependencies — implemented via CSS custom properties and a small Stimulus controller +- **Dashboard quick actions** — "Retry All Failed" and "Discard All Blocked" cards appear on the dashboard only when the respective count is non-zero; one-click bulk operations with confirm dialogs, keeping the dashboard clean when everything is healthy ## Screenshots @@ -82,25 +83,11 @@ Add to your `config/routes.rb`: mount SolidQueueWeb::Engine, at: "/jobs" ``` -The dashboard will be available at `/jobs`. See [Authentication](#authentication) to restrict access to admin users. - -## Authentication - -The engine ships with no authentication by default. Add a block to an initializer (e.g. `config/initializers/solid_queue_web.rb`) to protect the dashboard: - -```ruby -SolidQueueWeb.authenticate do - # Called in the context of ApplicationController — use any helper available there. - # Return a truthy value to allow access, falsy to deny (triggers HTTP Basic prompt). - current_user&.admin? -end -``` - -HTTP Basic authentication is used as a fallback when the block returns falsy. +The dashboard will be available at `/jobs`. ## Configuration -All settings are optional — the dashboard works with zero configuration. To override defaults, add a block to an initializer (e.g. `config/initializers/solid_queue_web.rb`): +All settings are optional — the dashboard works with zero configuration. Create `config/initializers/solid_queue_web.rb` to customize behavior: ```ruby SolidQueueWeb.configure do |config| @@ -109,15 +96,20 @@ SolidQueueWeb.configure do |config| config.default_refresh_interval = 30_000 # jobs/processes/history auto-refresh in ms (default: 10_000) config.search_results_limit = 10 # max results per status in global search (default: 25) end + +SolidQueueWeb.authenticate do + # Called in the context of ApplicationController — use any helper available there. + # Return a truthy value to allow access, falsy to deny (triggers HTTP Basic prompt). + current_user&.admin? +end ``` +No authentication is enforced by default. When the `authenticate` block returns falsy, HTTP Basic auth is used as a fallback. + ## Roadmap Planned features, roughly ordered by priority: -**Medium-term** -- Dashboard quick actions — Retry All Failed / Clear All Blocked directly from the dashboard - **Larger scope** - CSV export of any filtered view (jobs, failed jobs, history) - Webhook / alert config — POST to a URL when the failure count exceeds a threshold diff --git a/app/controllers/solid_queue_web/dashboard_controller.rb b/app/controllers/solid_queue_web/dashboard_controller.rb index 3a7ba5c..dc6753e 100644 --- a/app/controllers/solid_queue_web/dashboard_controller.rb +++ b/app/controllers/solid_queue_web/dashboard_controller.rb @@ -24,5 +24,21 @@ def index finished_times.count { |t| t >= from && t < to } end end + + def retry_all_failed + jobs = SolidQueue::FailedExecution.includes(:job).map(&:job) + SolidQueue::FailedExecution.retry_all(jobs) + redirect_to root_path, notice: "#{jobs.size} failed #{"job".pluralize(jobs.size)} queued for retry." + rescue => e + redirect_to root_path, alert: "Could not retry failed jobs: #{e.message}" + end + + def discard_all_blocked + jobs = SolidQueue::BlockedExecution.includes(:job).map(&:job) + SolidQueue::BlockedExecution.discard_all_from_jobs(jobs) + redirect_to root_path, notice: "#{jobs.size} blocked #{"job".pluralize(jobs.size)} discarded." + rescue => e + redirect_to root_path, alert: "Could not discard blocked jobs: #{e.message}" + end end end diff --git a/app/views/solid_queue_web/dashboard/index.html.erb b/app/views/solid_queue_web/dashboard/index.html.erb index 44ff981..f5d9942 100644 --- a/app/views/solid_queue_web/dashboard/index.html.erb +++ b/app/views/solid_queue_web/dashboard/index.html.erb @@ -74,7 +74,7 @@ <% end %> -
+
<%= pluralize(@stats[:failed], "failed job") %> need attention.
- <%= link_to "Review failed jobs →", failed_jobs_path, class: "sqd-btn sqd-btn--danger" %> + <%= button_to "Retry All Failed", retry_all_failed_path, + method: :post, + class: "sqd-btn sqd-btn--primary", + data: { confirm: "Retry all #{@stats[:failed]} failed #{"job".pluralize(@stats[:failed])}?" } %> + <%= link_to "Review →", failed_jobs_path, class: "sqd-btn sqd-btn--muted" %> ++ <%= pluralize(@stats[:blocked], "blocked job") %>. +
+ <%= button_to "Discard All Blocked", discard_all_blocked_path, + method: :post, + class: "sqd-btn sqd-btn--danger", + data: { confirm: "Discard all #{@stats[:blocked]} blocked #{"job".pluralize(@stats[:blocked])}? This cannot be undone." } %> + <%= link_to "Review →", jobs_path(status: "blocked"), class: "sqd-btn sqd-btn--muted" %>