diff --git a/CHANGELOG.md b/CHANGELOG.md index c7867e9..4de8338 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 +- Processes page showing workers, dispatchers, and supervisors with kind, PID, host, metadata, last heartbeat, and Healthy/Stale status - Queue pause / resume — Pause and Resume buttons per row on the Queues page - Pagination for jobs and failed jobs lists via pagy (25 per page) - Jobs URL segment renamed from `/jobs/jobs` to `/jobs/list` diff --git a/app/assets/stylesheets/solid_queue_web/application.css b/app/assets/stylesheets/solid_queue_web/application.css index 6858c30..cae07e5 100644 --- a/app/assets/stylesheets/solid_queue_web/application.css +++ b/app/assets/stylesheets/solid_queue_web/application.css @@ -210,8 +210,15 @@ tbody tr:hover { background: var(--bg); } .sqd-badge--claimed { background: #cfe2ff; color: #084298; } .sqd-badge--failed { background: #f8d7da; color: #842029; } .sqd-badge--blocked { background: #fff3cd; color: #664d03; } -.sqd-badge--paused { background: #e2e3e5; color: #41464b; } -.sqd-badge--running { background: #d1e7dd; color: #0f5132; } +.sqd-badge--paused { background: #e2e3e5; color: #41464b; } +.sqd-badge--running { background: #d1e7dd; color: #0f5132; } +.sqd-badge--supervisor { background: #e0d7f5; color: #4a2c8a; } +.sqd-badge--worker { background: #d1e7dd; color: #0f5132; } +.sqd-badge--dispatcher { background: #cff4fc; color: #055160; } + +.sqd-process-meta { font-size: 12px; color: var(--muted); } +.sqd-process-meta span + span::before { content: " · "; } +.sqd-muted-text { color: var(--muted); font-size: 13px; } /* Buttons */ .sqd-btn { diff --git a/app/controllers/solid_queue_web/processes_controller.rb b/app/controllers/solid_queue_web/processes_controller.rb new file mode 100644 index 0000000..b246b8c --- /dev/null +++ b/app/controllers/solid_queue_web/processes_controller.rb @@ -0,0 +1,7 @@ +module SolidQueueWeb + class ProcessesController < ApplicationController + def index + @processes = SolidQueue::Process.order(:kind, :name).to_a + end + end +end diff --git a/app/views/layouts/solid_queue_web/application.html.erb b/app/views/layouts/solid_queue_web/application.html.erb index 6afb82b..9e3b061 100644 --- a/app/views/layouts/solid_queue_web/application.html.erb +++ b/app/views/layouts/solid_queue_web/application.html.erb @@ -18,6 +18,7 @@
  • <%= link_to "Queues", queues_path, class: current_page?(queues_path) ? "active" : "" %>
  • <%= link_to "Jobs", jobs_path, class: current_page?(jobs_path) ? "active" : "" %>
  • <%= link_to "Failed", failed_jobs_path, class: current_page?(failed_jobs_path) ? "active" : "" %>
  • +
  • <%= link_to "Processes", processes_path, class: current_page?(processes_path) ? "active" : "" %>
  • diff --git a/app/views/solid_queue_web/processes/index.html.erb b/app/views/solid_queue_web/processes/index.html.erb new file mode 100644 index 0000000..f1505d6 --- /dev/null +++ b/app/views/solid_queue_web/processes/index.html.erb @@ -0,0 +1,54 @@ +

    Processes

    + +
    + <% if @processes.empty? %> +
    No processes registered.
    + <% else %> + + + + + + + + + + + + + + <% @processes.each do |process| %> + <% stale = process.last_heartbeat_at < SolidQueue.process_alive_threshold.ago %> + + + + + + + + + + <% end %> + +
    KindNamePIDHostDetailsLast HeartbeatStatus
    <%= process.kind %><%= process.name %><%= process.pid %><%= process.hostname %> + <% meta = process.metadata || {} %> + <% if meta["queues"].present? %> + <%= Array(meta["queues"]).join(", ") %> + <% end %> + <% if meta["thread_count"].present? %> + <%= meta["thread_count"] %> threads + <% end %> + <% if meta["polling_interval"].present? %> + every <%= meta["polling_interval"] %>s + <% end %> + + <%= time_ago_in_words(process.last_heartbeat_at) %> ago + + <% if stale %> + Stale + <% else %> + Healthy + <% end %> +
    + <% end %> +
    \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 1ecd8c8..f163725 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,7 @@ SolidQueueWeb::Engine.routes.draw do root to: "dashboard#index" + resources :processes, only: [ :index ] resources :queues, only: [ :index ], param: :name do member do post :pause diff --git a/spec/requests/solid_queue_web/processes_spec.rb b/spec/requests/solid_queue_web/processes_spec.rb new file mode 100644 index 0000000..98bcd32 --- /dev/null +++ b/spec/requests/solid_queue_web/processes_spec.rb @@ -0,0 +1,38 @@ +require "rails_helper" + +RSpec.describe "Processes", type: :request do + let!(:process) do + SolidQueue::Process.create!( + kind: "Worker", + name: "worker-0-web-1", + pid: 12_345, + hostname: "web-1.local", + last_heartbeat_at: 5.seconds.ago, + metadata: { queues: %w[default], thread_count: 2 } + ) + end + + describe "GET /jobs/processes" do + it "returns HTTP success" do + get "/jobs/processes" + expect(response).to have_http_status(:ok) + end + + it "displays process kind and name" do + get "/jobs/processes" + expect(response.body).to include("Worker") + expect(response.body).to include("worker-0-web-1") + end + + it "shows Healthy badge for a recent heartbeat" do + get "/jobs/processes" + expect(response.body).to include("Healthy") + end + + it "shows Stale badge for an old heartbeat" do + process.update!(last_heartbeat_at: 10.minutes.ago) + get "/jobs/processes" + expect(response.body).to include("Stale") + end + end +end