solid_queue_tui 0.1.1 → 0.1.2
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 +20 -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 +73 -17
- 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 +14 -2
- data/lib/solid_queue_tui/data/failed_query.rb +37 -91
- data/lib/solid_queue_tui/data/jobs_query.rb +90 -123
- data/lib/solid_queue_tui/data/processes_query.rb +10 -34
- 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/railtie.rb +9 -0
- data/lib/solid_queue_tui/version.rb +1 -1
- data/lib/solid_queue_tui/views/blocked_view.rb +126 -46
- data/lib/solid_queue_tui/views/concerns/filterable.rb +128 -0
- data/lib/solid_queue_tui/views/dashboard_view.rb +2 -1
- data/lib/solid_queue_tui/views/failed_view.rb +62 -72
- data/lib/solid_queue_tui/views/finished_view.rb +54 -67
- data/lib/solid_queue_tui/views/in_progress_view.rb +124 -44
- data/lib/solid_queue_tui/views/queues_view.rb +119 -35
- data/lib/solid_queue_tui/views/recurring_tasks_view.rb +202 -0
- data/lib/solid_queue_tui/views/scheduled_view.rb +74 -8
- data/lib/solid_queue_tui.rb +15 -4
- data/lib/tasks/solid_queue_tui.rake +8 -0
- metadata +16 -24
- 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: 362a8f6c4d4bd257ed5bdc4a7eaf93fb986b978313a5847bffd3dfe8e9f963ed
|
|
4
|
+
data.tar.gz: 9aabeac6f1fa7404ff7a485b2a47669ed1313244348fc29dc4bc34a751ab9bd8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7de0862123508768172c46a17458c55651b6bda487d79e76f7e61933d223e29e848ba0614b4fc116acc8222dc5a245ac590fc92cff595b3c65c67367537589c1
|
|
7
|
+
data.tar.gz: b8d145ba61da8f1de1fe3a4c1b3678e06ca36e19e6e4622acc67942adf194228eb6c96e09cfa114168cc8c3b3b28140ffd2d53b05d653081394c89406fdf6648
|
data/exe/sqtui
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
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
|
+
ActiveRecord::Base.logger = Rails.logger
|
|
23
|
+
Rails.logger.push_tags("SQTUI") if Rails.logger.respond_to?(:push_tags)
|
|
5
24
|
|
|
6
25
|
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)
|
|
@@ -191,11 +193,17 @@ module SolidQueueTui
|
|
|
191
193
|
switch_view(VIEW_FINISHED)
|
|
192
194
|
return false
|
|
193
195
|
in { type: :key, code: "8" }
|
|
196
|
+
switch_view(VIEW_RECURRING)
|
|
197
|
+
return false
|
|
198
|
+
in { type: :key, code: "9" }
|
|
194
199
|
switch_view(VIEW_WORKERS)
|
|
195
200
|
return false
|
|
196
201
|
in { type: :key, code: "tab" }
|
|
197
202
|
switch_view((@current_view + 1) % VIEW_COUNT)
|
|
198
203
|
return false
|
|
204
|
+
in { type: :key, code: "back_tab" }
|
|
205
|
+
switch_view((@current_view - 1) % VIEW_COUNT)
|
|
206
|
+
return false
|
|
199
207
|
in { type: :key, code: "enter" }
|
|
200
208
|
open_detail
|
|
201
209
|
return false
|
|
@@ -210,7 +218,11 @@ module SolidQueueTui
|
|
|
210
218
|
|
|
211
219
|
# Pass to current view
|
|
212
220
|
result = current_view.handle_input(event)
|
|
213
|
-
|
|
221
|
+
if result == :refresh
|
|
222
|
+
refresh_data!
|
|
223
|
+
elsif result == :load_more
|
|
224
|
+
load_more_data!
|
|
225
|
+
end
|
|
214
226
|
|
|
215
227
|
false
|
|
216
228
|
end
|
|
@@ -243,38 +255,79 @@ module SolidQueueTui
|
|
|
243
255
|
end
|
|
244
256
|
|
|
245
257
|
def refresh_data!
|
|
246
|
-
@stats = Data::Stats.fetch
|
|
247
258
|
@last_refresh = Time.now
|
|
248
259
|
|
|
249
260
|
case @current_view
|
|
250
261
|
when VIEW_DASHBOARD
|
|
262
|
+
@stats = Data::Stats.fetch
|
|
251
263
|
current_view.update(stats: @stats)
|
|
252
264
|
when VIEW_QUEUES
|
|
253
265
|
queues = Data::QueuesQuery.fetch
|
|
254
266
|
current_view.update(queues: queues)
|
|
255
267
|
when VIEW_FAILED
|
|
256
|
-
|
|
257
|
-
|
|
268
|
+
f = current_view.filters
|
|
269
|
+
current_view.total_count = Data::FailedQuery.count(filter: f[:class_name], queue: f[:queue])
|
|
270
|
+
failed_jobs = Data::FailedQuery.fetch(filter: f[:class_name], queue: f[:queue], limit: SolidQueueTui.page_size, offset: 0)
|
|
258
271
|
current_view.update(failed_jobs: failed_jobs)
|
|
259
272
|
when VIEW_IN_PROGRESS
|
|
260
|
-
|
|
273
|
+
f = current_view.filters
|
|
274
|
+
current_view.total_count = Data::JobsQuery.count(status: "claimed", filter: f[:class_name], queue: f[:queue])
|
|
275
|
+
jobs = Data::JobsQuery.fetch(status: "claimed", filter: f[:class_name], queue: f[:queue], limit: SolidQueueTui.page_size, offset: 0)
|
|
261
276
|
current_view.update(jobs: jobs)
|
|
262
277
|
when VIEW_BLOCKED
|
|
263
|
-
|
|
278
|
+
f = current_view.filters
|
|
279
|
+
current_view.total_count = Data::JobsQuery.count(status: "blocked", filter: f[:class_name], queue: f[:queue])
|
|
280
|
+
jobs = Data::JobsQuery.fetch(status: "blocked", filter: f[:class_name], queue: f[:queue], limit: SolidQueueTui.page_size, offset: 0)
|
|
264
281
|
current_view.update(jobs: jobs)
|
|
265
282
|
when VIEW_SCHEDULED
|
|
266
|
-
|
|
283
|
+
f = current_view.filters
|
|
284
|
+
current_view.total_count = Data::JobsQuery.count(status: "scheduled", filter: f[:class_name], queue: f[:queue])
|
|
285
|
+
jobs = Data::JobsQuery.fetch(status: "scheduled", filter: f[:class_name], queue: f[:queue], limit: SolidQueueTui.page_size, offset: 0)
|
|
267
286
|
current_view.update(jobs: jobs)
|
|
268
287
|
when VIEW_FINISHED
|
|
269
|
-
|
|
270
|
-
|
|
288
|
+
f = current_view.filters
|
|
289
|
+
current_view.total_count = Data::JobsQuery.count(status: "completed", filter: f[:class_name], queue: f[:queue])
|
|
290
|
+
jobs = Data::JobsQuery.fetch(status: "completed", filter: f[:class_name], queue: f[:queue], limit: SolidQueueTui.page_size, offset: 0)
|
|
271
291
|
current_view.update(jobs: jobs)
|
|
292
|
+
when VIEW_RECURRING
|
|
293
|
+
tasks = Data::RecurringTasksQuery.fetch
|
|
294
|
+
current_view.update(tasks: tasks)
|
|
272
295
|
when VIEW_WORKERS
|
|
273
296
|
processes = Data::ProcessesQuery.fetch
|
|
274
297
|
current_view.update(processes: processes)
|
|
275
298
|
end
|
|
276
299
|
rescue => e
|
|
277
|
-
|
|
300
|
+
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
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def load_more_data!
|
|
304
|
+
view = current_view
|
|
305
|
+
offset = view.current_offset
|
|
306
|
+
|
|
307
|
+
case @current_view
|
|
308
|
+
when VIEW_FAILED
|
|
309
|
+
f = view.filters
|
|
310
|
+
more = Data::FailedQuery.fetch(filter: f[:class_name], queue: f[:queue], limit: SolidQueueTui.page_size, offset: offset)
|
|
311
|
+
view.append(failed_jobs: more)
|
|
312
|
+
when VIEW_IN_PROGRESS
|
|
313
|
+
f = view.filters
|
|
314
|
+
more = Data::JobsQuery.fetch(status: "claimed", filter: f[:class_name], queue: f[:queue], limit: SolidQueueTui.page_size, offset: offset)
|
|
315
|
+
view.append(jobs: more)
|
|
316
|
+
when VIEW_BLOCKED
|
|
317
|
+
f = view.filters
|
|
318
|
+
more = Data::JobsQuery.fetch(status: "blocked", filter: f[:class_name], queue: f[:queue], limit: SolidQueueTui.page_size, offset: offset)
|
|
319
|
+
view.append(jobs: more)
|
|
320
|
+
when VIEW_SCHEDULED
|
|
321
|
+
f = view.filters
|
|
322
|
+
more = Data::JobsQuery.fetch(status: "scheduled", filter: f[:class_name], queue: f[:queue], limit: SolidQueueTui.page_size, offset: offset)
|
|
323
|
+
view.append(jobs: more)
|
|
324
|
+
when VIEW_FINISHED
|
|
325
|
+
f = view.filters
|
|
326
|
+
more = Data::JobsQuery.fetch(status: "completed", filter: f[:class_name], queue: f[:queue], limit: SolidQueueTui.page_size, offset: offset)
|
|
327
|
+
view.append(jobs: more)
|
|
328
|
+
end
|
|
329
|
+
rescue => e
|
|
330
|
+
Rails.logger.tagged("SQTUI") { Rails.logger.error("load_more_data! error: #{e.class}: #{e.message}") } if defined?(Rails) && Rails.logger
|
|
278
331
|
end
|
|
279
332
|
|
|
280
333
|
def setup_dev_reloader!
|
|
@@ -393,8 +446,9 @@ module SolidQueueTui
|
|
|
393
446
|
]),
|
|
394
447
|
empty_line,
|
|
395
448
|
help_section("Navigation"),
|
|
396
|
-
help_line("1-
|
|
449
|
+
help_line("1-9", "Switch between views"),
|
|
397
450
|
help_line("Tab", "Next view"),
|
|
451
|
+
help_line("Shift + Tab", "Previous View"),
|
|
398
452
|
help_line(":", "Command mode (:queues, :failed, ...)"),
|
|
399
453
|
help_line("Esc", "Back to Dashboard"),
|
|
400
454
|
help_line("j / Up", "Move selection up"),
|
|
@@ -418,12 +472,14 @@ module SolidQueueTui
|
|
|
418
472
|
help_line("5", "Blocked — Concurrency-blocked jobs"),
|
|
419
473
|
help_line("6", "Scheduled — Future scheduled jobs"),
|
|
420
474
|
help_line("7", "Finished — Completed jobs"),
|
|
421
|
-
help_line("8", "
|
|
475
|
+
help_line("8", "Recurring — Recurring tasks"),
|
|
476
|
+
help_line("9", "Workers — Active processes"),
|
|
422
477
|
empty_line,
|
|
423
478
|
help_section("General"),
|
|
424
479
|
help_line("?", "Toggle this help"),
|
|
425
480
|
help_line("q", "Quit"),
|
|
426
|
-
help_line("Ctrl+C", "Force quit")
|
|
481
|
+
help_line("Ctrl+C", "Force quit"),
|
|
482
|
+
help_line("fn + select", "Select Text")
|
|
427
483
|
]
|
|
428
484
|
|
|
429
485
|
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" }
|
|
@@ -12,15 +12,18 @@ module SolidQueueTui
|
|
|
12
12
|
"completed" => :dark_gray,
|
|
13
13
|
"active" => :green,
|
|
14
14
|
"paused" => :red,
|
|
15
|
+
"delayed" => :red,
|
|
16
|
+
"pending" => :dark_gray,
|
|
15
17
|
"unknown" => :white
|
|
16
18
|
}.freeze
|
|
17
19
|
|
|
18
|
-
def initialize(tui, title:, columns:, rows:, selected_row: nil, empty_message: "No data")
|
|
20
|
+
def initialize(tui, title:, columns:, rows:, selected_row: nil, total_count: nil, empty_message: "No data")
|
|
19
21
|
@tui = tui
|
|
20
22
|
@title = title
|
|
21
23
|
@columns = columns
|
|
22
24
|
@rows = rows
|
|
23
25
|
@selected_row = selected_row
|
|
26
|
+
@total_count = total_count
|
|
24
27
|
@empty_message = empty_message
|
|
25
28
|
end
|
|
26
29
|
|
|
@@ -36,11 +39,20 @@ module SolidQueueTui
|
|
|
36
39
|
private
|
|
37
40
|
|
|
38
41
|
def title_text
|
|
39
|
-
|
|
42
|
+
count_text = if @total_count && @total_count > @rows.size
|
|
43
|
+
"#{@rows.size}/#{format_number(@total_count)}"
|
|
44
|
+
else
|
|
45
|
+
@rows.size.to_s
|
|
46
|
+
end
|
|
47
|
+
text = " #{@title} [#{count_text}]"
|
|
40
48
|
text += " #{@selected_row + 1}/#{@rows.size}" if @selected_row && @rows.size > 0
|
|
41
49
|
text + " "
|
|
42
50
|
end
|
|
43
51
|
|
|
52
|
+
def format_number(n)
|
|
53
|
+
n.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
|
|
54
|
+
end
|
|
55
|
+
|
|
44
56
|
def render_table(frame, area, table_state)
|
|
45
57
|
widths = @columns.map do |col|
|
|
46
58
|
case col[:width]
|