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.
@@ -3,84 +3,140 @@
3
3
  module SolidQueueTui
4
4
  module Views
5
5
  class QueuesView
6
+ include Confirmable
7
+ include Filterable
8
+ include Paginatable
9
+ include FormattingHelpers
10
+
6
11
  def initialize(tui)
7
12
  @tui = tui
8
- @table_state = RatatuiRuby::TableState.new(nil)
9
- @table_state.select(0)
10
- @selected_row = 0
13
+ @mode = :list
14
+ @selected_queue = nil
15
+
16
+ # List mode state
17
+ @list_table_state = RatatuiRuby::TableState.new(nil)
18
+ @list_table_state.select(0)
19
+ @list_selected_row = 0
11
20
  @queues = []
12
- @confirm_action = nil
21
+
22
+ # Detail mode state (via Paginatable + Filterable)
23
+ init_pagination
24
+ init_confirm
25
+ init_filter
26
+ end
27
+
28
+ def detail_mode? = @mode == :detail
29
+
30
+ def selected_queue_name
31
+ @selected_queue&.name
13
32
  end
14
33
 
15
34
  def update(queues:)
16
35
  @queues = queues
17
- @selected_row = @selected_row.clamp(0, [@queues.size - 1, 0].max)
18
- @table_state.select(@selected_row)
36
+ @list_selected_row = @list_selected_row.clamp(0, [@queues.size - 1, 0].max)
37
+ @list_table_state.select(@list_selected_row)
38
+ end
39
+
40
+ def update_detail(jobs:)
41
+ update_items(jobs)
42
+ end
43
+
44
+ def append(jobs:)
45
+ append_items(jobs)
19
46
  end
20
47
 
21
48
  def render(frame, area)
22
- if @confirm_action
23
- confirm_area, content_area = @tui.layout_split(
24
- area,
25
- direction: :vertical,
26
- constraints: [
27
- @tui.constraint_length(3),
28
- @tui.constraint_fill(1)
29
- ]
30
- )
31
- render_confirm(frame, confirm_area)
32
- render_table(frame, content_area)
49
+ if @mode == :list
50
+ render_list(frame, area)
33
51
  else
34
- render_table(frame, area)
52
+ render_detail(frame, area)
35
53
  end
36
54
  end
37
55
 
38
56
  def handle_input(event)
39
- if @confirm_action
57
+ if @mode == :list
58
+ handle_list_input(event)
59
+ elsif confirm_mode?
40
60
  handle_confirm_input(event)
61
+ elsif filter_mode?
62
+ handle_filter_input(event)
41
63
  else
42
- handle_normal_input(event)
64
+ handle_detail_input(event)
43
65
  end
44
66
  end
45
67
 
46
68
  def selected_item
47
- return nil if @queues.empty? || @selected_row >= @queues.size
48
- @queues[@selected_row]
69
+ if @mode == :list
70
+ return nil if @queues.empty? || @list_selected_row >= @queues.size
71
+ @queues[@list_selected_row]
72
+ else
73
+ return nil if items.empty? || @selected_row >= items.size
74
+ items[@selected_row]
75
+ end
49
76
  end
50
77
 
51
78
  def capturing_input?
52
- !!@confirm_action
79
+ detail_mode? || confirm_mode? || filter_mode?
53
80
  end
54
81
 
55
82
  def bindings
56
- if @confirm_action
57
- [
58
- { key: "y", action: "Confirm" },
59
- { key: "n/Esc", action: "Cancel" }
60
- ]
83
+ if @mode == :list
84
+ if confirm_mode?
85
+ confirm_bindings
86
+ else
87
+ [
88
+ { key: "j/k", action: "Navigate" },
89
+ { key: "Enter", action: "View Jobs" },
90
+ { key: "p", action: "Pause/Resume" },
91
+ { key: "G/g", action: "Bottom/Top" }
92
+ ]
93
+ end
61
94
  else
62
- [
63
- { key: "j/k", action: "Navigate" },
64
- { key: "p", action: "Pause/Resume" },
65
- { key: "G/g", action: "Bottom/Top" }
66
- ]
95
+ if confirm_mode?
96
+ confirm_bindings
97
+ elsif filter_mode?
98
+ filter_bindings
99
+ else
100
+ [
101
+ { key: "j/k", action: "Navigate" },
102
+ { key: "Enter", action: "Detail" },
103
+ { key: "/", action: "Filter" },
104
+ { key: "Esc", action: "Back" },
105
+ { key: "G/g", action: "Bottom/Top" }
106
+ ]
107
+ end
67
108
  end
68
109
  end
69
110
 
70
- def breadcrumb = "queues"
111
+ def breadcrumb
112
+ if @mode == :list
113
+ "queues"
114
+ else
115
+ base = "queues > #{@selected_queue&.name}"
116
+ @filters.empty? ? base : "#{base}:filtered"
117
+ end
118
+ end
71
119
 
72
120
  private
73
121
 
74
- def handle_normal_input(event)
122
+ # === List mode ===
123
+
124
+ def handle_list_input(event)
125
+ if confirm_mode?
126
+ return handle_confirm_input(event)
127
+ end
128
+
75
129
  case event
76
130
  in { type: :key, code: "j" } | { type: :key, code: "up" }
77
- move_selection(-1)
131
+ list_move_selection(-1)
78
132
  in { type: :key, code: "k" } | { type: :key, code: "down" }
79
- move_selection(1)
133
+ list_move_selection(1)
80
134
  in { type: :key, code: "g" }
81
- jump_to_top
135
+ list_jump_to_top
82
136
  in { type: :key, code: "G" }
83
- jump_to_bottom
137
+ list_jump_to_bottom
138
+ in { type: :key, code: "enter" }
139
+ enter_detail_mode
84
140
  in { type: :key, code: "p" }
85
141
  queue = selected_item
86
142
  if queue
@@ -92,40 +148,46 @@ module SolidQueueTui
92
148
  end
93
149
  end
94
150
 
95
- def handle_confirm_input(event)
96
- case event
97
- in { type: :key, code: "y" }
98
- @confirm_action = nil
99
- queue = selected_item
100
- return nil unless queue
101
- Actions::ToggleQueuePause.call(queue.name)
102
- :refresh
103
- in { type: :key, code: "n" } | { type: :key, code: "esc" }
104
- @confirm_action = nil
105
- nil
106
- else
107
- nil
108
- end
151
+ def enter_detail_mode
152
+ queue = selected_item
153
+ return nil unless queue
154
+ @selected_queue = queue
155
+ @mode = :detail
156
+ reset_pagination!
157
+ clear_filter
158
+ :enter_queue
109
159
  end
110
160
 
111
- def move_selection(delta)
161
+ def list_move_selection(delta)
112
162
  return if @queues.empty?
113
- @selected_row = (@selected_row + delta).clamp(0, @queues.size - 1)
114
- @table_state.select(@selected_row)
163
+ @list_selected_row = (@list_selected_row + delta).clamp(0, @queues.size - 1)
164
+ @list_table_state.select(@list_selected_row)
165
+ nil
115
166
  end
116
167
 
117
- def jump_to_top
118
- @selected_row = 0
119
- @table_state.select(0)
168
+ def list_jump_to_top
169
+ @list_selected_row = 0
170
+ @list_table_state.select(0)
171
+ nil
120
172
  end
121
173
 
122
- def jump_to_bottom
123
- return if @queues.empty?
124
- @selected_row = @queues.size - 1
125
- @table_state.select(@selected_row)
174
+ def list_jump_to_bottom
175
+ return nil if @queues.empty?
176
+ @list_selected_row = @queues.size - 1
177
+ @list_table_state.select(@list_selected_row)
178
+ nil
179
+ end
180
+
181
+ def render_list(frame, area)
182
+ if confirm_mode?
183
+ render_list_table(frame, area)
184
+ render_confirm_popup(frame, area)
185
+ else
186
+ render_list_table(frame, area)
187
+ end
126
188
  end
127
189
 
128
- def render_table(frame, area)
190
+ def render_list_table(frame, area)
129
191
  columns = [
130
192
  { key: :name, label: "QUEUE", width: :fill },
131
193
  { key: :size, label: "SIZE", width: 10 },
@@ -145,36 +207,110 @@ module SolidQueueTui
145
207
  title: "Queues",
146
208
  columns: columns,
147
209
  rows: rows,
148
- selected_row: @selected_row,
210
+ selected_row: @list_selected_row,
149
211
  empty_message: "No queues found"
150
212
  )
151
213
 
152
- table.render(frame, area, @table_state)
214
+ table.render(frame, area, @list_table_state)
153
215
  end
154
216
 
155
- def render_confirm(frame, area)
156
- queue = selected_item
157
- message = if @confirm_action == :pause
158
- "Pause queue '#{queue&.name}'? Workers will stop picking up jobs from this queue. [y/n]"
159
- else
160
- "Resume queue '#{queue&.name}'? [y/n]"
161
- end
162
-
163
- frame.render_widget(
164
- @tui.paragraph(
165
- text: " #{message}",
166
- style: @tui.style(fg: :yellow, modifiers: [:bold]),
167
- block: @tui.block(
168
- title: " Confirm ",
169
- title_style: @tui.style(fg: :red, modifiers: [:bold]),
170
- borders: [:all],
171
- border_type: :rounded,
172
- border_style: @tui.style(fg: :red)
173
- )
174
- ),
175
- area
217
+ # === Detail mode ===
218
+
219
+ def handle_detail_input(event)
220
+ case event
221
+ in { type: :key, code: "j" } | { type: :key, code: "up" }
222
+ move_selection(-1)
223
+ in { type: :key, code: "k" } | { type: :key, code: "down" }
224
+ result = move_selection(1)
225
+ return :load_more if result == :load_more
226
+ nil
227
+ in { type: :key, code: "g" }
228
+ jump_to_top
229
+ in { type: :key, code: "G" }
230
+ jump_to_bottom
231
+ in { type: :key, code: "enter" }
232
+ :open_detail
233
+ in { type: :key, code: "/" }
234
+ enter_filter_mode
235
+ nil
236
+ in { type: :key, code: "esc" }
237
+ exit_detail_mode
238
+ else
239
+ nil
240
+ end
241
+ end
242
+
243
+ def exit_detail_mode
244
+ @mode = :list
245
+ @selected_queue = nil
246
+ :exit_queue
247
+ end
248
+
249
+ def render_detail(frame, area)
250
+ if filter_mode?
251
+ filter_area, content_area = @tui.layout_split(
252
+ area,
253
+ direction: :vertical,
254
+ constraints: [
255
+ @tui.constraint_length(3),
256
+ @tui.constraint_fill(1)
257
+ ]
258
+ )
259
+ render_filter_input(frame, filter_area)
260
+ render_detail_table(frame, content_area)
261
+ else
262
+ render_detail_table(frame, area)
263
+ end
264
+ end
265
+
266
+ def render_detail_table(frame, area)
267
+ columns = [
268
+ { key: :id, label: "ID", width: 8 },
269
+ { key: :class_name, label: "CLASS", width: :fill },
270
+ { key: :priority, label: "PRI", width: 5 },
271
+ { key: :created_at, label: "ENQUEUED", width: 12 }
272
+ ]
273
+
274
+ rows = items.map do |job|
275
+ {
276
+ id: job.id,
277
+ class_name: job.class_name,
278
+ priority: job.priority,
279
+ created_at: time_ago(job.created_at)
280
+ }
281
+ end
282
+
283
+ table = Components::JobTable.new(
284
+ @tui,
285
+ title: filter_title("Queue '#{@selected_queue&.name}' — Pending"),
286
+ columns: columns,
287
+ rows: rows,
288
+ selected_row: @selected_row,
289
+ total_count: @total_count,
290
+ empty_message: "No pending jobs in this queue"
176
291
  )
292
+
293
+ table.render(frame, area, @table_state)
294
+ end
295
+
296
+ # === Confirmable hooks (list mode only) ===
297
+
298
+ def confirm_message
299
+ queue = @queues[@list_selected_row]
300
+ if @confirm_action == :pause
301
+ "Pause queue '#{queue&.name}'? Workers will stop picking up jobs from this queue. [y/n]"
302
+ else
303
+ "Resume queue '#{queue&.name}'? [y/n]"
304
+ end
177
305
  end
306
+
307
+ def execute_confirm_action(action)
308
+ queue = @queues[@list_selected_row]
309
+ return nil unless queue
310
+ Actions::ToggleQueuePause.call(queue.name)
311
+ :refresh
312
+ end
313
+
178
314
  end
179
315
  end
180
316
  end
@@ -3,13 +3,16 @@
3
3
  module SolidQueueTui
4
4
  module Views
5
5
  class RecurringTasksView
6
+ include Confirmable
7
+ include FormattingHelpers
8
+
6
9
  def initialize(tui)
7
10
  @tui = tui
8
11
  @table_state = RatatuiRuby::TableState.new(nil)
9
12
  @table_state.select(0)
10
13
  @selected_row = 0
11
14
  @tasks = []
12
- @confirm_action = nil
15
+ init_confirm
13
16
  end
14
17
 
15
18
  def update(tasks:)
@@ -19,24 +22,16 @@ module SolidQueueTui
19
22
  end
20
23
 
21
24
  def render(frame, area)
22
- if @confirm_action
23
- confirm_area, content_area = @tui.layout_split(
24
- area,
25
- direction: :vertical,
26
- constraints: [
27
- @tui.constraint_length(3),
28
- @tui.constraint_fill(1)
29
- ]
30
- )
31
- render_confirm(frame, confirm_area)
32
- render_table(frame, content_area)
25
+ if confirm_mode?
26
+ render_table(frame, area)
27
+ render_confirm_popup(frame, area)
33
28
  else
34
29
  render_table(frame, area)
35
30
  end
36
31
  end
37
32
 
38
33
  def handle_input(event)
39
- if @confirm_action
34
+ if confirm_mode?
40
35
  handle_confirm_input(event)
41
36
  else
42
37
  handle_normal_input(event)
@@ -49,15 +44,12 @@ module SolidQueueTui
49
44
  end
50
45
 
51
46
  def capturing_input?
52
- !!@confirm_action
47
+ confirm_mode?
53
48
  end
54
49
 
55
50
  def bindings
56
- if @confirm_action
57
- [
58
- { key: "y", action: "Confirm" },
59
- { key: "n/Esc", action: "Cancel" }
60
- ]
51
+ if confirm_mode?
52
+ confirm_bindings
61
53
  else
62
54
  [
63
55
  { key: "j/k", action: "Navigate" },
@@ -89,20 +81,16 @@ module SolidQueueTui
89
81
  end
90
82
  end
91
83
 
92
- def handle_confirm_input(event)
93
- case event
94
- in { type: :key, code: "y" }
95
- @confirm_action = nil
96
- task = selected_item
97
- return nil unless task
98
- Actions::EnqueueRecurringTask.call(task.key)
99
- :refresh
100
- in { type: :key, code: "n" } | { type: :key, code: "esc" }
101
- @confirm_action = nil
102
- nil
103
- else
104
- nil
105
- end
84
+ def confirm_message
85
+ task = selected_item
86
+ "Run '#{task&.key}' (#{task&.class_name || task&.command}) now? [y/n]"
87
+ end
88
+
89
+ def execute_confirm_action(action)
90
+ task = selected_item
91
+ return nil unless task
92
+ Actions::EnqueueRecurringTask.call(task.key)
93
+ :refresh
106
94
  end
107
95
 
108
96
  def move_selection(delta)
@@ -155,48 +143,13 @@ module SolidQueueTui
155
143
  table.render(frame, area, @table_state)
156
144
  end
157
145
 
158
- def render_confirm(frame, area)
159
- task = selected_item
160
- message = "Run '#{task&.key}' (#{task&.class_name || task&.command}) now? [y/n]"
161
-
162
- frame.render_widget(
163
- @tui.paragraph(
164
- text: " #{message}",
165
- style: @tui.style(fg: :yellow, modifiers: [:bold]),
166
- block: @tui.block(
167
- title: " Confirm ",
168
- title_style: @tui.style(fg: :red, modifiers: [:bold]),
169
- borders: [:all],
170
- border_type: :rounded,
171
- border_style: @tui.style(fg: :red)
172
- )
173
- ),
174
- area
175
- )
176
- end
177
-
146
+ # Override: returns "Never" for nil and "just now" for very recent
178
147
  def time_ago(time)
179
148
  return "Never" unless time
180
149
  seconds = (Time.now.utc - time).to_i
181
150
  return "just now" if seconds < 5
182
151
  "#{humanize_duration(seconds)} ago"
183
152
  end
184
-
185
- def time_until(time)
186
- return "n/a" unless time
187
- seconds = (time - Time.now.utc).to_i
188
- return "now" if seconds <= 0
189
- "in #{humanize_duration(seconds)}"
190
- end
191
-
192
- def humanize_duration(seconds)
193
- case seconds.abs
194
- when 0..59 then "#{seconds.abs}s"
195
- when 60..3599 then "#{seconds.abs / 60}m"
196
- when 3600..86399 then "#{seconds.abs / 3600}h"
197
- else "#{seconds.abs / 86400}d"
198
- end
199
- end
200
153
  end
201
154
  end
202
155
  end