solid_queue_tui 0.1.2 → 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 +19 -2
- data/lib/solid_queue_tui/application.rb +36 -4
- data/lib/solid_queue_tui/components/job_table.rb +2 -3
- data/lib/solid_queue_tui/data/jobs_query.rb +31 -0
- data/lib/solid_queue_tui/data/processes_query.rb +23 -0
- data/lib/solid_queue_tui/formatting_helpers.rb +63 -0
- data/lib/solid_queue_tui/version.rb +1 -1
- data/lib/solid_queue_tui/views/blocked_view.rb +6 -75
- data/lib/solid_queue_tui/views/concerns/confirmable.rb +53 -0
- data/lib/solid_queue_tui/views/concerns/paginatable.rb +79 -0
- data/lib/solid_queue_tui/views/dashboard_view.rb +2 -4
- data/lib/solid_queue_tui/views/failed_view.rb +45 -149
- data/lib/solid_queue_tui/views/finished_view.rb +9 -77
- data/lib/solid_queue_tui/views/in_progress_view.rb +6 -70
- 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 +223 -87
- data/lib/solid_queue_tui/views/recurring_tasks_view.rb +22 -69
- data/lib/solid_queue_tui/views/scheduled_view.rb +36 -140
- data/lib/solid_queue_tui.rb +3 -0
- metadata +6 -3
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
|
@@ -19,7 +19,24 @@ require env_file
|
|
|
19
19
|
# Log ActiveRecord queries to the Rails log file so TUI operations
|
|
20
20
|
# are visible in log/development.log (or whichever environment is active).
|
|
21
21
|
# Tag all TUI logs with [SQTUI] so they're easy to distinguish from app logs.
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
24
41
|
|
|
25
42
|
SolidQueueTui::CLI.run(ARGV)
|
|
@@ -143,10 +143,17 @@ module SolidQueueTui
|
|
|
143
143
|
return false
|
|
144
144
|
end
|
|
145
145
|
|
|
146
|
-
# 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
|
|
147
147
|
if current_view.respond_to?(:capturing_input?) && current_view.capturing_input?
|
|
148
148
|
result = current_view.handle_input(event)
|
|
149
|
-
|
|
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
|
|
150
157
|
return false
|
|
151
158
|
end
|
|
152
159
|
|
|
@@ -241,11 +248,21 @@ module SolidQueueTui
|
|
|
241
248
|
return unless item
|
|
242
249
|
|
|
243
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
|
|
244
258
|
when VIEW_FAILED
|
|
245
259
|
failed_job = Data::FailedQuery.fetch_one(item.id) if item.respond_to?(:id)
|
|
246
260
|
@job_detail.show(failed_job: failed_job || item)
|
|
247
261
|
when VIEW_IN_PROGRESS, VIEW_BLOCKED, VIEW_SCHEDULED, VIEW_FINISHED
|
|
248
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)
|
|
249
266
|
end
|
|
250
267
|
end
|
|
251
268
|
|
|
@@ -262,8 +279,16 @@ module SolidQueueTui
|
|
|
262
279
|
@stats = Data::Stats.fetch
|
|
263
280
|
current_view.update(stats: @stats)
|
|
264
281
|
when VIEW_QUEUES
|
|
265
|
-
|
|
266
|
-
|
|
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
|
|
267
292
|
when VIEW_FAILED
|
|
268
293
|
f = current_view.filters
|
|
269
294
|
current_view.total_count = Data::FailedQuery.count(filter: f[:class_name], queue: f[:queue])
|
|
@@ -305,6 +330,13 @@ module SolidQueueTui
|
|
|
305
330
|
offset = view.current_offset
|
|
306
331
|
|
|
307
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
|
|
308
340
|
when VIEW_FAILED
|
|
309
341
|
f = view.filters
|
|
310
342
|
more = Data::FailedQuery.fetch(filter: f[:class_name], queue: f[:queue], limit: SolidQueueTui.page_size, offset: offset)
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
module SolidQueueTui
|
|
4
4
|
module Components
|
|
5
5
|
class JobTable
|
|
6
|
+
include FormattingHelpers
|
|
7
|
+
|
|
6
8
|
STATUS_COLORS = {
|
|
7
9
|
"ready" => :green,
|
|
8
10
|
"claimed" => :yellow,
|
|
@@ -49,9 +51,6 @@ module SolidQueueTui
|
|
|
49
51
|
text + " "
|
|
50
52
|
end
|
|
51
53
|
|
|
52
|
-
def format_number(n)
|
|
53
|
-
n.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
|
|
54
|
-
end
|
|
55
54
|
|
|
56
55
|
def render_table(frame, area, table_state)
|
|
57
56
|
widths = @columns.map do |col|
|
|
@@ -13,6 +13,7 @@ module SolidQueueTui
|
|
|
13
13
|
|
|
14
14
|
def self.fetch(status:, filter: nil, queue: nil, limit: 100, offset: 0)
|
|
15
15
|
case status
|
|
16
|
+
when "pending" then fetch_pending(queue: queue, filter: filter, limit: limit, offset: offset)
|
|
16
17
|
when "claimed" then fetch_claimed(filter: filter, queue: queue, limit: limit, offset: offset)
|
|
17
18
|
when "blocked" then fetch_blocked(filter: filter, queue: queue, limit: limit, offset: offset)
|
|
18
19
|
when "scheduled" then fetch_scheduled(filter: filter, queue: queue, limit: limit, offset: offset)
|
|
@@ -25,6 +26,7 @@ module SolidQueueTui
|
|
|
25
26
|
|
|
26
27
|
def self.count(status:, filter: nil, queue: nil)
|
|
27
28
|
case status
|
|
29
|
+
when "pending" then count_pending(queue: queue, filter: filter)
|
|
28
30
|
when "claimed" then count_scope(SolidQueue::ClaimedExecution.joins(:job), filter: filter, queue: queue)
|
|
29
31
|
when "blocked" then count_scope(SolidQueue::BlockedExecution.joins(:job), filter: filter, queue: queue)
|
|
30
32
|
when "scheduled" then count_scope(SolidQueue::ScheduledExecution.joins(:job), filter: filter, queue: queue)
|
|
@@ -35,6 +37,35 @@ module SolidQueueTui
|
|
|
35
37
|
0
|
|
36
38
|
end
|
|
37
39
|
|
|
40
|
+
def self.fetch_pending(queue:, filter: nil, limit: 100, offset: 0)
|
|
41
|
+
scope = SolidQueue::ReadyExecution.joins(:job)
|
|
42
|
+
scope = scope.where(queue_name: queue) if queue
|
|
43
|
+
scope = apply_class_name_filter(scope, filter)
|
|
44
|
+
scope = scope.order(priority: :asc, job_id: :asc).offset(offset).limit(limit)
|
|
45
|
+
|
|
46
|
+
scope.includes(:job).map do |re|
|
|
47
|
+
job = re.job
|
|
48
|
+
Job.new(
|
|
49
|
+
id: job.id,
|
|
50
|
+
queue_name: job.queue_name,
|
|
51
|
+
class_name: job.class_name,
|
|
52
|
+
priority: job.priority,
|
|
53
|
+
status: "pending",
|
|
54
|
+
active_job_id: job.active_job_id,
|
|
55
|
+
arguments: job.arguments,
|
|
56
|
+
scheduled_at: job.scheduled_at,
|
|
57
|
+
created_at: job.created_at
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def self.count_pending(queue:, filter: nil)
|
|
63
|
+
scope = SolidQueue::ReadyExecution.joins(:job)
|
|
64
|
+
scope = scope.where(queue_name: queue) if queue
|
|
65
|
+
scope = apply_class_name_filter(scope, filter)
|
|
66
|
+
scope.count
|
|
67
|
+
end
|
|
68
|
+
|
|
38
69
|
def self.fetch_claimed(filter: nil, queue: nil, limit: 100, offset: 0)
|
|
39
70
|
scope = SolidQueue::ClaimedExecution.joins(:job)
|
|
40
71
|
scope = scope.merge(SolidQueue::Job.where(queue_name: queue)) if queue
|
|
@@ -29,6 +29,29 @@ module SolidQueueTui
|
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
RunningJob = Struct.new(
|
|
33
|
+
:job_id, :class_name, :queue_name, :started_at,
|
|
34
|
+
keyword_init: true
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
def self.fetch_running_jobs(process_id:)
|
|
38
|
+
SolidQueue::ClaimedExecution
|
|
39
|
+
.where(process_id: process_id)
|
|
40
|
+
.joins(:job).includes(:job)
|
|
41
|
+
.order(:created_at)
|
|
42
|
+
.map do |ce|
|
|
43
|
+
job = ce.job
|
|
44
|
+
RunningJob.new(
|
|
45
|
+
job_id: job.id,
|
|
46
|
+
class_name: job.class_name,
|
|
47
|
+
queue_name: job.queue_name,
|
|
48
|
+
started_at: ce.created_at
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
rescue => e
|
|
52
|
+
[]
|
|
53
|
+
end
|
|
54
|
+
|
|
32
55
|
def self.fetch
|
|
33
56
|
SolidQueue::Process.where(kind: "Worker").order(:id).map do |proc|
|
|
34
57
|
Process.new(
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidQueueTui
|
|
4
|
+
module FormattingHelpers
|
|
5
|
+
def time_ago(time)
|
|
6
|
+
return "n/a" unless time
|
|
7
|
+
seconds = (Time.now.utc - time).to_i
|
|
8
|
+
case seconds
|
|
9
|
+
when 0..59 then "#{seconds}s ago"
|
|
10
|
+
when 60..3599 then "#{seconds / 60}m ago"
|
|
11
|
+
when 3600..86399 then "#{seconds / 3600}h ago"
|
|
12
|
+
else "#{seconds / 86400}d ago"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def format_time(time)
|
|
17
|
+
return "n/a" unless time
|
|
18
|
+
time.strftime("%Y-%m-%d %H:%M:%S")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def format_duration(seconds)
|
|
22
|
+
return "n/a" unless seconds
|
|
23
|
+
seconds = seconds.to_i
|
|
24
|
+
if seconds < 1
|
|
25
|
+
"<1s"
|
|
26
|
+
elsif seconds < 60
|
|
27
|
+
"#{seconds}s"
|
|
28
|
+
elsif seconds < 3600
|
|
29
|
+
"#{seconds / 60}m #{seconds % 60}s"
|
|
30
|
+
elsif seconds < 86400
|
|
31
|
+
"#{seconds / 3600}h #{(seconds % 3600) / 60}m"
|
|
32
|
+
else
|
|
33
|
+
"#{seconds / 86400}d #{(seconds % 86400) / 3600}h"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def format_number(n)
|
|
38
|
+
return "0" if n.nil? || n == 0
|
|
39
|
+
n.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def truncate(str, max)
|
|
43
|
+
return "" unless str
|
|
44
|
+
str.length > max ? "#{str[0...max - 3]}..." : str
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def humanize_duration(seconds)
|
|
48
|
+
case seconds.abs
|
|
49
|
+
when 0..59 then "#{seconds.abs}s"
|
|
50
|
+
when 60..3599 then "#{seconds.abs / 60}m"
|
|
51
|
+
when 3600..86399 then "#{seconds.abs / 3600}h"
|
|
52
|
+
else "#{seconds.abs / 86400}d"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def time_until(time)
|
|
57
|
+
return "n/a" unless time
|
|
58
|
+
seconds = (time - Time.now.utc).to_i
|
|
59
|
+
return "now" if seconds <= 0
|
|
60
|
+
"in #{humanize_duration(seconds)}"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -4,47 +4,21 @@ module SolidQueueTui
|
|
|
4
4
|
module Views
|
|
5
5
|
class BlockedView
|
|
6
6
|
include Filterable
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
LOAD_THRESHOLD = 10
|
|
7
|
+
include Paginatable
|
|
8
|
+
include FormattingHelpers
|
|
10
9
|
|
|
11
10
|
def initialize(tui)
|
|
12
11
|
@tui = tui
|
|
13
|
-
|
|
14
|
-
@table_state.select(0)
|
|
15
|
-
@selected_row = 0
|
|
16
|
-
@jobs = []
|
|
17
|
-
@total_count = nil
|
|
18
|
-
@all_loaded = false
|
|
12
|
+
init_pagination
|
|
19
13
|
init_filter
|
|
20
14
|
end
|
|
21
15
|
|
|
22
16
|
def update(jobs:)
|
|
23
|
-
|
|
24
|
-
@all_loaded = jobs.size < SolidQueueTui.page_size
|
|
25
|
-
@selected_row = @selected_row.clamp(0, [@jobs.size - 1, 0].max)
|
|
26
|
-
@table_state.select(@selected_row)
|
|
17
|
+
update_items(jobs)
|
|
27
18
|
end
|
|
28
19
|
|
|
29
20
|
def append(jobs:)
|
|
30
|
-
|
|
31
|
-
@all_loaded = jobs.size < SolidQueueTui.page_size
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def total_count=(count)
|
|
35
|
-
@total_count = count
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def current_offset
|
|
39
|
-
@jobs.size
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def reset_pagination!
|
|
43
|
-
@jobs = []
|
|
44
|
-
@total_count = nil
|
|
45
|
-
@all_loaded = false
|
|
46
|
-
@selected_row = 0
|
|
47
|
-
@table_state.select(0)
|
|
21
|
+
append_items(jobs)
|
|
48
22
|
end
|
|
49
23
|
|
|
50
24
|
def render(frame, area)
|
|
@@ -72,11 +46,6 @@ module SolidQueueTui
|
|
|
72
46
|
end
|
|
73
47
|
end
|
|
74
48
|
|
|
75
|
-
def selected_item
|
|
76
|
-
return nil if @jobs.empty? || @selected_row >= @jobs.size
|
|
77
|
-
@jobs[@selected_row]
|
|
78
|
-
end
|
|
79
|
-
|
|
80
49
|
def bindings
|
|
81
50
|
if filter_mode?
|
|
82
51
|
filter_bindings
|
|
@@ -100,10 +69,6 @@ module SolidQueueTui
|
|
|
100
69
|
|
|
101
70
|
private
|
|
102
71
|
|
|
103
|
-
def needs_more?
|
|
104
|
-
!@all_loaded && @selected_row >= @jobs.size - LOAD_THRESHOLD
|
|
105
|
-
end
|
|
106
|
-
|
|
107
72
|
def handle_normal_input(event)
|
|
108
73
|
case event
|
|
109
74
|
in { type: :key, code: "j" } | { type: :key, code: "up" }
|
|
@@ -126,25 +91,6 @@ module SolidQueueTui
|
|
|
126
91
|
end
|
|
127
92
|
end
|
|
128
93
|
|
|
129
|
-
def move_selection(delta)
|
|
130
|
-
return if @jobs.empty?
|
|
131
|
-
@selected_row = (@selected_row + delta).clamp(0, @jobs.size - 1)
|
|
132
|
-
@table_state.select(@selected_row)
|
|
133
|
-
:load_more if needs_more?
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
def jump_to_top
|
|
137
|
-
@selected_row = 0
|
|
138
|
-
@table_state.select(0)
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
def jump_to_bottom
|
|
142
|
-
return if @jobs.empty?
|
|
143
|
-
@selected_row = @jobs.size - 1
|
|
144
|
-
@table_state.select(@selected_row)
|
|
145
|
-
return :load_more if needs_more?
|
|
146
|
-
end
|
|
147
|
-
|
|
148
94
|
def render_table(frame, area)
|
|
149
95
|
columns = [
|
|
150
96
|
{ key: :id, label: "ID", width: 8 },
|
|
@@ -156,7 +102,7 @@ module SolidQueueTui
|
|
|
156
102
|
{ key: :blocked_since, label: "BLOCKED SINCE", width: 14 }
|
|
157
103
|
]
|
|
158
104
|
|
|
159
|
-
rows =
|
|
105
|
+
rows = items.map do |job|
|
|
160
106
|
{
|
|
161
107
|
id: job.id,
|
|
162
108
|
queue_name: job.queue_name,
|
|
@@ -181,21 +127,6 @@ module SolidQueueTui
|
|
|
181
127
|
table.render(frame, area, @table_state)
|
|
182
128
|
end
|
|
183
129
|
|
|
184
|
-
def format_time(time)
|
|
185
|
-
return "n/a" unless time
|
|
186
|
-
time.strftime("%Y-%m-%d %H:%M:%S")
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
def time_ago(time)
|
|
190
|
-
return "n/a" unless time
|
|
191
|
-
seconds = (Time.now.utc - time).to_i
|
|
192
|
-
case seconds
|
|
193
|
-
when 0..59 then "#{seconds}s ago"
|
|
194
|
-
when 60..3599 then "#{seconds / 60}m ago"
|
|
195
|
-
when 3600..86399 then "#{seconds / 3600}h ago"
|
|
196
|
-
else "#{seconds / 86400}d ago"
|
|
197
|
-
end
|
|
198
|
-
end
|
|
199
130
|
end
|
|
200
131
|
end
|
|
201
132
|
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidQueueTui
|
|
4
|
+
module Views
|
|
5
|
+
module Confirmable
|
|
6
|
+
def init_confirm
|
|
7
|
+
@confirm_action = nil
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def confirm_mode? = !!@confirm_action
|
|
11
|
+
|
|
12
|
+
def confirm_bindings
|
|
13
|
+
[{ key: "y", action: "Confirm" }, { key: "n/Esc", action: "Cancel" }]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def handle_confirm_input(event)
|
|
17
|
+
case event
|
|
18
|
+
in { type: :key, code: "y" }
|
|
19
|
+
action = @confirm_action
|
|
20
|
+
@confirm_action = nil
|
|
21
|
+
execute_confirm_action(action)
|
|
22
|
+
in { type: :key, code: "n" } | { type: :key, code: "esc" }
|
|
23
|
+
@confirm_action = nil
|
|
24
|
+
nil
|
|
25
|
+
else
|
|
26
|
+
nil
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def render_confirm_popup(frame, area)
|
|
31
|
+
popup_area = area.centered(
|
|
32
|
+
@tui.constraint_percentage(50),
|
|
33
|
+
@tui.constraint_length(5)
|
|
34
|
+
)
|
|
35
|
+
frame.render_widget(@tui.clear(), popup_area)
|
|
36
|
+
frame.render_widget(
|
|
37
|
+
@tui.paragraph(
|
|
38
|
+
text: " #{confirm_message}",
|
|
39
|
+
style: @tui.style(fg: :yellow, modifiers: [:bold]),
|
|
40
|
+
block: @tui.block(
|
|
41
|
+
title: " Confirm ",
|
|
42
|
+
title_style: @tui.style(fg: :red, modifiers: [:bold]),
|
|
43
|
+
borders: [:all],
|
|
44
|
+
border_type: :rounded,
|
|
45
|
+
border_style: @tui.style(fg: :red)
|
|
46
|
+
)
|
|
47
|
+
),
|
|
48
|
+
popup_area
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidQueueTui
|
|
4
|
+
module Views
|
|
5
|
+
module Paginatable
|
|
6
|
+
LOAD_THRESHOLD = 10
|
|
7
|
+
|
|
8
|
+
def init_pagination
|
|
9
|
+
@table_state = RatatuiRuby::TableState.new(nil)
|
|
10
|
+
@table_state.select(0)
|
|
11
|
+
@selected_row = 0
|
|
12
|
+
@items = []
|
|
13
|
+
@total_count = nil
|
|
14
|
+
@all_loaded = false
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def items = @items
|
|
18
|
+
|
|
19
|
+
def selected_item
|
|
20
|
+
return nil if @items.empty? || @selected_row >= @items.size
|
|
21
|
+
@items[@selected_row]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def total_count=(count)
|
|
25
|
+
@total_count = count
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def current_offset
|
|
29
|
+
@items.size
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def reset_pagination!
|
|
33
|
+
@items = []
|
|
34
|
+
@total_count = nil
|
|
35
|
+
@all_loaded = false
|
|
36
|
+
@selected_row = 0
|
|
37
|
+
@table_state.select(0)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def update_items(new_items)
|
|
43
|
+
@selected_row = 0 if @selected_row >= new_items.size
|
|
44
|
+
@items = new_items
|
|
45
|
+
@all_loaded = new_items.size < SolidQueueTui.page_size
|
|
46
|
+
@selected_row = @selected_row.clamp(0, [@items.size - 1, 0].max)
|
|
47
|
+
@table_state.select(@selected_row)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def append_items(more_items)
|
|
51
|
+
@items.concat(more_items)
|
|
52
|
+
@all_loaded = more_items.size < SolidQueueTui.page_size
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def needs_more?
|
|
56
|
+
!@all_loaded && @selected_row >= @items.size - LOAD_THRESHOLD
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def move_selection(delta)
|
|
60
|
+
return if @items.empty?
|
|
61
|
+
@selected_row = (@selected_row + delta).clamp(0, @items.size - 1)
|
|
62
|
+
@table_state.select(@selected_row)
|
|
63
|
+
:load_more if needs_more?
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def jump_to_top
|
|
67
|
+
@selected_row = 0
|
|
68
|
+
@table_state.select(0)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def jump_to_bottom
|
|
72
|
+
return if @items.empty?
|
|
73
|
+
@selected_row = @items.size - 1
|
|
74
|
+
@table_state.select(@selected_row)
|
|
75
|
+
:load_more if needs_more?
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
module SolidQueueTui
|
|
4
4
|
module Views
|
|
5
5
|
class DashboardView
|
|
6
|
+
include FormattingHelpers
|
|
7
|
+
|
|
6
8
|
def initialize(tui)
|
|
7
9
|
@tui = tui
|
|
8
10
|
@selected_row = 0
|
|
@@ -179,10 +181,6 @@ module SolidQueueTui
|
|
|
179
181
|
])
|
|
180
182
|
end
|
|
181
183
|
|
|
182
|
-
def format_number(n)
|
|
183
|
-
return "0" if n.nil? || n == 0
|
|
184
|
-
n.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
|
|
185
|
-
end
|
|
186
184
|
end
|
|
187
185
|
end
|
|
188
186
|
end
|