job_harbor 0.7.0 → 0.7.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5ae6910cda3368da6dc2a045f5ee6530e40f476f641c0995fb2d5bcb7e9bc39c
4
- data.tar.gz: 5fb0dba8946dec6d7246045bc3cadcc58055f0f8003cc5bc2222f322840f5eab
3
+ metadata.gz: 61fd4075abe3acb8265885f2ff9e5d007bede4df800c2c466b98e11cbc1810b2
4
+ data.tar.gz: c7b3836df141e4711ccb3f4828eb926da10f6f5d9901a996338e7977cb011641
5
5
  SHA512:
6
- metadata.gz: 2dbeee4543ff34b659adaf03845c04cb014b38c57db10d1148b64ac850d92dfe3b675a099c87128b21ddadfd74e55764e4b9512253bf2bfc00a308277fd5ac99
7
- data.tar.gz: c25578475dbc7c3265bef7560eed0327f58beb1584b6fa3cd9ab0cf643d9ccfa6a571824ea585edf1ac0b71d9e4d6cf3acb947c1ac483a193c4c441c052c3936
6
+ metadata.gz: fca96f13699a78acf5e848527c86df9f98e53ca6f1ce7ab7bd4b7d0ba923e737e8badadc86d990627bed83ffe8c81d07b1c324e0654f4f0704a5790bc223bf26
7
+ data.tar.gz: f3b64f11f8a06145c40830703b2d48f7ced097e01a173b3c63a9c645bb880701bf4e13174c2c3ecc9a570d7bbec9e4a2bd422888e9751c645138429720bfb412
@@ -3,8 +3,8 @@
3
3
  module JobHarbor
4
4
  class JobFiltersComponent < ApplicationComponent
5
5
  def initialize(class_names:, queue_names:, current_class:, current_queue:, current_path:, params: {})
6
- @class_names = class_names
7
- @queue_names = queue_names
6
+ @class_names = with_selected(class_names, current_class)
7
+ @queue_names = with_selected(queue_names, current_queue)
8
8
  @current_class = current_class
9
9
  @current_queue = current_queue
10
10
  @current_path = current_path
@@ -22,6 +22,11 @@ module JobHarbor
22
22
 
23
23
  private
24
24
 
25
+ # The active filter stays selectable even after its last job is gone.
26
+ def with_selected(names, current)
27
+ current.present? ? (names | [ current ]).sort : names
28
+ end
29
+
25
30
  def class_filter
26
31
  content_tag(:div, class: "flex items-center gap-1.5") do
27
32
  safe_join([
@@ -2,8 +2,9 @@
2
2
 
3
3
  module JobHarbor
4
4
  class JobRowComponent < ApplicationComponent
5
- def initialize(job:)
5
+ def initialize(job:, return_to: nil)
6
6
  @job = job
7
+ @return_to = return_to
7
8
  end
8
9
 
9
10
  def call
@@ -92,16 +93,20 @@ module JobHarbor
92
93
  end
93
94
  end
94
95
 
96
+ def return_to
97
+ @return_to || request.fullpath
98
+ end
99
+
95
100
  def retry_button
96
101
  return unless @job.can_retry?
97
102
 
98
- button_to "Retry", retry_job_path(@job), method: :post, class: "btn btn-secondary btn-xs"
103
+ button_to "Retry", retry_job_path(@job), method: :post, params: { return_to: return_to }, class: "btn btn-secondary btn-xs"
99
104
  end
100
105
 
101
106
  def discard_button
102
107
  return unless @job.can_discard?
103
108
 
104
- button_to "Discard", discard_job_path(@job), method: :delete, class: "btn btn-destructive btn-xs",
109
+ button_to "Discard", discard_job_path(@job), method: :delete, params: { return_to: return_to }, class: "btn btn-destructive btn-xs",
105
110
  form: { data: { confirm: "Are you sure you want to discard this job?" } }
106
111
  end
107
112
  end
@@ -44,7 +44,7 @@ module JobHarbor
44
44
  def retry
45
45
  if @job.can_retry?
46
46
  perform_retry(@job)
47
- redirect_to job_path(@job), notice: "Job has been queued for retry."
47
+ redirect_to return_path || job_path(@job), allow_other_host: false, notice: "Job has been queued for retry."
48
48
  else
49
49
  redirect_to job_path(@job), alert: "This job cannot be retried."
50
50
  end
@@ -52,7 +52,7 @@ module JobHarbor
52
52
 
53
53
  def discard
54
54
  if @job.can_discard? && @job.discard
55
- redirect_to jobs_path(status: params[:return_status]), notice: "Job has been discarded."
55
+ redirect_to return_path || jobs_path(status: params[:return_status]), allow_other_host: false, notice: "Job has been discarded."
56
56
  else
57
57
  redirect_to job_path(@job), alert: "This job cannot be discarded."
58
58
  end
@@ -78,6 +78,31 @@ module JobHarbor
78
78
  redirect_to jobs_path, alert: "Job not found."
79
79
  end
80
80
 
81
+ # Only a path inside this engine's mount, so a crafted param can't send the operator elsewhere.
82
+ def return_path
83
+ path = url_from(params[:return_to]) if params[:return_to].is_a?(String)
84
+ return unless path && within_mount?(path)
85
+
86
+ queue_page_gone?(path) ? queues_path : path
87
+ end
88
+
89
+ def within_mount?(path)
90
+ mount = request.script_name
91
+ return false unless path == mount || path.start_with?("#{mount}/", "#{mount}?")
92
+
93
+ raw = path.split("?", 2).first
94
+ # %2e%2e and %2f are resolved by the browser, so judge the decoded path too.
95
+ [ raw, URI.decode_www_form_component(raw) ].none? { |candidate| candidate.match?(%r{//|\\|\s}) || candidate.split("/").include?("..") }
96
+ rescue ArgumentError
97
+ false
98
+ end
99
+
100
+ # A queue with no jobs left stops existing, so its page would answer "Queue not found" and drop the notice.
101
+ def queue_page_gone?(path)
102
+ name = path.split("?", 2).first[%r{\A#{Regexp.escape(request.script_name)}/queues/([^/]+)\z}, 1]
103
+ name.present? && QueueStats.find(Rack::Utils.unescape_path(name)).nil?
104
+ end
105
+
81
106
  def per_page_param
82
107
  per_page = params[:per_page].to_i
83
108
  valid_sizes = JobHarbor::PerPageSelectorComponent::PAGE_SIZES
@@ -5,6 +5,7 @@ module JobHarbor
5
5
  def index
6
6
  @workers = SolidQueue::Process.order(last_heartbeat_at: :desc)
7
7
  @active_count = Processes.alive.count
8
+ @live_workers_count = Processes.live_workers.count
8
9
  @stale_count = @workers.count - @active_count
9
10
  set_page_title "Workers"
10
11
  end
@@ -192,9 +192,10 @@ module JobHarbor
192
192
  end
193
193
 
194
194
  def paginate(scope, page:, per_page:)
195
- page = [ page.to_i, 1 ].max
196
- offset = (page - 1) * per_page
197
195
  total = scope.count
196
+ pages = (total.to_f / per_page).ceil
197
+ page = [ [ page.to_i, 1 ].max, [ pages, 1 ].max ].min
198
+ offset = (page - 1) * per_page
198
199
 
199
200
  jobs = scope.preload(*EXECUTION_STATUSES.keys).order(id: :desc).limit(per_page).offset(offset).map { |job| new(job) }
200
201
  [ PageInfo.new(page: page, per_page: per_page, count: total), jobs ]
@@ -1,12 +1,12 @@
1
1
  <div class="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-6">
2
2
  <%= render JobHarbor::StatCardComponent.new(
3
- label: "Total Workers",
3
+ label: "Processes",
4
4
  value: @workers.count,
5
5
  type: :workers
6
6
  ) %>
7
7
 
8
8
  <%= render JobHarbor::StatCardComponent.new(
9
- label: "Active",
9
+ label: "Live",
10
10
  value: @active_count,
11
11
  type: :finished
12
12
  ) %>
@@ -18,6 +18,8 @@
18
18
  ) %>
19
19
  </div>
20
20
 
21
+ <p class="text-sm text-muted-foreground mb-6" data-live-workers>Live workers: <%= @live_workers_count %></p>
22
+
21
23
  <% if @workers.empty? %>
22
24
  <%= render JobHarbor::EmptyStateComponent.new(
23
25
  title: "No workers found",
@@ -1,3 +1,3 @@
1
1
  module JobHarbor
2
- VERSION = "0.7.0"
2
+ VERSION = "0.7.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: job_harbor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Sparkman