solid_queue_web 0.8.0 → 0.9.0
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 +4 -4
- data/README.md +4 -7
- data/app/assets/stylesheets/solid_queue_web/_04_table.css +8 -1
- data/app/assets/stylesheets/solid_queue_web/_05_badges.css +1 -0
- data/app/assets/stylesheets/solid_queue_web/_11_throughput.css +30 -1
- data/app/controllers/solid_queue_web/blocked_jobs_controller.rb +11 -0
- data/app/controllers/solid_queue_web/dashboard_controller.rb +1 -38
- data/app/controllers/solid_queue_web/queues/jobs_controller.rb +15 -19
- data/app/controllers/solid_queue_web/queues/pauses_controller.rb +21 -0
- data/app/controllers/solid_queue_web/queues_controller.rb +5 -31
- data/app/services/solid_queue_web/dashboard_stats.rb +47 -0
- data/app/services/solid_queue_web/queue_stats.rb +52 -0
- data/app/views/solid_queue_web/dashboard/index.html.erb +68 -24
- data/app/views/solid_queue_web/failed_jobs/index.html.erb +1 -1
- data/app/views/solid_queue_web/history/index.html.erb +1 -1
- data/app/views/solid_queue_web/jobs/index.html.erb +16 -3
- data/app/views/solid_queue_web/queues/index.html.erb +19 -2
- data/app/views/solid_queue_web/queues/jobs/index.html.erb +1 -1
- data/app/views/solid_queue_web/search/index.html.erb +1 -1
- data/config/routes.rb +3 -7
- data/lib/solid_queue_web/version.rb +1 -1
- data/lib/solid_queue_web.rb +6 -1
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 462f81d84dead7a68833c768775eda6cb0b55b66862d58fa1b49b504fa417644
|
|
4
|
+
data.tar.gz: cc388f882d5709e8d92a778a2c6ccaa39d04022c62c9bc7e89320e416c5148d2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '087a8cb99798a1d21df1347008380c58242a98247d48b80cba0865c4bb0e46e800d37a2c21eb887fc516c6c724c5e138ebbc8a517dc62e730b1d2ea08fe6b5d3'
|
|
7
|
+
data.tar.gz: 3885a114fa3f31db4b949a3b397070734b76c6e447d82bc7cd5e67628163760e03a2b70e90c1ef532143170752430de6ba1dd5a18fa0c52ff379462e90dc8d30
|
data/README.md
CHANGED
|
@@ -33,8 +33,8 @@ SolidQueueWeb surfaces all of this in a browser UI available at any route you ch
|
|
|
33
33
|
|
|
34
34
|
## Features
|
|
35
35
|
|
|
36
|
-
- **Dashboard** — stat cards showing counts for ready, scheduled, running, blocked, and failed jobs, plus queues, recurring tasks, and processes; "Done (1h)" and "Done (24h)" throughput cards; a "Throughput — Last 12 Hours" bar chart showing
|
|
37
|
-
- **Queues** — all queues sorted by name with size; oldest ready job latency (color-coded, with UTC timestamp tooltip); Done (24h) and Failed (24h) throughput counts; pause/resume controls
|
|
36
|
+
- **Dashboard** — stat cards showing counts for ready, scheduled, running, blocked, and failed jobs, plus queues, recurring tasks, and processes; "Done (1h)" and "Done (24h)" throughput cards; a "Throughput — Last 12 Hours" bar chart (blue) and a "Queue Depth — Last 12 Hours" bar chart (purple) showing hourly snapshots of active job count; pure CSS, no charting library; auto-refreshes every 5 seconds
|
|
37
|
+
- **Queues** — all queues sorted by name with size; oldest ready job latency (color-coded, with UTC timestamp tooltip); Done (24h) and Failed (24h) throughput counts; a mini 12-bar failure rate sparkline per queue showing failure % per hour over the last 12 hours; pause/resume controls
|
|
38
38
|
- **Jobs** — filterable by status (ready, scheduled, claimed, blocked, failed) and by queue; search by job class name with dynamic auto-submit; time-based period filter (1 h / 24 h / 7 d); discard individual or all jobs; Turbo Frame navigation so only the table updates on filter or search; auto-refreshes every 10 seconds
|
|
39
39
|
- **Failed jobs** — list of failed executions with error details; search by class name; filter by queue; time-based period filter; retry or discard individually or in bulk
|
|
40
40
|
- **Job detail** — full arguments, timestamps, blocked-until date, and error backtrace; action buttons based on job status
|
|
@@ -47,6 +47,7 @@ SolidQueueWeb surfaces all of this in a browser UI available at any route you ch
|
|
|
47
47
|
- **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
|
|
48
48
|
- **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
|
|
49
49
|
- **CSV export** — "Export CSV" button on the jobs, failed jobs, and history pages downloads all records matching the current filters; columns are tailored per view
|
|
50
|
+
- **Slow job detection** — when `slow_job_threshold` is configured, claimed jobs running longer than the threshold are flagged with an orange row, a "slow" badge, and a "Running For" duration column on the Running tab; a "Slow Jobs" warning card appears on the dashboard with a link to the Running tab
|
|
50
51
|
|
|
51
52
|
## Screenshots
|
|
52
53
|
|
|
@@ -96,6 +97,7 @@ SolidQueueWeb.configure do |config|
|
|
|
96
97
|
config.dashboard_refresh_interval = 10_000 # dashboard auto-refresh in ms (default: 5_000)
|
|
97
98
|
config.default_refresh_interval = 30_000 # jobs/processes/history auto-refresh in ms (default: 10_000)
|
|
98
99
|
config.search_results_limit = 10 # max results per status in global search (default: 25)
|
|
100
|
+
config.slow_job_threshold = 5.minutes # flag claimed jobs running longer than this (default: nil = disabled)
|
|
99
101
|
end
|
|
100
102
|
|
|
101
103
|
SolidQueueWeb.authenticate do
|
|
@@ -111,11 +113,6 @@ No authentication is enforced by default. When the `authenticate` block returns
|
|
|
111
113
|
|
|
112
114
|
Planned features, roughly ordered by priority:
|
|
113
115
|
|
|
114
|
-
**Observability**
|
|
115
|
-
- Job failure rate chart — sparkline per queue showing failure percentage over time, mirroring the throughput chart
|
|
116
|
-
- Queue depth trend — historical queue size over time, not just the current snapshot
|
|
117
|
-
- Slow job detection — flag jobs exceeding a configurable duration threshold
|
|
118
|
-
|
|
119
116
|
**Operations**
|
|
120
117
|
- Scheduled job management — reschedule a job to run immediately, or push its `scheduled_at` forward
|
|
121
118
|
- Bulk retry with delay — retry all failed jobs with a configurable stagger to avoid thundering herd
|
|
@@ -44,9 +44,16 @@ td {
|
|
|
44
44
|
|
|
45
45
|
tr:last-child td { border-bottom: none; }
|
|
46
46
|
tbody tr:hover { background: var(--bg); }
|
|
47
|
+
.sqd-table-link,
|
|
48
|
+
.sqd-table-link:hover,
|
|
49
|
+
.sqd-table-link:visited { text-decoration: none; color: var(--primary); }
|
|
47
50
|
|
|
48
51
|
.sqd-empty {
|
|
49
52
|
text-align: center;
|
|
50
53
|
padding: 3rem 1rem;
|
|
51
54
|
color: var(--muted);
|
|
52
|
-
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.sqd-row--slow { background: rgba(253, 126, 20, 0.07); }
|
|
58
|
+
.sqd-row--slow:hover { background: rgba(253, 126, 20, 0.13); }
|
|
59
|
+
.sqd-slow-duration { color: var(--warning); font-weight: 600; }
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
.sqd-badge--supervisor { background: #e0d7f5; color: #4a2c8a; }
|
|
22
22
|
.sqd-badge--worker { background: #d1e7dd; color: #0f5132; }
|
|
23
23
|
.sqd-badge--dispatcher { background: #cff4fc; color: #055160; }
|
|
24
|
+
.sqd-badge--slow { background: #ffe8cc; color: #7c3d00; }
|
|
24
25
|
|
|
25
26
|
.sqd-process-meta { font-size: 12px; color: var(--muted); }
|
|
26
27
|
.sqd-process-meta span + span::before { content: " · "; }
|
|
@@ -65,4 +65,33 @@
|
|
|
65
65
|
padding: 1rem 1.25rem;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
.sqd-stat--done .sqd-stat__value { color: var(--success); }
|
|
68
|
+
.sqd-stat--done .sqd-stat__value { color: var(--success); }
|
|
69
|
+
|
|
70
|
+
.sqd-mini-sparkline {
|
|
71
|
+
display: flex;
|
|
72
|
+
align-items: flex-end;
|
|
73
|
+
gap: 2px;
|
|
74
|
+
height: 28px;
|
|
75
|
+
width: 88px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.sqd-mini-sparkline__bar {
|
|
79
|
+
flex: 1;
|
|
80
|
+
background: var(--danger);
|
|
81
|
+
border-radius: 1px 1px 0 0;
|
|
82
|
+
opacity: 0.7;
|
|
83
|
+
transition: opacity 0.15s;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.sqd-mini-sparkline__bar:hover {
|
|
87
|
+
opacity: 1;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.sqd-mini-sparkline__bar--empty {
|
|
91
|
+
background: var(--border);
|
|
92
|
+
opacity: 0.5;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.sqd-sparkline__bar--depth {
|
|
96
|
+
background: var(--purple);
|
|
97
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module SolidQueueWeb
|
|
2
|
+
class BlockedJobsController < ApplicationController
|
|
3
|
+
def destroy
|
|
4
|
+
jobs = SolidQueue::BlockedExecution.includes(:job).map(&:job)
|
|
5
|
+
SolidQueue::BlockedExecution.discard_all_from_jobs(jobs)
|
|
6
|
+
redirect_to root_path, notice: "#{jobs.size} blocked #{"job".pluralize(jobs.size)} discarded."
|
|
7
|
+
rescue => e
|
|
8
|
+
redirect_to root_path, alert: "Could not discard blocked jobs: #{e.message}"
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -1,44 +1,7 @@
|
|
|
1
1
|
module SolidQueueWeb
|
|
2
2
|
class DashboardController < ApplicationController
|
|
3
3
|
def index
|
|
4
|
-
@stats =
|
|
5
|
-
ready: SolidQueue::ReadyExecution.count,
|
|
6
|
-
scheduled: SolidQueue::ScheduledExecution.count,
|
|
7
|
-
claimed: SolidQueue::ClaimedExecution.count,
|
|
8
|
-
failed: SolidQueue::FailedExecution.count,
|
|
9
|
-
blocked: SolidQueue::BlockedExecution.count,
|
|
10
|
-
queues: SolidQueue::Job.select(:queue_name).distinct.count,
|
|
11
|
-
processes: SolidQueue::Process.count,
|
|
12
|
-
recurring: SolidQueue::RecurringTask.count
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
now = Time.current
|
|
16
|
-
finished_times = SolidQueue::Job.where(finished_at: 24.hours.ago..now).pluck(:finished_at)
|
|
17
|
-
@throughput = {
|
|
18
|
-
completed_1h: finished_times.count { |t| t >= 1.hour.ago },
|
|
19
|
-
completed_24h: finished_times.size
|
|
20
|
-
}
|
|
21
|
-
@sparkline = 12.times.map do |i|
|
|
22
|
-
from = (12 - i).hours.ago
|
|
23
|
-
to = i == 11 ? now : (11 - i).hours.ago
|
|
24
|
-
finished_times.count { |t| t >= from && t < to }
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def retry_all_failed
|
|
29
|
-
jobs = SolidQueue::FailedExecution.includes(:job).map(&:job)
|
|
30
|
-
SolidQueue::FailedExecution.retry_all(jobs)
|
|
31
|
-
redirect_to root_path, notice: "#{jobs.size} failed #{"job".pluralize(jobs.size)} queued for retry."
|
|
32
|
-
rescue => e
|
|
33
|
-
redirect_to root_path, alert: "Could not retry failed jobs: #{e.message}"
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def discard_all_blocked
|
|
37
|
-
jobs = SolidQueue::BlockedExecution.includes(:job).map(&:job)
|
|
38
|
-
SolidQueue::BlockedExecution.discard_all_from_jobs(jobs)
|
|
39
|
-
redirect_to root_path, notice: "#{jobs.size} blocked #{"job".pluralize(jobs.size)} discarded."
|
|
40
|
-
rescue => e
|
|
41
|
-
redirect_to root_path, alert: "Could not discard blocked jobs: #{e.message}"
|
|
4
|
+
@stats = DashboardStats.new
|
|
42
5
|
end
|
|
43
6
|
end
|
|
44
7
|
end
|
|
@@ -2,7 +2,7 @@ module SolidQueueWeb
|
|
|
2
2
|
module Queues
|
|
3
3
|
class JobsController < ApplicationController
|
|
4
4
|
before_action :set_queue
|
|
5
|
-
before_action :set_status, only: [:destroy
|
|
5
|
+
before_action :set_status, only: [:destroy]
|
|
6
6
|
|
|
7
7
|
def index
|
|
8
8
|
@status = params[:status].presence_in(Job::STATUSES) || "ready"
|
|
@@ -15,29 +15,25 @@ module SolidQueueWeb
|
|
|
15
15
|
|
|
16
16
|
def destroy
|
|
17
17
|
model = execution_model_for!(@status)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
format
|
|
23
|
-
|
|
18
|
+
if params[:id]
|
|
19
|
+
@execution = model.find(params[:id])
|
|
20
|
+
@execution.discard
|
|
21
|
+
@remaining_count = filtered_scope(model).count
|
|
22
|
+
respond_to do |format|
|
|
23
|
+
format.turbo_stream
|
|
24
|
+
format.html { redirect_to queue_jobs_path(queue_name: @queue, status: @status), notice: "Job discarded." }
|
|
25
|
+
end
|
|
26
|
+
else
|
|
27
|
+
jobs = filtered_scope(model).map(&:job)
|
|
28
|
+
model.discard_all_from_jobs(jobs)
|
|
29
|
+
redirect_to queue_jobs_path(queue_name: @queue, status: @status),
|
|
30
|
+
notice: "#{jobs.size} #{"job".pluralize(jobs.size)} discarded."
|
|
24
31
|
end
|
|
25
32
|
rescue ArgumentError => e
|
|
26
33
|
redirect_to queue_jobs_path(queue_name: @queue, status: @status), alert: e.message
|
|
27
34
|
rescue => e
|
|
28
|
-
redirect_to queue_jobs_path(queue_name: @queue, status: @status), alert: "Could not discard job: #{e.message}"
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def discard_all
|
|
32
|
-
model = execution_model_for!(@status)
|
|
33
|
-
jobs = filtered_scope(model).map(&:job)
|
|
34
|
-
model.discard_all_from_jobs(jobs)
|
|
35
35
|
redirect_to queue_jobs_path(queue_name: @queue, status: @status),
|
|
36
|
-
|
|
37
|
-
rescue ArgumentError => e
|
|
38
|
-
redirect_to queue_jobs_path(queue_name: @queue, status: @status), alert: e.message
|
|
39
|
-
rescue => e
|
|
40
|
-
redirect_to queue_jobs_path(queue_name: @queue, status: @status), alert: "Could not discard jobs: #{e.message}"
|
|
36
|
+
alert: "Could not discard #{params[:id] ? "job" : "jobs"}: #{e.message}"
|
|
41
37
|
end
|
|
42
38
|
|
|
43
39
|
private
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module SolidQueueWeb
|
|
2
|
+
module Queues
|
|
3
|
+
class PausesController < ApplicationController
|
|
4
|
+
def create
|
|
5
|
+
queue = SolidQueue::Queue.find_by_name(params[:queue_name])
|
|
6
|
+
queue.pause
|
|
7
|
+
redirect_to queues_path, notice: "Queue \"#{queue.name}\" paused."
|
|
8
|
+
rescue => e
|
|
9
|
+
redirect_to queues_path, alert: "Could not pause queue: #{e.message}"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def destroy
|
|
13
|
+
queue = SolidQueue::Queue.find_by_name(params[:queue_name])
|
|
14
|
+
queue.resume
|
|
15
|
+
redirect_to queues_path, notice: "Queue \"#{queue.name}\" resumed."
|
|
16
|
+
rescue => e
|
|
17
|
+
redirect_to queues_path, alert: "Could not resume queue: #{e.message}"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -2,37 +2,11 @@ module SolidQueueWeb
|
|
|
2
2
|
class QueuesController < ApplicationController
|
|
3
3
|
def index
|
|
4
4
|
@queues = SolidQueue::Queue.all.sort_by(&:name)
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
@
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
.count
|
|
11
|
-
@failed_24h = SolidQueue::FailedExecution
|
|
12
|
-
.joins(:job)
|
|
13
|
-
.where(created_at: 24.hours.ago..now)
|
|
14
|
-
.group("solid_queue_jobs.queue_name")
|
|
15
|
-
.count
|
|
16
|
-
@oldest_ready = SolidQueue::ReadyExecution
|
|
17
|
-
.joins(:job)
|
|
18
|
-
.group("solid_queue_jobs.queue_name")
|
|
19
|
-
.minimum("solid_queue_jobs.created_at")
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def pause
|
|
23
|
-
queue = SolidQueue::Queue.find_by_name(params[:name])
|
|
24
|
-
queue.pause
|
|
25
|
-
redirect_to queues_path, notice: "Queue \"#{queue.name}\" paused."
|
|
26
|
-
rescue => e
|
|
27
|
-
redirect_to queues_path, alert: "Could not pause queue: #{e.message}"
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def resume
|
|
31
|
-
queue = SolidQueue::Queue.find_by_name(params[:name])
|
|
32
|
-
queue.resume
|
|
33
|
-
redirect_to queues_path, notice: "Queue \"#{queue.name}\" resumed."
|
|
34
|
-
rescue => e
|
|
35
|
-
redirect_to queues_path, alert: "Could not resume queue: #{e.message}"
|
|
5
|
+
stats = QueueStats.new(@queues)
|
|
6
|
+
@completed_24h = stats.completed_24h
|
|
7
|
+
@failed_24h = stats.failed_24h
|
|
8
|
+
@oldest_ready = stats.oldest_ready
|
|
9
|
+
@failure_sparklines = stats.failure_sparklines
|
|
36
10
|
end
|
|
37
11
|
end
|
|
38
12
|
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module SolidQueueWeb
|
|
2
|
+
class DashboardStats
|
|
3
|
+
attr_reader :counts, :throughput, :sparkline, :depth_sparkline, :slow_jobs_count
|
|
4
|
+
|
|
5
|
+
def initialize
|
|
6
|
+
@now = Time.current
|
|
7
|
+
compute
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def compute
|
|
13
|
+
@counts = {
|
|
14
|
+
ready: SolidQueue::ReadyExecution.count,
|
|
15
|
+
scheduled: SolidQueue::ScheduledExecution.count,
|
|
16
|
+
claimed: SolidQueue::ClaimedExecution.count,
|
|
17
|
+
failed: SolidQueue::FailedExecution.count,
|
|
18
|
+
blocked: SolidQueue::BlockedExecution.count,
|
|
19
|
+
queues: SolidQueue::Job.select(:queue_name).distinct.count,
|
|
20
|
+
processes: SolidQueue::Process.count,
|
|
21
|
+
recurring: SolidQueue::RecurringTask.count
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
finished_times = SolidQueue::Job.where(finished_at: 24.hours.ago..@now).pluck(:finished_at)
|
|
25
|
+
@throughput = {
|
|
26
|
+
completed_1h: finished_times.count { |t| t >= 1.hour.ago },
|
|
27
|
+
completed_24h: finished_times.size
|
|
28
|
+
}
|
|
29
|
+
@sparkline = 12.times.map do |i|
|
|
30
|
+
from = (12 - i).hours.ago
|
|
31
|
+
to = i == 11 ? @now : (11 - i).hours.ago
|
|
32
|
+
finished_times.count { |t| t >= from && t < to }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
threshold = SolidQueueWeb.slow_job_threshold
|
|
36
|
+
@slow_jobs_count = threshold ? SolidQueue::ClaimedExecution.where("created_at <= ?", threshold.ago).count : 0
|
|
37
|
+
|
|
38
|
+
job_timestamps = SolidQueue::Job
|
|
39
|
+
.where("created_at >= ? OR finished_at IS NULL", 72.hours.ago)
|
|
40
|
+
.pluck(:created_at, :finished_at)
|
|
41
|
+
@depth_sparkline = 12.times.map do |i|
|
|
42
|
+
t = i == 11 ? @now : (12 - i).hours.ago
|
|
43
|
+
job_timestamps.count { |created, finished| created <= t && (finished.nil? || finished > t) }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module SolidQueueWeb
|
|
2
|
+
class QueueStats
|
|
3
|
+
attr_reader :completed_24h, :failed_24h, :oldest_ready, :failure_sparklines
|
|
4
|
+
|
|
5
|
+
def initialize(queues)
|
|
6
|
+
@queues = queues
|
|
7
|
+
@now = Time.current
|
|
8
|
+
compute
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def compute
|
|
14
|
+
@completed_24h = SolidQueue::Job
|
|
15
|
+
.where(finished_at: 24.hours.ago..@now)
|
|
16
|
+
.group(:queue_name)
|
|
17
|
+
.count
|
|
18
|
+
|
|
19
|
+
@failed_24h = SolidQueue::FailedExecution
|
|
20
|
+
.joins(:job)
|
|
21
|
+
.where(created_at: 24.hours.ago..@now)
|
|
22
|
+
.group("solid_queue_jobs.queue_name")
|
|
23
|
+
.count
|
|
24
|
+
|
|
25
|
+
@oldest_ready = SolidQueue::ReadyExecution
|
|
26
|
+
.joins(:job)
|
|
27
|
+
.group("solid_queue_jobs.queue_name")
|
|
28
|
+
.minimum("solid_queue_jobs.created_at")
|
|
29
|
+
|
|
30
|
+
failed_raw = SolidQueue::FailedExecution
|
|
31
|
+
.joins(:job)
|
|
32
|
+
.where(created_at: 12.hours.ago..@now)
|
|
33
|
+
.pluck("solid_queue_jobs.queue_name", "solid_queue_failed_executions.created_at")
|
|
34
|
+
done_raw = SolidQueue::Job
|
|
35
|
+
.where(finished_at: 12.hours.ago..@now)
|
|
36
|
+
.pluck(:queue_name, :finished_at)
|
|
37
|
+
|
|
38
|
+
@failure_sparklines = @queues.each_with_object({}) do |queue, h|
|
|
39
|
+
failed_times = failed_raw.filter_map { |q, t| t if q == queue.name }
|
|
40
|
+
done_times = done_raw.filter_map { |q, t| t if q == queue.name }
|
|
41
|
+
h[queue.name] = 12.times.map do |i|
|
|
42
|
+
from = (12 - i).hours.ago
|
|
43
|
+
to = i == 11 ? @now : (11 - i).hours.ago
|
|
44
|
+
f = failed_times.count { |t| t >= from && t < to }
|
|
45
|
+
d = done_times.count { |t| t >= from && t < to }
|
|
46
|
+
total = f + d
|
|
47
|
+
total > 0 ? (f.to_f / total * 100).round : nil
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -3,61 +3,61 @@
|
|
|
3
3
|
|
|
4
4
|
<div class="sqd-stats">
|
|
5
5
|
<%= link_to jobs_path(status: "ready"), class: "sqd-stat sqd-stat--ready sqd-stat--link" do %>
|
|
6
|
-
<div class="sqd-stat__value"><%= @stats[:ready] %></div>
|
|
6
|
+
<div class="sqd-stat__value"><%= @stats.counts[:ready] %></div>
|
|
7
7
|
<div class="sqd-stat__label">Ready</div>
|
|
8
8
|
<% end %>
|
|
9
9
|
<%= link_to jobs_path(status: "scheduled"), class: "sqd-stat sqd-stat--scheduled sqd-stat--link" do %>
|
|
10
|
-
<div class="sqd-stat__value"><%= @stats[:scheduled] %></div>
|
|
10
|
+
<div class="sqd-stat__value"><%= @stats.counts[:scheduled] %></div>
|
|
11
11
|
<div class="sqd-stat__label">Scheduled</div>
|
|
12
12
|
<% end %>
|
|
13
13
|
<%= link_to jobs_path(status: "claimed"), class: "sqd-stat sqd-stat--claimed sqd-stat--link" do %>
|
|
14
|
-
<div class="sqd-stat__value"><%= @stats[:claimed] %></div>
|
|
14
|
+
<div class="sqd-stat__value"><%= @stats.counts[:claimed] %></div>
|
|
15
15
|
<div class="sqd-stat__label">Running</div>
|
|
16
16
|
<% end %>
|
|
17
17
|
<%= link_to jobs_path(status: "blocked"), class: "sqd-stat sqd-stat--blocked sqd-stat--link" do %>
|
|
18
|
-
<div class="sqd-stat__value"><%= @stats[:blocked] %></div>
|
|
18
|
+
<div class="sqd-stat__value"><%= @stats.counts[:blocked] %></div>
|
|
19
19
|
<div class="sqd-stat__label">Blocked</div>
|
|
20
20
|
<% end %>
|
|
21
21
|
<%= link_to failed_jobs_path, class: "sqd-stat sqd-stat--failed sqd-stat--link" do %>
|
|
22
|
-
<div class="sqd-stat__value"><%= @stats[:failed] %></div>
|
|
22
|
+
<div class="sqd-stat__value"><%= @stats.counts[:failed] %></div>
|
|
23
23
|
<div class="sqd-stat__label">Failed</div>
|
|
24
24
|
<% end %>
|
|
25
25
|
<%= link_to queues_path, class: "sqd-stat sqd-stat--queues sqd-stat--link" do %>
|
|
26
|
-
<div class="sqd-stat__value"><%= @stats[:queues] %></div>
|
|
26
|
+
<div class="sqd-stat__value"><%= @stats.counts[:queues] %></div>
|
|
27
27
|
<div class="sqd-stat__label">Queues</div>
|
|
28
28
|
<% end %>
|
|
29
29
|
<%= link_to recurring_tasks_path, class: "sqd-stat sqd-stat--recurring sqd-stat--link" do %>
|
|
30
|
-
<div class="sqd-stat__value"><%= @stats[:recurring] %></div>
|
|
30
|
+
<div class="sqd-stat__value"><%= @stats.counts[:recurring] %></div>
|
|
31
31
|
<div class="sqd-stat__label">Recurring</div>
|
|
32
32
|
<% end %>
|
|
33
33
|
<%= link_to processes_path, class: "sqd-stat sqd-stat--processes sqd-stat--link" do %>
|
|
34
|
-
<div class="sqd-stat__value"><%= @stats[:processes] %></div>
|
|
34
|
+
<div class="sqd-stat__value"><%= @stats.counts[:processes] %></div>
|
|
35
35
|
<div class="sqd-stat__label">Processes</div>
|
|
36
36
|
<% end %>
|
|
37
37
|
<%= link_to history_path(period: "1h"), class: "sqd-stat sqd-stat--done sqd-stat--link" do %>
|
|
38
|
-
<div class="sqd-stat__value"><%= @throughput[:completed_1h] %></div>
|
|
38
|
+
<div class="sqd-stat__value"><%= @stats.throughput[:completed_1h] %></div>
|
|
39
39
|
<div class="sqd-stat__label">Done (1h)</div>
|
|
40
40
|
<% end %>
|
|
41
41
|
<%= link_to history_path(period: "24h"), class: "sqd-stat sqd-stat--done sqd-stat--link" do %>
|
|
42
|
-
<div class="sqd-stat__value"><%= @throughput[:completed_24h] %></div>
|
|
42
|
+
<div class="sqd-stat__value"><%= @stats.throughput[:completed_24h] %></div>
|
|
43
43
|
<div class="sqd-stat__label">Done (24h)</div>
|
|
44
44
|
<% end %>
|
|
45
45
|
</div>
|
|
46
46
|
|
|
47
|
-
<% max_val = [@sparkline.max, 1].max %>
|
|
47
|
+
<% max_val = [@stats.sparkline.max, 1].max %>
|
|
48
48
|
<div class="sqd-card" style="margin-bottom: 1rem;">
|
|
49
49
|
<div class="sqd-card__header">
|
|
50
50
|
<span class="sqd-card__title">Throughput — Last 12 Hours</span>
|
|
51
51
|
<div class="sqd-throughput__summary">
|
|
52
|
-
<span>1h: <strong><%= @throughput[:completed_1h] %></strong></span>
|
|
53
|
-
<span>24h: <strong><%= @throughput[:completed_24h] %></strong></span>
|
|
52
|
+
<span>1h: <strong><%= @stats.throughput[:completed_1h] %></strong></span>
|
|
53
|
+
<span>24h: <strong><%= @stats.throughput[:completed_24h] %></strong></span>
|
|
54
54
|
</div>
|
|
55
55
|
</div>
|
|
56
|
-
<% if @throughput[:completed_24h] == 0 %>
|
|
56
|
+
<% if @stats.throughput[:completed_24h] == 0 %>
|
|
57
57
|
<div class="sqd-sparkline__empty">No completed jobs in the last 24 hours</div>
|
|
58
58
|
<% else %>
|
|
59
59
|
<div class="sqd-sparkline" aria-label="Jobs completed per hour over the last 12 hours">
|
|
60
|
-
<% @sparkline.each_with_index do |count, i| %>
|
|
60
|
+
<% @stats.sparkline.each_with_index do |count, i| %>
|
|
61
61
|
<% pct = (count.to_f / max_val * 100).round %>
|
|
62
62
|
<% hour_start = (12 - i).hours.ago %>
|
|
63
63
|
<% show_tick = [0, 3, 6, 9, 11].include?(i) %>
|
|
@@ -74,6 +74,36 @@
|
|
|
74
74
|
<% end %>
|
|
75
75
|
</div>
|
|
76
76
|
|
|
77
|
+
<% current_depth = @stats.counts[:ready] + @stats.counts[:scheduled] + @stats.counts[:claimed] + @stats.counts[:blocked] + @stats.counts[:failed] %>
|
|
78
|
+
<% max_depth = [@stats.depth_sparkline.max, 1].max %>
|
|
79
|
+
<div class="sqd-card" style="margin-bottom: 1rem;">
|
|
80
|
+
<div class="sqd-card__header">
|
|
81
|
+
<span class="sqd-card__title">Queue Depth — Last 12 Hours</span>
|
|
82
|
+
<div class="sqd-throughput__summary">
|
|
83
|
+
<span>Now: <strong><%= current_depth %></strong></span>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
<% if @stats.depth_sparkline.all?(&:zero?) %>
|
|
87
|
+
<div class="sqd-sparkline__empty">No active jobs in the last 12 hours</div>
|
|
88
|
+
<% else %>
|
|
89
|
+
<div class="sqd-sparkline" aria-label="Queue depth over the last 12 hours">
|
|
90
|
+
<% @stats.depth_sparkline.each_with_index do |depth, i| %>
|
|
91
|
+
<% pct = (depth.to_f / max_depth * 100).round %>
|
|
92
|
+
<% t = i == 11 ? Time.current : (12 - i).hours.ago %>
|
|
93
|
+
<% show_tick = [0, 3, 6, 9, 11].include?(i) %>
|
|
94
|
+
<div class="sqd-sparkline__col">
|
|
95
|
+
<div class="sqd-sparkline__bar-wrap">
|
|
96
|
+
<div class="sqd-sparkline__bar sqd-sparkline__bar--depth"
|
|
97
|
+
style="height: <%= [pct, 3].max %>%"
|
|
98
|
+
title="<%= i == 11 ? "now" : t.strftime("%-I%p").downcase %>: <%= depth %> <%= "job".pluralize(depth) %> in queue"></div>
|
|
99
|
+
</div>
|
|
100
|
+
<div class="sqd-sparkline__tick"><%= show_tick ? (i == 11 ? "now" : t.strftime("%-I%p").downcase) : "" %></div>
|
|
101
|
+
</div>
|
|
102
|
+
<% end %>
|
|
103
|
+
</div>
|
|
104
|
+
<% end %>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
77
107
|
<div style="display:grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1rem;">
|
|
78
108
|
<div class="sqd-card">
|
|
79
109
|
<div class="sqd-card__header">
|
|
@@ -88,37 +118,51 @@
|
|
|
88
118
|
</div>
|
|
89
119
|
</div>
|
|
90
120
|
|
|
91
|
-
<% if @stats[:failed] > 0 %>
|
|
121
|
+
<% if @stats.counts[:failed] > 0 %>
|
|
92
122
|
<div class="sqd-card">
|
|
93
123
|
<div class="sqd-card__header">
|
|
94
124
|
<span class="sqd-card__title">Failed Jobs</span>
|
|
95
125
|
</div>
|
|
96
126
|
<div style="padding: 1rem; display: flex; flex-direction: column; gap: 0.5rem;">
|
|
97
127
|
<p style="color: var(--danger); font-size: 13px;">
|
|
98
|
-
<%= pluralize(@stats[:failed], "failed job") %> need attention.
|
|
128
|
+
<%= pluralize(@stats.counts[:failed], "failed job") %> need attention.
|
|
99
129
|
</p>
|
|
100
|
-
<%= button_to "Retry All Failed",
|
|
130
|
+
<%= button_to "Retry All Failed", retry_all_failed_jobs_path,
|
|
101
131
|
method: :post,
|
|
102
132
|
class: "sqd-btn sqd-btn--primary",
|
|
103
|
-
data: { confirm: "Retry all #{@stats[:failed]} failed #{"job".pluralize(@stats[:failed])}?" } %>
|
|
133
|
+
data: { confirm: "Retry all #{@stats.counts[:failed]} failed #{"job".pluralize(@stats.counts[:failed])}?" } %>
|
|
104
134
|
<%= link_to "Review →", failed_jobs_path, class: "sqd-btn sqd-btn--muted" %>
|
|
105
135
|
</div>
|
|
106
136
|
</div>
|
|
107
137
|
<% end %>
|
|
108
138
|
|
|
109
|
-
<% if @stats
|
|
139
|
+
<% if SolidQueueWeb.slow_job_threshold && @stats.slow_jobs_count > 0 %>
|
|
140
|
+
<div class="sqd-card">
|
|
141
|
+
<div class="sqd-card__header">
|
|
142
|
+
<span class="sqd-card__title">Slow Jobs</span>
|
|
143
|
+
</div>
|
|
144
|
+
<div style="padding: 1rem; display: flex; flex-direction: column; gap: 0.5rem;">
|
|
145
|
+
<p style="color: var(--warning); font-size: 13px;">
|
|
146
|
+
<%= pluralize(@stats.slow_jobs_count, "job") %> running longer than <%= distance_of_time_in_words(SolidQueueWeb.slow_job_threshold) %>.
|
|
147
|
+
</p>
|
|
148
|
+
<%= link_to "Review →", jobs_path(status: "claimed"), class: "sqd-btn sqd-btn--muted" %>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
<% end %>
|
|
152
|
+
|
|
153
|
+
<% if @stats.counts[:blocked] > 0 %>
|
|
110
154
|
<div class="sqd-card">
|
|
111
155
|
<div class="sqd-card__header">
|
|
112
156
|
<span class="sqd-card__title">Blocked Jobs</span>
|
|
113
157
|
</div>
|
|
114
158
|
<div style="padding: 1rem; display: flex; flex-direction: column; gap: 0.5rem;">
|
|
115
159
|
<p style="color: var(--warning); font-size: 13px;">
|
|
116
|
-
<%= pluralize(@stats[:blocked], "blocked job") %>.
|
|
160
|
+
<%= pluralize(@stats.counts[:blocked], "blocked job") %>.
|
|
117
161
|
</p>
|
|
118
|
-
<%= button_to "Discard All Blocked",
|
|
119
|
-
method: :
|
|
162
|
+
<%= button_to "Discard All Blocked", blocked_jobs_path,
|
|
163
|
+
method: :delete,
|
|
120
164
|
class: "sqd-btn sqd-btn--danger",
|
|
121
|
-
data: { confirm: "Discard all #{@stats[:blocked]} blocked #{"job".pluralize(@stats[:blocked])}? This cannot be undone." } %>
|
|
165
|
+
data: { confirm: "Discard all #{@stats.counts[:blocked]} blocked #{"job".pluralize(@stats.counts[:blocked])}? This cannot be undone." } %>
|
|
122
166
|
<%= link_to "Review →", jobs_path(status: "blocked"), class: "sqd-btn sqd-btn--muted" %>
|
|
123
167
|
</div>
|
|
124
168
|
</div>
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
data-action="change->selection#toggle"
|
|
93
93
|
aria-label="Select job <%= job.class_name %>">
|
|
94
94
|
</td>
|
|
95
|
-
<td><%= link_to job.class_name, job_path(job) %></td>
|
|
95
|
+
<td><%= link_to job.class_name, job_path(job), class: "sqd-table-link" %></td>
|
|
96
96
|
<td>
|
|
97
97
|
<%= link_to job.queue_name, failed_jobs_path(queue: job.queue_name, q: @search, period: @period),
|
|
98
98
|
class: "sqd-mono", style: "color: inherit;" %>
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
<tbody>
|
|
50
50
|
<% @jobs.each do |job| %>
|
|
51
51
|
<tr>
|
|
52
|
-
<td><%= link_to job.class_name, job_path(job) %></td>
|
|
52
|
+
<td><%= link_to job.class_name, job_path(job), class: "sqd-table-link" %></td>
|
|
53
53
|
<td>
|
|
54
54
|
<%= link_to job.queue_name, history_path(queue: job.queue_name, q: @search, period: @period),
|
|
55
55
|
class: "sqd-mono", style: "color: inherit;" %>
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
</td>
|
|
88
88
|
<td>
|
|
89
89
|
<span class="sqd-badge sqd-badge--<%= @status %>"><%= @status %></span>
|
|
90
|
-
<%= link_to job.class_name, job_path(job), style: "margin-left: 0.5rem;", data: { turbo_frame: "_top" } %>
|
|
90
|
+
<%= link_to job.class_name, job_path(job), class: "sqd-table-link", style: "margin-left: 0.5rem;", data: { turbo_frame: "_top" } %>
|
|
91
91
|
</td>
|
|
92
92
|
<td>
|
|
93
93
|
<%= link_to job.queue_name, queue_jobs_path(queue_name: job.queue_name, status: @status),
|
|
@@ -116,6 +116,7 @@
|
|
|
116
116
|
<% if @jobs.empty? %>
|
|
117
117
|
<div class="sqd-empty">No <%= @status %> jobs.</div>
|
|
118
118
|
<% else %>
|
|
119
|
+
<% slow_threshold = @status == "claimed" ? SolidQueueWeb.slow_job_threshold : nil %>
|
|
119
120
|
<table>
|
|
120
121
|
<thead>
|
|
121
122
|
<tr>
|
|
@@ -124,15 +125,22 @@
|
|
|
124
125
|
<th scope="col">Priority</th>
|
|
125
126
|
<th scope="col">Scheduled At</th>
|
|
126
127
|
<th scope="col">Enqueued At</th>
|
|
128
|
+
<% if @status == "claimed" %>
|
|
129
|
+
<th scope="col">Running For</th>
|
|
130
|
+
<% end %>
|
|
127
131
|
</tr>
|
|
128
132
|
</thead>
|
|
129
133
|
<tbody>
|
|
130
134
|
<% @jobs.each do |execution| %>
|
|
131
135
|
<% job = execution.job %>
|
|
132
|
-
|
|
136
|
+
<% slow = slow_threshold && execution.created_at <= slow_threshold.ago %>
|
|
137
|
+
<tr id="execution_<%= execution.id %>"<%= slow ? ' class="sqd-row--slow"'.html_safe : "" %>>
|
|
133
138
|
<td>
|
|
134
139
|
<span class="sqd-badge sqd-badge--<%= @status %>"><%= @status %></span>
|
|
135
|
-
|
|
140
|
+
<% if slow %>
|
|
141
|
+
<span class="sqd-badge sqd-badge--slow">slow</span>
|
|
142
|
+
<% end %>
|
|
143
|
+
<%= link_to job.class_name, job_path(job), class: "sqd-table-link", style: "margin-left: 0.5rem;", data: { turbo_frame: "_top" } %>
|
|
136
144
|
</td>
|
|
137
145
|
<td>
|
|
138
146
|
<%= link_to job.queue_name, queue_jobs_path(queue_name: job.queue_name, status: @status),
|
|
@@ -143,6 +151,11 @@
|
|
|
143
151
|
<%= job.scheduled_at ? job.scheduled_at.strftime("%Y-%m-%d %H:%M:%S") : "—" %>
|
|
144
152
|
</td>
|
|
145
153
|
<td class="sqd-mono"><%= job.created_at.strftime("%Y-%m-%d %H:%M:%S") %></td>
|
|
154
|
+
<% if @status == "claimed" %>
|
|
155
|
+
<td class="sqd-mono<%= slow ? " sqd-slow-duration" : "" %>">
|
|
156
|
+
<%= time_ago_in_words(execution.created_at) %>
|
|
157
|
+
</td>
|
|
158
|
+
<% end %>
|
|
146
159
|
</tr>
|
|
147
160
|
<% end %>
|
|
148
161
|
</tbody>
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
<th scope="col">Latency</th>
|
|
13
13
|
<th scope="col">Done (24h)</th>
|
|
14
14
|
<th scope="col">Failed (24h)</th>
|
|
15
|
+
<th scope="col">Failure Rate (12h)</th>
|
|
15
16
|
<th scope="col">Status</th>
|
|
16
17
|
<th scope="col"><span class="sqd-sr-only">Actions</span></th>
|
|
17
18
|
</tr>
|
|
@@ -34,6 +35,22 @@
|
|
|
34
35
|
</td>
|
|
35
36
|
<td style="color: var(--success);"><%= @completed_24h[queue.name] || 0 %></td>
|
|
36
37
|
<td style="color: <%= (@failed_24h[queue.name] || 0) > 0 ? "var(--danger)" : "inherit" %>;"><%= @failed_24h[queue.name] || 0 %></td>
|
|
38
|
+
<td>
|
|
39
|
+
<% sparkline = @failure_sparklines[queue.name] %>
|
|
40
|
+
<% if sparkline.any? %>
|
|
41
|
+
<div class="sqd-mini-sparkline" aria-label="Failure rate last 12 hours for <%= queue.name %>">
|
|
42
|
+
<% sparkline.each_with_index do |rate, i| %>
|
|
43
|
+
<% pct = rate || 0 %>
|
|
44
|
+
<% hour_label = (12 - i).hours.ago.strftime("%-I%p").downcase %>
|
|
45
|
+
<div class="sqd-mini-sparkline__bar sqd-mini-sparkline__bar--<%= rate ? "data" : "empty" %>"
|
|
46
|
+
style="height: <%= [pct, 2].max %>%"
|
|
47
|
+
title="<%= hour_label %>: <%= rate ? "#{rate}% failure rate" : "no data" %>"></div>
|
|
48
|
+
<% end %>
|
|
49
|
+
</div>
|
|
50
|
+
<% else %>
|
|
51
|
+
<span style="color: var(--muted)">—</span>
|
|
52
|
+
<% end %>
|
|
53
|
+
</td>
|
|
37
54
|
<td>
|
|
38
55
|
<% if queue.paused? %>
|
|
39
56
|
<span class="sqd-badge sqd-badge--paused">Paused</span>
|
|
@@ -43,10 +60,10 @@
|
|
|
43
60
|
</td>
|
|
44
61
|
<td class="sqd-row-actions">
|
|
45
62
|
<% if queue.paused? %>
|
|
46
|
-
<%= button_to "Resume",
|
|
63
|
+
<%= button_to "Resume", queue_pause_path(queue.name), method: :delete,
|
|
47
64
|
class: "sqd-btn sqd-btn--primary sqd-btn--sm" %>
|
|
48
65
|
<% else %>
|
|
49
|
-
<%= button_to "Pause",
|
|
66
|
+
<%= button_to "Pause", queue_pause_path(queue.name), method: :post,
|
|
50
67
|
class: "sqd-btn sqd-btn--muted sqd-btn--sm",
|
|
51
68
|
data: { confirm: "Pause queue \"#{queue.name}\"?" } %>
|
|
52
69
|
<% end %>
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
<tr id="execution_<%= execution.id %>">
|
|
61
61
|
<td>
|
|
62
62
|
<span class="sqd-badge sqd-badge--<%= @status %>"><%= @status %></span>
|
|
63
|
-
<%= link_to job.class_name, job_path(job), style: "margin-left: 0.5rem;", data: { turbo_frame: "_top" } %>
|
|
63
|
+
<%= link_to job.class_name, job_path(job), class: "sqd-table-link", style: "margin-left: 0.5rem;", data: { turbo_frame: "_top" } %>
|
|
64
64
|
</td>
|
|
65
65
|
<td><%= job.priority %></td>
|
|
66
66
|
<td class="sqd-mono">
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
<% data[:executions].each do |execution| %>
|
|
51
51
|
<% job = execution.job %>
|
|
52
52
|
<tr>
|
|
53
|
-
<td><%= link_to job.class_name, job_path(job) %></td>
|
|
53
|
+
<td><%= link_to job.class_name, job_path(job), class: "sqd-table-link" %></td>
|
|
54
54
|
<td class="sqd-mono"><%= job.queue_name %></td>
|
|
55
55
|
<td class="sqd-mono"><%= job.created_at.strftime("%Y-%m-%d %H:%M:%S") %></td>
|
|
56
56
|
</tr>
|
data/config/routes.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
SolidQueueWeb::Engine.routes.draw do
|
|
2
2
|
root to: "dashboard#index"
|
|
3
|
-
|
|
4
|
-
post "discard_all_blocked", to: "dashboard#discard_all_blocked", as: :discard_all_blocked
|
|
3
|
+
resource :blocked_jobs, only: [:destroy]
|
|
5
4
|
|
|
6
5
|
get "search", to: "search#index", as: :search
|
|
7
6
|
get "history", to: "history#index", as: :history
|
|
@@ -9,13 +8,10 @@ SolidQueueWeb::Engine.routes.draw do
|
|
|
9
8
|
resources :recurring_tasks, only: [:index]
|
|
10
9
|
resources :processes, only: [:index]
|
|
11
10
|
resources :queues, only: [:index], param: :name do
|
|
12
|
-
|
|
13
|
-
post :pause
|
|
14
|
-
post :resume
|
|
15
|
-
end
|
|
11
|
+
resource :pause, only: [:create, :destroy], controller: "queues/pauses"
|
|
16
12
|
resources :jobs, path: "list", only: [:index, :destroy], controller: "queues/jobs" do
|
|
17
13
|
collection do
|
|
18
|
-
post :discard_all
|
|
14
|
+
post :discard_all, action: :destroy
|
|
19
15
|
end
|
|
20
16
|
end
|
|
21
17
|
end
|
data/lib/solid_queue_web.rb
CHANGED
|
@@ -4,7 +4,8 @@ require "solid_queue_web/engine"
|
|
|
4
4
|
|
|
5
5
|
module SolidQueueWeb
|
|
6
6
|
class << self
|
|
7
|
-
attr_writer :page_size, :dashboard_refresh_interval, :default_refresh_interval, :search_results_limit
|
|
7
|
+
attr_writer :page_size, :dashboard_refresh_interval, :default_refresh_interval, :search_results_limit,
|
|
8
|
+
:slow_job_threshold
|
|
8
9
|
|
|
9
10
|
def page_size
|
|
10
11
|
@page_size || 25
|
|
@@ -22,6 +23,10 @@ module SolidQueueWeb
|
|
|
22
23
|
@search_results_limit || 25
|
|
23
24
|
end
|
|
24
25
|
|
|
26
|
+
def slow_job_threshold
|
|
27
|
+
@slow_job_threshold
|
|
28
|
+
end
|
|
29
|
+
|
|
25
30
|
def configure
|
|
26
31
|
yield self
|
|
27
32
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: solid_queue_web
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chuck Smith
|
|
@@ -119,6 +119,7 @@ files:
|
|
|
119
119
|
- app/assets/stylesheets/solid_queue_web/_12_dark_mode.css
|
|
120
120
|
- app/assets/stylesheets/solid_queue_web/application.css
|
|
121
121
|
- app/controllers/solid_queue_web/application_controller.rb
|
|
122
|
+
- app/controllers/solid_queue_web/blocked_jobs_controller.rb
|
|
122
123
|
- app/controllers/solid_queue_web/dashboard_controller.rb
|
|
123
124
|
- app/controllers/solid_queue_web/failed_jobs/selections_controller.rb
|
|
124
125
|
- app/controllers/solid_queue_web/failed_jobs_controller.rb
|
|
@@ -127,6 +128,7 @@ files:
|
|
|
127
128
|
- app/controllers/solid_queue_web/jobs_controller.rb
|
|
128
129
|
- app/controllers/solid_queue_web/processes_controller.rb
|
|
129
130
|
- app/controllers/solid_queue_web/queues/jobs_controller.rb
|
|
131
|
+
- app/controllers/solid_queue_web/queues/pauses_controller.rb
|
|
130
132
|
- app/controllers/solid_queue_web/queues_controller.rb
|
|
131
133
|
- app/controllers/solid_queue_web/recurring_tasks_controller.rb
|
|
132
134
|
- app/controllers/solid_queue_web/retry_failed_jobs_controller.rb
|
|
@@ -140,6 +142,8 @@ files:
|
|
|
140
142
|
- app/jobs/solid_queue_web/application_job.rb
|
|
141
143
|
- app/models/solid_queue_web/application_record.rb
|
|
142
144
|
- app/models/solid_queue_web/job.rb
|
|
145
|
+
- app/services/solid_queue_web/dashboard_stats.rb
|
|
146
|
+
- app/services/solid_queue_web/queue_stats.rb
|
|
143
147
|
- app/views/layouts/solid_queue_web/application.html.erb
|
|
144
148
|
- app/views/solid_queue_web/dashboard/index.html.erb
|
|
145
149
|
- app/views/solid_queue_web/failed_jobs/index.html.erb
|