solid_queue_tui 0.1.1 → 0.1.3
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/exe/sqtui +37 -1
- data/lib/solid_queue_tui/actions/discard_job.rb +4 -21
- data/lib/solid_queue_tui/actions/discard_scheduled_job.rb +5 -21
- data/lib/solid_queue_tui/actions/dispatch_scheduled_job.rb +4 -23
- data/lib/solid_queue_tui/actions/enqueue_recurring_task.rb +12 -0
- data/lib/solid_queue_tui/actions/retry_job.rb +14 -59
- data/lib/solid_queue_tui/actions/toggle_queue_pause.rb +19 -0
- data/lib/solid_queue_tui/application.rb +109 -21
- data/lib/solid_queue_tui/cli.rb +15 -8
- data/lib/solid_queue_tui/components/header.rb +26 -27
- data/lib/solid_queue_tui/components/help_bar.rb +1 -0
- data/lib/solid_queue_tui/components/job_table.rb +13 -2
- data/lib/solid_queue_tui/data/failed_query.rb +37 -91
- data/lib/solid_queue_tui/data/jobs_query.rb +119 -121
- data/lib/solid_queue_tui/data/processes_query.rb +32 -33
- data/lib/solid_queue_tui/data/queues_query.rb +6 -15
- data/lib/solid_queue_tui/data/recurring_tasks_query.rb +36 -0
- data/lib/solid_queue_tui/data/stats.rb +9 -27
- data/lib/solid_queue_tui/formatting_helpers.rb +63 -0
- data/lib/solid_queue_tui/railtie.rb +9 -0
- data/lib/solid_queue_tui/version.rb +1 -1
- data/lib/solid_queue_tui/views/blocked_view.rb +85 -74
- data/lib/solid_queue_tui/views/concerns/confirmable.rb +53 -0
- data/lib/solid_queue_tui/views/concerns/filterable.rb +128 -0
- data/lib/solid_queue_tui/views/concerns/paginatable.rb +79 -0
- data/lib/solid_queue_tui/views/dashboard_view.rb +4 -5
- data/lib/solid_queue_tui/views/failed_view.rb +65 -179
- data/lib/solid_queue_tui/views/finished_view.rb +33 -114
- data/lib/solid_queue_tui/views/in_progress_view.rb +85 -69
- data/lib/solid_queue_tui/views/job_detail_view.rb +179 -31
- data/lib/solid_queue_tui/views/processes_view.rb +2 -24
- data/lib/solid_queue_tui/views/queues_view.rb +250 -30
- data/lib/solid_queue_tui/views/recurring_tasks_view.rb +155 -0
- data/lib/solid_queue_tui/views/scheduled_view.rb +69 -107
- data/lib/solid_queue_tui.rb +18 -4
- data/lib/tasks/solid_queue_tui.rake +8 -0
- metadata +20 -25
- data/lib/solid_queue_tui/connection.rb +0 -58
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ea9a6cc3e42df6d7c5f50f91ed2bbf360bbd3e65c9fabb6f53bcf649b69826f1
|
|
4
|
+
data.tar.gz: 3cd1c06f2c4ea19f2c59e0c5dc79962211d8f18b8200b4f46955910c2fcd78e6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2364ad1bd990fca2ec78ad80a744c3d820e07b3add19dcbfcfeb88cebb5c07b47dc9b29acefafc27dac06a24b333cb11cd894ffba070435d4fbdcbb051d79941
|
|
7
|
+
data.tar.gz: af34ecfb3046c67d8aac92f227d37740130946ee5f3abb919b55c2d8edd86b40ba242970dc9bda05e2191c397e42548f200295f8259ca05653a7992fa5d3731a
|
data/exe/sqtui
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
# Boot the host Rails application's environment.
|
|
5
|
+
# This ensures ActiveRecord, Solid Queue models, and the database
|
|
6
|
+
# connection pool are fully loaded before the TUI starts.
|
|
7
|
+
env_file = File.join(Dir.pwd, "config", "environment.rb")
|
|
8
|
+
|
|
9
|
+
unless File.exist?(env_file)
|
|
10
|
+
$stderr.puts "Error: config/environment.rb not found in #{Dir.pwd}"
|
|
11
|
+
$stderr.puts ""
|
|
12
|
+
$stderr.puts "sqtui must be run from your Rails application's root directory."
|
|
13
|
+
$stderr.puts "Make sure solid_queue_tui is in your Gemfile and Solid Queue is configured."
|
|
14
|
+
exit 1
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
require env_file
|
|
18
|
+
|
|
19
|
+
# Log ActiveRecord queries to the Rails log file so TUI operations
|
|
20
|
+
# are visible in log/development.log (or whichever environment is active).
|
|
21
|
+
# Tag all TUI logs with [SQTUI] so they're easy to distinguish from app logs.
|
|
22
|
+
# CORRUPTING TERMINAL
|
|
23
|
+
# ActiveRecord::Base.logger = Rails.logger
|
|
24
|
+
# Rails.logger.push_tags("SQTUI") if Rails.logger.respond_to?(:push_tags)
|
|
25
|
+
|
|
26
|
+
# Logs must never hit STDOUT/STDERR — that would corrupt the TUI.
|
|
27
|
+
# If a log/ directory exists (traditional Rails), write there.
|
|
28
|
+
# Otherwise (Rails 8 defaults), silence logging.
|
|
29
|
+
#TODO: figure out logging for rails 8, docker
|
|
30
|
+
|
|
31
|
+
log_dir = File.join(Dir.pwd, "log")
|
|
32
|
+
if Dir.exist?(log_dir)
|
|
33
|
+
log_file = File.join(log_dir, "#{Rails.env}.log")
|
|
34
|
+
tui_logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(log_file))
|
|
35
|
+
tui_logger.push_tags("SQTUI")
|
|
36
|
+
else
|
|
37
|
+
tui_logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(File::NULL))
|
|
38
|
+
end
|
|
39
|
+
ActiveRecord::Base.logger = tui_logger
|
|
40
|
+
Rails.logger = tui_logger
|
|
5
41
|
|
|
6
42
|
SolidQueueTui::CLI.run(ARGV)
|
|
@@ -4,28 +4,11 @@ module SolidQueueTui
|
|
|
4
4
|
module Actions
|
|
5
5
|
class DiscardJob
|
|
6
6
|
def self.call(failed_execution_id)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
row = conn.select_one(
|
|
10
|
-
"SELECT fe.id, fe.job_id FROM solid_queue_failed_executions fe " \
|
|
11
|
-
"WHERE fe.id = #{conn.quote(failed_execution_id.to_i)}"
|
|
12
|
-
)
|
|
13
|
-
return false unless row
|
|
14
|
-
|
|
15
|
-
conn.transaction do
|
|
16
|
-
# Remove the failed execution
|
|
17
|
-
conn.execute(
|
|
18
|
-
"DELETE FROM solid_queue_failed_executions WHERE id = #{conn.quote(failed_execution_id.to_i)}"
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
# Mark the job as finished (discarded)
|
|
22
|
-
conn.execute(
|
|
23
|
-
"UPDATE solid_queue_jobs SET finished_at = #{conn.quote(Time.now.utc.iso8601)} " \
|
|
24
|
-
"WHERE id = #{conn.quote(row['job_id'])}"
|
|
25
|
-
)
|
|
26
|
-
end
|
|
27
|
-
|
|
7
|
+
fe = SolidQueue::FailedExecution.find(failed_execution_id)
|
|
8
|
+
fe.discard
|
|
28
9
|
true
|
|
10
|
+
rescue ActiveRecord::RecordNotFound
|
|
11
|
+
false
|
|
29
12
|
rescue => e
|
|
30
13
|
false
|
|
31
14
|
end
|
|
@@ -4,30 +4,14 @@ module SolidQueueTui
|
|
|
4
4
|
module Actions
|
|
5
5
|
class DiscardScheduledJob
|
|
6
6
|
def self.call(job_id)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
row = conn.select_one(
|
|
10
|
-
"SELECT se.id, se.job_id " \
|
|
11
|
-
"FROM solid_queue_scheduled_executions se " \
|
|
12
|
-
"WHERE se.job_id = #{conn.quote(job_id.to_i)}"
|
|
13
|
-
)
|
|
14
|
-
return false unless row
|
|
15
|
-
|
|
16
|
-
conn.transaction do
|
|
17
|
-
conn.execute(
|
|
18
|
-
"DELETE FROM solid_queue_scheduled_executions WHERE id = #{conn.quote(row['id'])}"
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
conn.execute(
|
|
22
|
-
"UPDATE solid_queue_jobs SET finished_at = #{conn.quote(Time.now.utc.iso8601)} " \
|
|
23
|
-
"WHERE id = #{conn.quote(row['job_id'])}"
|
|
24
|
-
)
|
|
25
|
-
end
|
|
26
|
-
|
|
7
|
+
se = SolidQueue::ScheduledExecution.find_by!(job_id: job_id)
|
|
8
|
+
se.discard
|
|
27
9
|
true
|
|
10
|
+
rescue ActiveRecord::RecordNotFound
|
|
11
|
+
false
|
|
28
12
|
rescue => e
|
|
29
13
|
false
|
|
30
14
|
end
|
|
31
15
|
end
|
|
32
16
|
end
|
|
33
|
-
end
|
|
17
|
+
end
|
|
@@ -4,32 +4,13 @@ module SolidQueueTui
|
|
|
4
4
|
module Actions
|
|
5
5
|
class DispatchScheduledJob
|
|
6
6
|
def self.call(job_id)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
row = conn.select_one(
|
|
10
|
-
"SELECT se.id, se.job_id, j.queue_name, j.priority " \
|
|
11
|
-
"FROM solid_queue_scheduled_executions se " \
|
|
12
|
-
"JOIN solid_queue_jobs j ON j.id = se.job_id " \
|
|
13
|
-
"WHERE j.id = #{conn.quote(job_id.to_i)}"
|
|
14
|
-
)
|
|
15
|
-
return false unless row
|
|
16
|
-
|
|
17
|
-
conn.transaction do
|
|
18
|
-
conn.execute(
|
|
19
|
-
"INSERT INTO solid_queue_ready_executions (job_id, queue_name, priority, created_at) " \
|
|
20
|
-
"VALUES (#{conn.quote(row['job_id'])}, #{conn.quote(row['queue_name'])}, " \
|
|
21
|
-
"#{conn.quote(row['priority'])}, #{conn.quote(Time.now.utc.iso8601)})"
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
conn.execute(
|
|
25
|
-
"DELETE FROM solid_queue_scheduled_executions WHERE id = #{conn.quote(row['id'])}"
|
|
26
|
-
)
|
|
27
|
-
end
|
|
28
|
-
|
|
7
|
+
SolidQueue::ScheduledExecution.dispatch_jobs([job_id])
|
|
29
8
|
true
|
|
9
|
+
rescue ActiveRecord::RecordNotFound
|
|
10
|
+
false
|
|
30
11
|
rescue => e
|
|
31
12
|
false
|
|
32
13
|
end
|
|
33
14
|
end
|
|
34
15
|
end
|
|
35
|
-
end
|
|
16
|
+
end
|
|
@@ -4,72 +4,27 @@ module SolidQueueTui
|
|
|
4
4
|
module Actions
|
|
5
5
|
class RetryJob
|
|
6
6
|
def self.call(failed_execution_id)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
# Get the failed execution and its job
|
|
10
|
-
row = conn.select_one(
|
|
11
|
-
"SELECT fe.id, fe.job_id, j.queue_name, j.priority " \
|
|
12
|
-
"FROM solid_queue_failed_executions fe " \
|
|
13
|
-
"JOIN solid_queue_jobs j ON j.id = fe.job_id " \
|
|
14
|
-
"WHERE fe.id = #{conn.quote(failed_execution_id.to_i)}"
|
|
15
|
-
)
|
|
16
|
-
return false unless row
|
|
17
|
-
|
|
18
|
-
conn.transaction do
|
|
19
|
-
# Create a ready execution for the job
|
|
20
|
-
conn.execute(
|
|
21
|
-
"INSERT INTO solid_queue_ready_executions (job_id, queue_name, priority, created_at) " \
|
|
22
|
-
"VALUES (#{conn.quote(row['job_id'])}, #{conn.quote(row['queue_name'])}, " \
|
|
23
|
-
"#{conn.quote(row['priority'])}, #{conn.quote(Time.now.utc.iso8601)})"
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
# Remove the failed execution
|
|
27
|
-
conn.execute(
|
|
28
|
-
"DELETE FROM solid_queue_failed_executions WHERE id = #{conn.quote(failed_execution_id.to_i)}"
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
# Clear finished_at on the job
|
|
32
|
-
conn.execute(
|
|
33
|
-
"UPDATE solid_queue_jobs SET finished_at = NULL " \
|
|
34
|
-
"WHERE id = #{conn.quote(row['job_id'])}"
|
|
35
|
-
)
|
|
36
|
-
end
|
|
37
|
-
|
|
7
|
+
fe = SolidQueue::FailedExecution.find(failed_execution_id)
|
|
8
|
+
fe.retry
|
|
38
9
|
true
|
|
10
|
+
rescue ActiveRecord::RecordNotFound
|
|
11
|
+
false
|
|
39
12
|
rescue => e
|
|
40
13
|
false
|
|
41
14
|
end
|
|
42
15
|
|
|
43
|
-
def self.retry_all
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
"JOIN solid_queue_jobs j ON j.id = fe.job_id"
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
count = 0
|
|
53
|
-
rows.each do |row|
|
|
54
|
-
conn.transaction do
|
|
55
|
-
conn.execute(
|
|
56
|
-
"INSERT INTO solid_queue_ready_executions (job_id, queue_name, priority, created_at) " \
|
|
57
|
-
"VALUES (#{conn.quote(row['job_id'])}, #{conn.quote(row['queue_name'])}, " \
|
|
58
|
-
"#{conn.quote(row['priority'])}, #{conn.quote(Time.now.utc.iso8601)})"
|
|
59
|
-
)
|
|
60
|
-
conn.execute(
|
|
61
|
-
"DELETE FROM solid_queue_failed_executions WHERE id = #{conn.quote(row['id'])}"
|
|
62
|
-
)
|
|
63
|
-
conn.execute(
|
|
64
|
-
"UPDATE solid_queue_jobs SET finished_at = NULL WHERE id = #{conn.quote(row['job_id'])}"
|
|
65
|
-
)
|
|
66
|
-
count += 1
|
|
67
|
-
end
|
|
68
|
-
rescue
|
|
69
|
-
next
|
|
70
|
-
end
|
|
16
|
+
def self.retry_all(filter: nil, queue: nil)
|
|
17
|
+
scope = SolidQueue::FailedExecution.joins(:job)
|
|
18
|
+
scope = scope.merge(SolidQueue::Job.where("class_name LIKE ?", "%#{filter}%")) if filter.present?
|
|
19
|
+
scope = scope.merge(SolidQueue::Job.where(queue_name: queue)) if queue.present?
|
|
20
|
+
count = scope.count
|
|
21
|
+
return 0 if count == 0
|
|
71
22
|
|
|
23
|
+
jobs = SolidQueue::Job.where(id: scope.select(:job_id))
|
|
24
|
+
SolidQueue::FailedExecution.retry_all(jobs)
|
|
72
25
|
count
|
|
26
|
+
rescue => e
|
|
27
|
+
0
|
|
73
28
|
end
|
|
74
29
|
end
|
|
75
30
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidQueueTui
|
|
4
|
+
module Actions
|
|
5
|
+
class ToggleQueuePause
|
|
6
|
+
def self.call(queue_name)
|
|
7
|
+
queue = SolidQueue::Queue.find_by_name(queue_name)
|
|
8
|
+
if queue.paused?
|
|
9
|
+
queue.resume
|
|
10
|
+
else
|
|
11
|
+
queue.pause
|
|
12
|
+
end
|
|
13
|
+
true
|
|
14
|
+
rescue => e
|
|
15
|
+
false
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -12,9 +12,10 @@ module SolidQueueTui
|
|
|
12
12
|
VIEW_BLOCKED = 4
|
|
13
13
|
VIEW_SCHEDULED = 5
|
|
14
14
|
VIEW_FINISHED = 6
|
|
15
|
-
|
|
15
|
+
VIEW_RECURRING = 7
|
|
16
|
+
VIEW_WORKERS = 8
|
|
16
17
|
|
|
17
|
-
VIEW_COUNT =
|
|
18
|
+
VIEW_COUNT = 9
|
|
18
19
|
|
|
19
20
|
COMMAND_MAP = {
|
|
20
21
|
"dashboard" => VIEW_DASHBOARD,
|
|
@@ -24,6 +25,7 @@ module SolidQueueTui
|
|
|
24
25
|
"blocked" => VIEW_BLOCKED,
|
|
25
26
|
"scheduled" => VIEW_SCHEDULED,
|
|
26
27
|
"finished" => VIEW_FINISHED,
|
|
28
|
+
"recurring" => VIEW_RECURRING,
|
|
27
29
|
"workers" => VIEW_WORKERS
|
|
28
30
|
}.freeze
|
|
29
31
|
|
|
@@ -39,8 +41,7 @@ module SolidQueueTui
|
|
|
39
41
|
end
|
|
40
42
|
|
|
41
43
|
def run
|
|
42
|
-
|
|
43
|
-
@refresh_interval = config.fetch("refresh", 2).to_i
|
|
44
|
+
@refresh_interval = SolidQueueTui.refresh_interval
|
|
44
45
|
setup_dev_reloader! if @dev
|
|
45
46
|
|
|
46
47
|
RatatuiRuby.run do |tui|
|
|
@@ -69,6 +70,7 @@ module SolidQueueTui
|
|
|
69
70
|
VIEW_BLOCKED => Views::BlockedView.new(@tui),
|
|
70
71
|
VIEW_SCHEDULED => Views::ScheduledView.new(@tui),
|
|
71
72
|
VIEW_FINISHED => Views::FinishedView.new(@tui),
|
|
73
|
+
VIEW_RECURRING => Views::RecurringTasksView.new(@tui),
|
|
72
74
|
VIEW_WORKERS => Views::ProcessesView.new(@tui)
|
|
73
75
|
}
|
|
74
76
|
@job_detail = Views::JobDetailView.new(@tui)
|
|
@@ -141,10 +143,17 @@ module SolidQueueTui
|
|
|
141
143
|
return false
|
|
142
144
|
end
|
|
143
145
|
|
|
144
|
-
# If view is in a modal state (filter, confirm), it gets all input
|
|
146
|
+
# If view is in a modal state (filter, confirm, detail sub-view), it gets all input
|
|
145
147
|
if current_view.respond_to?(:capturing_input?) && current_view.capturing_input?
|
|
146
148
|
result = current_view.handle_input(event)
|
|
147
|
-
|
|
149
|
+
case result
|
|
150
|
+
when :refresh, :enter_queue, :exit_queue
|
|
151
|
+
refresh_data!
|
|
152
|
+
when :load_more
|
|
153
|
+
load_more_data!
|
|
154
|
+
when :open_detail
|
|
155
|
+
open_detail
|
|
156
|
+
end
|
|
148
157
|
return false
|
|
149
158
|
end
|
|
150
159
|
|
|
@@ -191,11 +200,17 @@ module SolidQueueTui
|
|
|
191
200
|
switch_view(VIEW_FINISHED)
|
|
192
201
|
return false
|
|
193
202
|
in { type: :key, code: "8" }
|
|
203
|
+
switch_view(VIEW_RECURRING)
|
|
204
|
+
return false
|
|
205
|
+
in { type: :key, code: "9" }
|
|
194
206
|
switch_view(VIEW_WORKERS)
|
|
195
207
|
return false
|
|
196
208
|
in { type: :key, code: "tab" }
|
|
197
209
|
switch_view((@current_view + 1) % VIEW_COUNT)
|
|
198
210
|
return false
|
|
211
|
+
in { type: :key, code: "back_tab" }
|
|
212
|
+
switch_view((@current_view - 1) % VIEW_COUNT)
|
|
213
|
+
return false
|
|
199
214
|
in { type: :key, code: "enter" }
|
|
200
215
|
open_detail
|
|
201
216
|
return false
|
|
@@ -210,7 +225,11 @@ module SolidQueueTui
|
|
|
210
225
|
|
|
211
226
|
# Pass to current view
|
|
212
227
|
result = current_view.handle_input(event)
|
|
213
|
-
|
|
228
|
+
if result == :refresh
|
|
229
|
+
refresh_data!
|
|
230
|
+
elsif result == :load_more
|
|
231
|
+
load_more_data!
|
|
232
|
+
end
|
|
214
233
|
|
|
215
234
|
false
|
|
216
235
|
end
|
|
@@ -229,11 +248,21 @@ module SolidQueueTui
|
|
|
229
248
|
return unless item
|
|
230
249
|
|
|
231
250
|
case @current_view
|
|
251
|
+
when VIEW_QUEUES
|
|
252
|
+
if current_view.detail_mode?
|
|
253
|
+
@job_detail.show(job: item) if item.respond_to?(:id)
|
|
254
|
+
else
|
|
255
|
+
result = current_view.handle_input({ type: :key, code: "enter" })
|
|
256
|
+
refresh_data! if result == :enter_queue
|
|
257
|
+
end
|
|
232
258
|
when VIEW_FAILED
|
|
233
259
|
failed_job = Data::FailedQuery.fetch_one(item.id) if item.respond_to?(:id)
|
|
234
260
|
@job_detail.show(failed_job: failed_job || item)
|
|
235
261
|
when VIEW_IN_PROGRESS, VIEW_BLOCKED, VIEW_SCHEDULED, VIEW_FINISHED
|
|
236
262
|
@job_detail.show(job: item) if item.respond_to?(:id)
|
|
263
|
+
when VIEW_WORKERS
|
|
264
|
+
running_jobs = Data::ProcessesQuery.fetch_running_jobs(process_id: item.id)
|
|
265
|
+
@job_detail.show(process: item, running_jobs: running_jobs)
|
|
237
266
|
end
|
|
238
267
|
end
|
|
239
268
|
|
|
@@ -243,38 +272,94 @@ module SolidQueueTui
|
|
|
243
272
|
end
|
|
244
273
|
|
|
245
274
|
def refresh_data!
|
|
246
|
-
@stats = Data::Stats.fetch
|
|
247
275
|
@last_refresh = Time.now
|
|
248
276
|
|
|
249
277
|
case @current_view
|
|
250
278
|
when VIEW_DASHBOARD
|
|
279
|
+
@stats = Data::Stats.fetch
|
|
251
280
|
current_view.update(stats: @stats)
|
|
252
281
|
when VIEW_QUEUES
|
|
253
|
-
|
|
254
|
-
|
|
282
|
+
if current_view.detail_mode?
|
|
283
|
+
q = current_view.selected_queue_name
|
|
284
|
+
f = current_view.filters
|
|
285
|
+
current_view.total_count = Data::JobsQuery.count_pending(queue: q, filter: f[:class_name])
|
|
286
|
+
jobs = Data::JobsQuery.fetch_pending(queue: q, filter: f[:class_name], limit: SolidQueueTui.page_size, offset: 0)
|
|
287
|
+
current_view.update_detail(jobs: jobs)
|
|
288
|
+
else
|
|
289
|
+
queues = Data::QueuesQuery.fetch
|
|
290
|
+
current_view.update(queues: queues)
|
|
291
|
+
end
|
|
255
292
|
when VIEW_FAILED
|
|
256
|
-
|
|
257
|
-
|
|
293
|
+
f = current_view.filters
|
|
294
|
+
current_view.total_count = Data::FailedQuery.count(filter: f[:class_name], queue: f[:queue])
|
|
295
|
+
failed_jobs = Data::FailedQuery.fetch(filter: f[:class_name], queue: f[:queue], limit: SolidQueueTui.page_size, offset: 0)
|
|
258
296
|
current_view.update(failed_jobs: failed_jobs)
|
|
259
297
|
when VIEW_IN_PROGRESS
|
|
260
|
-
|
|
298
|
+
f = current_view.filters
|
|
299
|
+
current_view.total_count = Data::JobsQuery.count(status: "claimed", filter: f[:class_name], queue: f[:queue])
|
|
300
|
+
jobs = Data::JobsQuery.fetch(status: "claimed", filter: f[:class_name], queue: f[:queue], limit: SolidQueueTui.page_size, offset: 0)
|
|
261
301
|
current_view.update(jobs: jobs)
|
|
262
302
|
when VIEW_BLOCKED
|
|
263
|
-
|
|
303
|
+
f = current_view.filters
|
|
304
|
+
current_view.total_count = Data::JobsQuery.count(status: "blocked", filter: f[:class_name], queue: f[:queue])
|
|
305
|
+
jobs = Data::JobsQuery.fetch(status: "blocked", filter: f[:class_name], queue: f[:queue], limit: SolidQueueTui.page_size, offset: 0)
|
|
264
306
|
current_view.update(jobs: jobs)
|
|
265
307
|
when VIEW_SCHEDULED
|
|
266
|
-
|
|
308
|
+
f = current_view.filters
|
|
309
|
+
current_view.total_count = Data::JobsQuery.count(status: "scheduled", filter: f[:class_name], queue: f[:queue])
|
|
310
|
+
jobs = Data::JobsQuery.fetch(status: "scheduled", filter: f[:class_name], queue: f[:queue], limit: SolidQueueTui.page_size, offset: 0)
|
|
267
311
|
current_view.update(jobs: jobs)
|
|
268
312
|
when VIEW_FINISHED
|
|
269
|
-
|
|
270
|
-
|
|
313
|
+
f = current_view.filters
|
|
314
|
+
current_view.total_count = Data::JobsQuery.count(status: "completed", filter: f[:class_name], queue: f[:queue])
|
|
315
|
+
jobs = Data::JobsQuery.fetch(status: "completed", filter: f[:class_name], queue: f[:queue], limit: SolidQueueTui.page_size, offset: 0)
|
|
271
316
|
current_view.update(jobs: jobs)
|
|
317
|
+
when VIEW_RECURRING
|
|
318
|
+
tasks = Data::RecurringTasksQuery.fetch
|
|
319
|
+
current_view.update(tasks: tasks)
|
|
272
320
|
when VIEW_WORKERS
|
|
273
321
|
processes = Data::ProcessesQuery.fetch
|
|
274
322
|
current_view.update(processes: processes)
|
|
275
323
|
end
|
|
276
324
|
rescue => e
|
|
277
|
-
|
|
325
|
+
Rails.logger.tagged("SQTUI") { Rails.logger.error("refresh_data! error: #{e.class}: #{e.message}\n#{e.backtrace&.first(5)&.join("\n")}") } if defined?(Rails) && Rails.logger
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def load_more_data!
|
|
329
|
+
view = current_view
|
|
330
|
+
offset = view.current_offset
|
|
331
|
+
|
|
332
|
+
case @current_view
|
|
333
|
+
when VIEW_QUEUES
|
|
334
|
+
if view.detail_mode?
|
|
335
|
+
q = view.selected_queue_name
|
|
336
|
+
f = view.filters
|
|
337
|
+
more = Data::JobsQuery.fetch_pending(queue: q, filter: f[:class_name], limit: SolidQueueTui.page_size, offset: offset)
|
|
338
|
+
view.append(jobs: more)
|
|
339
|
+
end
|
|
340
|
+
when VIEW_FAILED
|
|
341
|
+
f = view.filters
|
|
342
|
+
more = Data::FailedQuery.fetch(filter: f[:class_name], queue: f[:queue], limit: SolidQueueTui.page_size, offset: offset)
|
|
343
|
+
view.append(failed_jobs: more)
|
|
344
|
+
when VIEW_IN_PROGRESS
|
|
345
|
+
f = view.filters
|
|
346
|
+
more = Data::JobsQuery.fetch(status: "claimed", filter: f[:class_name], queue: f[:queue], limit: SolidQueueTui.page_size, offset: offset)
|
|
347
|
+
view.append(jobs: more)
|
|
348
|
+
when VIEW_BLOCKED
|
|
349
|
+
f = view.filters
|
|
350
|
+
more = Data::JobsQuery.fetch(status: "blocked", filter: f[:class_name], queue: f[:queue], limit: SolidQueueTui.page_size, offset: offset)
|
|
351
|
+
view.append(jobs: more)
|
|
352
|
+
when VIEW_SCHEDULED
|
|
353
|
+
f = view.filters
|
|
354
|
+
more = Data::JobsQuery.fetch(status: "scheduled", filter: f[:class_name], queue: f[:queue], limit: SolidQueueTui.page_size, offset: offset)
|
|
355
|
+
view.append(jobs: more)
|
|
356
|
+
when VIEW_FINISHED
|
|
357
|
+
f = view.filters
|
|
358
|
+
more = Data::JobsQuery.fetch(status: "completed", filter: f[:class_name], queue: f[:queue], limit: SolidQueueTui.page_size, offset: offset)
|
|
359
|
+
view.append(jobs: more)
|
|
360
|
+
end
|
|
361
|
+
rescue => e
|
|
362
|
+
Rails.logger.tagged("SQTUI") { Rails.logger.error("load_more_data! error: #{e.class}: #{e.message}") } if defined?(Rails) && Rails.logger
|
|
278
363
|
end
|
|
279
364
|
|
|
280
365
|
def setup_dev_reloader!
|
|
@@ -393,8 +478,9 @@ module SolidQueueTui
|
|
|
393
478
|
]),
|
|
394
479
|
empty_line,
|
|
395
480
|
help_section("Navigation"),
|
|
396
|
-
help_line("1-
|
|
481
|
+
help_line("1-9", "Switch between views"),
|
|
397
482
|
help_line("Tab", "Next view"),
|
|
483
|
+
help_line("Shift + Tab", "Previous View"),
|
|
398
484
|
help_line(":", "Command mode (:queues, :failed, ...)"),
|
|
399
485
|
help_line("Esc", "Back to Dashboard"),
|
|
400
486
|
help_line("j / Up", "Move selection up"),
|
|
@@ -418,12 +504,14 @@ module SolidQueueTui
|
|
|
418
504
|
help_line("5", "Blocked — Concurrency-blocked jobs"),
|
|
419
505
|
help_line("6", "Scheduled — Future scheduled jobs"),
|
|
420
506
|
help_line("7", "Finished — Completed jobs"),
|
|
421
|
-
help_line("8", "
|
|
507
|
+
help_line("8", "Recurring — Recurring tasks"),
|
|
508
|
+
help_line("9", "Workers — Active processes"),
|
|
422
509
|
empty_line,
|
|
423
510
|
help_section("General"),
|
|
424
511
|
help_line("?", "Toggle this help"),
|
|
425
512
|
help_line("q", "Quit"),
|
|
426
|
-
help_line("Ctrl+C", "Force quit")
|
|
513
|
+
help_line("Ctrl+C", "Force quit"),
|
|
514
|
+
help_line("fn + select", "Select Text")
|
|
427
515
|
]
|
|
428
516
|
|
|
429
517
|
frame.render_widget(
|
data/lib/solid_queue_tui/cli.rb
CHANGED
|
@@ -7,9 +7,6 @@ module SolidQueueTui
|
|
|
7
7
|
def self.run(args)
|
|
8
8
|
options = parse_options(args)
|
|
9
9
|
Application.new(**options).run
|
|
10
|
-
rescue Connection::ConnectionError => e
|
|
11
|
-
$stderr.puts "Connection error: #{e.message}"
|
|
12
|
-
exit 1
|
|
13
10
|
rescue Interrupt
|
|
14
11
|
exit 0
|
|
15
12
|
end
|
|
@@ -22,10 +19,22 @@ module SolidQueueTui
|
|
|
22
19
|
opts.separator ""
|
|
23
20
|
opts.separator "Options:"
|
|
24
21
|
|
|
25
|
-
opts.on("--dev", "Enable hot-reload (
|
|
22
|
+
opts.on("--dev", "Enable hot-reload (development only)") do
|
|
23
|
+
unless defined?(Rails) && Rails.env.development?
|
|
24
|
+
$stderr.puts "Error: --dev is only allowed in the development environment."
|
|
25
|
+
exit 1
|
|
26
|
+
end
|
|
26
27
|
options[:dev] = true
|
|
27
28
|
end
|
|
28
29
|
|
|
30
|
+
opts.on("--page-size N", Integer, "Number of rows per page (default: #{SolidQueueTui.page_size})") do |n|
|
|
31
|
+
SolidQueueTui.page_size = n
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
opts.on("--refresh-interval N", Integer, "Refresh interval in seconds (default: #{SolidQueueTui.refresh_interval})") do |n|
|
|
35
|
+
SolidQueueTui.refresh_interval = n
|
|
36
|
+
end
|
|
37
|
+
|
|
29
38
|
opts.on("-v", "--version", "Show version") do
|
|
30
39
|
puts "sqtui v#{SolidQueueTui::VERSION}"
|
|
31
40
|
exit
|
|
@@ -34,10 +43,8 @@ module SolidQueueTui
|
|
|
34
43
|
opts.on("-h", "--help", "Show this help") do
|
|
35
44
|
puts opts
|
|
36
45
|
puts ""
|
|
37
|
-
puts "
|
|
38
|
-
puts "
|
|
39
|
-
puts " database_url: sqlite3:storage/queue.sqlite3"
|
|
40
|
-
puts " refresh: 2"
|
|
46
|
+
puts "Run from your Rails app root directory."
|
|
47
|
+
puts "Requires solid_queue_tui in your Gemfile and Solid Queue configured."
|
|
41
48
|
exit
|
|
42
49
|
end
|
|
43
50
|
end.parse!(args)
|
|
@@ -4,11 +4,11 @@ module SolidQueueTui
|
|
|
4
4
|
module Components
|
|
5
5
|
class Header
|
|
6
6
|
LOGO = [
|
|
7
|
-
" ____ _ _ _ ___
|
|
8
|
-
"/ ___| ___ | (_) __| | / _ \\
|
|
9
|
-
"\\___ \\ / _ \\| | |/ _` | | | |
|
|
10
|
-
" ___) | (_) | | | (_| | | |_|
|
|
11
|
-
"|____/ \\___/|_|_|\\__,_| \\
|
|
7
|
+
" ____ _ _ _ ___ ",
|
|
8
|
+
"/ ___| ___ | (_) __| | / _ \\ _ _ ___ _ _ ___",
|
|
9
|
+
"\\___ \\ / _ \\| | |/ _` | | | | || | | |/ _ \\ | | |/ _ \\",
|
|
10
|
+
" ___) | (_) | | | (_| | | |_| || |_| | __/ |_| | __/",
|
|
11
|
+
"|____/ \\___/|_|_|\\__,_| \\__\\_\\ \\__,_|\\___|\\__,_|\\___|"
|
|
12
12
|
].freeze
|
|
13
13
|
|
|
14
14
|
VIEWS = [
|
|
@@ -19,7 +19,8 @@ module SolidQueueTui
|
|
|
19
19
|
{ key: "5", label: "Blocked" },
|
|
20
20
|
{ key: "6", label: "Scheduled" },
|
|
21
21
|
{ key: "7", label: "Finished" },
|
|
22
|
-
{ key: "8", label: "
|
|
22
|
+
{ key: "8", label: "Recurring" },
|
|
23
|
+
{ key: "9", label: "Workers" }
|
|
23
24
|
].freeze
|
|
24
25
|
|
|
25
26
|
def initialize(tui, current_view:)
|
|
@@ -61,36 +62,34 @@ module SolidQueueTui
|
|
|
61
62
|
end
|
|
62
63
|
|
|
63
64
|
def render_nav(frame, area)
|
|
64
|
-
|
|
65
|
-
@tui.text_span(content: " ", style: @tui.style(fg: :white))
|
|
66
|
-
]
|
|
65
|
+
tab_spans = []
|
|
67
66
|
|
|
68
67
|
VIEWS.each_with_index do |view, idx|
|
|
69
68
|
active = idx == @current_view
|
|
70
69
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
70
|
+
if active
|
|
71
|
+
tab_spans << @tui.text_span(
|
|
72
|
+
content: " #{view[:key]}·#{view[:label]} ",
|
|
73
|
+
style: @tui.style(fg: :cyan, modifiers: [:bold, :underlined])
|
|
74
|
+
)
|
|
75
|
+
else
|
|
76
|
+
tab_spans << @tui.text_span(
|
|
77
|
+
content: " #{view[:key]}·#{view[:label]} ",
|
|
78
|
+
style: @tui.style(fg: :dark_gray)
|
|
80
79
|
)
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
end
|
|
81
|
+
tab_spans << @tui.text_span(content: " ", style: @tui.style(fg: :white))
|
|
83
82
|
end
|
|
84
83
|
|
|
85
84
|
lines = [
|
|
86
|
-
@tui.text_line(spans:
|
|
85
|
+
@tui.text_line(spans: tab_spans, alignment: :right),
|
|
87
86
|
@tui.text_line(spans: [
|
|
88
|
-
@tui.text_span(content: "<?>", style: @tui.style(fg: :cyan)),
|
|
89
|
-
@tui.text_span(content: "
|
|
90
|
-
@tui.text_span(content: "<q>", style: @tui.style(fg: :cyan)),
|
|
91
|
-
@tui.text_span(content: "
|
|
92
|
-
@tui.text_span(content: "<r>", style: @tui.style(fg: :cyan)),
|
|
93
|
-
@tui.text_span(content: "
|
|
87
|
+
@tui.text_span(content: "<?> ", style: @tui.style(fg: :cyan)),
|
|
88
|
+
@tui.text_span(content: "Help ", style: @tui.style(fg: :dark_gray)),
|
|
89
|
+
@tui.text_span(content: "<q> ", style: @tui.style(fg: :cyan)),
|
|
90
|
+
@tui.text_span(content: "Quit ", style: @tui.style(fg: :dark_gray)),
|
|
91
|
+
@tui.text_span(content: "<r> ", style: @tui.style(fg: :cyan)),
|
|
92
|
+
@tui.text_span(content: "Refresh", style: @tui.style(fg: :dark_gray))
|
|
94
93
|
], alignment: :right)
|
|
95
94
|
]
|
|
96
95
|
|
|
@@ -7,6 +7,7 @@ module SolidQueueTui
|
|
|
7
7
|
{ key: "q", action: "Quit" },
|
|
8
8
|
{ key: "r", action: "Refresh" },
|
|
9
9
|
{ key: "Tab", action: "Next View" },
|
|
10
|
+
{ Key: "Shift + Tab", action: "Previous View"},
|
|
10
11
|
{ key: "j/k", action: "Navigate" },
|
|
11
12
|
{ key: "/", action: "Filter" },
|
|
12
13
|
{ key: "Esc", action: "Clear" }
|