job_harbor 0.1.1 → 0.5.0

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.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +103 -41
  3. data/app/components/job_harbor/badge_component.rb +35 -3
  4. data/app/components/job_harbor/chart_component.rb +19 -17
  5. data/app/components/job_harbor/empty_state_component.rb +11 -5
  6. data/app/components/job_harbor/failure_rates_component.rb +7 -7
  7. data/app/components/job_harbor/job_filters_component.rb +7 -9
  8. data/app/components/job_harbor/job_row_component.rb +16 -14
  9. data/app/components/job_harbor/nav_link_component.rb +4 -18
  10. data/app/components/job_harbor/pagination_component.rb +8 -9
  11. data/app/components/job_harbor/per_page_selector_component.rb +4 -3
  12. data/app/components/job_harbor/queue_card_component.rb +23 -23
  13. data/app/components/job_harbor/refresh_selector_component.rb +6 -5
  14. data/app/components/job_harbor/stat_card_component.rb +29 -45
  15. data/app/components/job_harbor/theme_toggle_component.rb +3 -3
  16. data/app/components/job_harbor/worker_card_component.rb +29 -30
  17. data/app/models/job_harbor/job_presenter.rb +12 -3
  18. data/app/views/job_harbor/dashboard/index.html.erb +33 -31
  19. data/app/views/job_harbor/jobs/index.html.erb +25 -32
  20. data/app/views/job_harbor/jobs/search.html.erb +10 -8
  21. data/app/views/job_harbor/jobs/show.html.erb +72 -73
  22. data/app/views/job_harbor/queues/index.html.erb +1 -1
  23. data/app/views/job_harbor/queues/show.html.erb +20 -19
  24. data/app/views/job_harbor/recurring_tasks/index.html.erb +8 -8
  25. data/app/views/job_harbor/recurring_tasks/show.html.erb +46 -44
  26. data/app/views/job_harbor/workers/index.html.erb +2 -2
  27. data/app/views/layouts/job_harbor/application.html.erb +636 -1294
  28. data/lib/job_harbor/engine.rb +1 -1
  29. data/lib/job_harbor/version.rb +1 -1
  30. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8886fa57253d6ca9b9d65e8a7e969b7758505a73b7edc2590b1c39efbd5e7c06
4
- data.tar.gz: 7d11c049a0e8ca9131601a6c88cb818e3724b4c4854bb2450d36686cb6a34cc4
3
+ metadata.gz: 421784ad68128e6ee09898b0bd2a93ef4acfc3a8313f18a241c3c25fdb2e2cce
4
+ data.tar.gz: 94cbfb2e3682ab600424c7f9c607434fd3e64cc4692fbc7473cce13a8c7dfeff
5
5
  SHA512:
6
- metadata.gz: 68200679eacafc5f57e61ffaf5d13897ac4017b95f8422db7ced82f3be29077325c384be39c16d1a0abb75f2ec690e5f36f3bc398bd8f34bdf76b0ff557796ce
7
- data.tar.gz: 4af20afe1df6d6b8ee22f99ab85cdbaead13d083fc2a4c46cdd7569d98d023f125e6ea52df97daaea1f7316509dee5e8d83d5f77920b3b68800acb0e496071b2
6
+ metadata.gz: f54f4ad961c726515225230daa9a99886c12a76eaf0dc769690d1f1af99a6ccd53589a8979dfc9a219442a3f03931636e08d1f7dba8edb3bd91bcf4eb4b76eea
7
+ data.tar.gz: 6d577a23966412672c5363bc91d1cd3915a8d46b5eba021bbcd41de8a70a4961906f1c5d4723b1f7c1a40eb0d7e95b5fbe10df7233679da21ea8312d544b7e5c
data/README.md CHANGED
@@ -1,23 +1,72 @@
1
- # SolidQueue Dashboard
1
+ # Job Harbor
2
2
 
3
- A modern, self-contained dashboard for monitoring and managing [Solid Queue](https://github.com/rails/solid_queue) jobs. Built with ViewComponents and vanilla CSS for easy integration with any Rails application.
3
+ A modern, beautiful dashboard for monitoring and managing [Solid Queue](https://github.com/rails/solid_queue) jobs in Rails. Built with ViewComponents and a shadcn/ui-inspired design system.
4
+
5
+ ![Dashboard Dark](docs/screenshots/dashboard-dark.png)
4
6
 
5
7
  ## Features
6
8
 
7
- - **Dashboard Overview**: Real-time statistics for pending, scheduled, in-progress, failed, blocked, and finished jobs
8
- - **Job Management**: View, search, retry, and discard jobs with full argument and error inspection
9
- - **Queue Management**: Monitor queue health, pause/resume queues
10
- - **Worker Monitoring**: Track active workers with heartbeat status detection
11
- - **Recurring Tasks**: View and manually trigger recurring jobs
12
- - **Real-time Updates**: Auto-refresh with configurable polling interval
13
- - **Dark/Light Themes**: CSS variable-based theming with no external dependencies
9
+ - **Dashboard Overview** - Real-time statistics, job activity charts, failure rates, and recent failures at a glance
10
+ - **Job Management** - View, search, filter, retry, and discard jobs with full argument and error inspection
11
+ - **Queue Management** - Monitor queue health, pause and resume queues
12
+ - **Worker Monitoring** - Track active workers with heartbeat status and hostname details
13
+ - **Recurring Tasks** - View schedules and manually trigger recurring jobs
14
+ - **Auto-Refresh** - Configurable polling interval keeps data current
15
+ - **Dark/Light Themes** - Toggle between themes with localStorage persistence
16
+
17
+ ## Screenshots
18
+
19
+ <details>
20
+ <summary>Dashboard</summary>
21
+
22
+ | Dark | Light |
23
+ |------|-------|
24
+ | ![Dashboard Dark](docs/screenshots/dashboard-dark.png) | ![Dashboard Light](docs/screenshots/dashboard-light.png) |
25
+
26
+ </details>
27
+
28
+ <details>
29
+ <summary>Jobs</summary>
30
+
31
+ | Dark | Light |
32
+ |------|-------|
33
+ | ![Jobs Dark](docs/screenshots/jobs-dark.png) | ![Jobs Light](docs/screenshots/jobs-light.png) |
34
+
35
+ </details>
36
+
37
+ <details>
38
+ <summary>Queues</summary>
39
+
40
+ | Dark | Light |
41
+ |------|-------|
42
+ | ![Queues Dark](docs/screenshots/queues-dark.png) | ![Queues Light](docs/screenshots/queues-light.png) |
43
+
44
+ </details>
45
+
46
+ <details>
47
+ <summary>Workers</summary>
48
+
49
+ | Dark | Light |
50
+ |------|-------|
51
+ | ![Workers Dark](docs/screenshots/workers-dark.png) | ![Workers Light](docs/screenshots/workers-light.png) |
52
+
53
+ </details>
54
+
55
+ <details>
56
+ <summary>Recurring Tasks</summary>
57
+
58
+ | Dark | Light |
59
+ |------|-------|
60
+ | ![Recurring Dark](docs/screenshots/recurring-dark.png) | ![Recurring Light](docs/screenshots/recurring-light.png) |
61
+
62
+ </details>
14
63
 
15
64
  ## Installation
16
65
 
17
66
  Add to your Gemfile:
18
67
 
19
68
  ```ruby
20
- gem "solidqueue_dashboard"
69
+ gem "job_harbor"
21
70
  ```
22
71
 
23
72
  Then run:
@@ -26,49 +75,58 @@ Then run:
26
75
  bundle install
27
76
  ```
28
77
 
78
+ ## Mounting
79
+
80
+ Mount the engine in your routes:
81
+
82
+ ```ruby
83
+ # config/routes.rb
84
+ Rails.application.routes.draw do
85
+ mount JobHarbor::Engine, at: "/job_harbor"
86
+ end
87
+ ```
88
+
89
+ Or with an authentication constraint:
90
+
91
+ ```ruby
92
+ authenticated :user, ->(u) { u.admin? } do
93
+ mount JobHarbor::Engine, at: "/admin/jobs"
94
+ end
95
+ ```
96
+
29
97
  ## Configuration
30
98
 
31
- Create an initializer at `config/initializers/solidqueue_dashboard.rb`:
99
+ Create an initializer at `config/initializers/job_harbor.rb`:
32
100
 
33
101
  ```ruby
34
- SolidqueueDashboard.configure do |config|
35
- # Authorization callback - must return true to allow access
102
+ JobHarbor.configure do |config|
103
+ # Authorization - must return true to allow access (default: open)
36
104
  config.authorize_with = -> { current_user&.admin? }
37
105
 
38
- # Theme: :dark or :light
106
+ # Theme: :dark or :light (default: :dark)
39
107
  config.theme = :dark
40
108
 
41
- # Primary accent color
42
- config.primary_color = "amber"
43
-
44
- # Jobs per page
109
+ # Jobs per page (default: 25)
45
110
  config.jobs_per_page = 25
46
111
 
47
- # Enable recurring tasks management
48
- config.enable_recurring_tasks = true
49
-
50
- # Enable auto-refresh
51
- config.enable_real_time_updates = true
52
-
53
- # Poll interval in seconds
112
+ # Auto-refresh polling interval in seconds (default: 5)
54
113
  config.poll_interval = 5
55
- end
56
- ```
57
114
 
58
- ## Mounting
115
+ # Toggle features on/off
116
+ config.enable_recurring_tasks = true # default: true
117
+ config.enable_real_time_updates = true # default: true
118
+ config.enable_failure_stats = true # default: true
119
+ config.enable_charts = true # default: true
59
120
 
60
- Mount the engine in your routes:
121
+ # Default chart time range (default: "24h")
122
+ config.default_chart_range = "24h"
61
123
 
62
- ```ruby
63
- # config/routes.rb
64
- Rails.application.routes.draw do
65
- # Basic mount
66
- mount SolidqueueDashboard::Engine, at: "/jobs"
124
+ # "Back to App" link in the navbar (default: nil, hidden)
125
+ config.return_to_app_path = "/"
126
+ config.return_to_app_label = "Back to App"
67
127
 
68
- # Or with authentication constraint
69
- authenticated :user, ->(u) { u.admin? } do
70
- mount SolidqueueDashboard::Engine, at: "/admin/jobs"
71
- end
128
+ # Also accepts a proc for dynamic paths:
129
+ # config.return_to_app_path = -> { main_app.root_path }
72
130
  end
73
131
  ```
74
132
 
@@ -77,18 +135,22 @@ end
77
135
  The dashboard uses a configurable authorization callback. Return `true` to allow access:
78
136
 
79
137
  ```ruby
80
- # Allow all authenticated users
138
+ # Allow all (default - not recommended for production)
139
+ config.authorize_with = -> { true }
140
+
141
+ # Require authentication
81
142
  config.authorize_with = -> { current_user.present? }
82
143
 
83
144
  # Require admin role
84
145
  config.authorize_with = -> { current_user&.admin? }
85
146
 
86
147
  # Use Pundit or similar
87
- config.authorize_with = -> { authorize(:solid_queue, :manage?) }
148
+ config.authorize_with = -> { authorize(:job_harbor, :manage?) }
88
149
  ```
89
150
 
90
- ## Dependencies
151
+ ## Requirements
91
152
 
153
+ - Ruby >= 3.2
92
154
  - Rails >= 7.1
93
155
  - Solid Queue >= 0.3
94
156
  - ViewComponent >= 3.0
@@ -4,19 +4,51 @@ module JobHarbor
4
4
  class BadgeComponent < ApplicationComponent
5
5
  VALID_STATUSES = %w[pending scheduled in_progress failed finished blocked active paused].freeze
6
6
 
7
+ STATUS_COLORS = {
8
+ "pending" => "badge-sky",
9
+ "scheduled" => "badge-yellow",
10
+ "in_progress" => "badge-amber",
11
+ "failed" => "badge-red",
12
+ "finished" => "badge-green",
13
+ "blocked" => "badge-zinc",
14
+ "active" => "badge-green",
15
+ "paused" => "badge-amber"
16
+ }.freeze
17
+
18
+ STATUS_CIRCLES = {
19
+ "pending" => "circle-sky",
20
+ "scheduled" => "circle-yellow",
21
+ "in_progress" => "circle-amber",
22
+ "failed" => "circle-red",
23
+ "finished" => "circle-green",
24
+ "blocked" => "circle-zinc",
25
+ "active" => "circle-green",
26
+ "paused" => "circle-amber"
27
+ }.freeze
28
+
7
29
  def initialize(status:)
8
30
  @status = status.to_s.downcase
9
31
  end
10
32
 
11
33
  def call
12
- content_tag(:span, display_text, class: css_classes)
34
+ content_tag(:span, class: css_classes) do
35
+ safe_join([
36
+ content_tag(:span, "", class: "circle #{circle_class}"),
37
+ display_text
38
+ ])
39
+ end
13
40
  end
14
41
 
15
42
  private
16
43
 
17
44
  def css_classes
18
- status_class = VALID_STATUSES.include?(@status) ? @status : "pending"
19
- "sqd-badge sqd-badge-#{status_class}"
45
+ status_key = VALID_STATUSES.include?(@status) ? @status : "pending"
46
+ "badge #{STATUS_COLORS[status_key]}"
47
+ end
48
+
49
+ def circle_class
50
+ status_key = VALID_STATUSES.include?(@status) ? @status : "pending"
51
+ STATUS_CIRCLES[status_key]
20
52
  end
21
53
 
22
54
  def display_text
@@ -8,7 +8,7 @@ module JobHarbor
8
8
  end
9
9
 
10
10
  def call
11
- content_tag(:div, class: "sqd-card sqd-chart-card") do
11
+ content_tag(:div, class: "card") do
12
12
  safe_join([
13
13
  header,
14
14
  body
@@ -19,23 +19,25 @@ module JobHarbor
19
19
  private
20
20
 
21
21
  def header
22
- content_tag(:div, class: "sqd-card-header sqd-chart-header") do
23
- safe_join([
24
- content_tag(:h3, "Job Activity", class: "sqd-card-title"),
25
- range_selector
26
- ])
22
+ content_tag(:div, class: "card-header") do
23
+ content_tag(:div, class: "flex items-center justify-between") do
24
+ safe_join([
25
+ content_tag(:h3, "Job Activity", class: "card-title"),
26
+ range_selector
27
+ ])
28
+ end
27
29
  end
28
30
  end
29
31
 
30
32
  def range_selector
31
- content_tag(:div, class: "sqd-chart-ranges") do
33
+ content_tag(:div, class: "sqd-chart-ranges flex gap-1") do
32
34
  safe_join(ChartData.available_ranges.map { |range| range_button(range) })
33
35
  end
34
36
  end
35
37
 
36
38
  def range_button(range)
37
39
  active = range[:value] == @current_range
38
- css_class = "sqd-chart-range-btn#{' active' if active}"
40
+ css_class = active ? "btn btn-default btn-xs" : "btn btn-secondary btn-xs"
39
41
 
40
42
  link_to range[:label],
41
43
  "#{root_path}?chart_range=#{range[:value]}",
@@ -43,7 +45,7 @@ module JobHarbor
43
45
  end
44
46
 
45
47
  def body
46
- content_tag(:div, class: "sqd-card-body") do
48
+ content_tag(:div, class: "card-content") do
47
49
  safe_join([
48
50
  legend,
49
51
  chart_container
@@ -52,20 +54,20 @@ module JobHarbor
52
54
  end
53
55
 
54
56
  def legend
55
- content_tag(:div, class: "sqd-chart-legend") do
57
+ content_tag(:div, class: "flex gap-4 mb-3") do
56
58
  safe_join([
57
- legend_item("Completed", "sqd-chart-completed"),
58
- legend_item("Failed", "sqd-chart-failed"),
59
- legend_item("Enqueued", "sqd-chart-enqueued")
59
+ legend_item("Completed", "circle-green"),
60
+ legend_item("Failed", "circle-red"),
61
+ legend_item("Enqueued", "circle-sky")
60
62
  ])
61
63
  end
62
64
  end
63
65
 
64
- def legend_item(label, css_class)
65
- content_tag(:div, class: "sqd-legend-item") do
66
+ def legend_item(label, circle_class)
67
+ content_tag(:div, class: "flex items-center gap-1.5 text-xs") do
66
68
  safe_join([
67
- content_tag(:span, "", class: "sqd-legend-color #{css_class}"),
68
- content_tag(:span, label, class: "sqd-legend-label")
69
+ content_tag(:span, "", class: "circle #{circle_class}"),
70
+ content_tag(:span, label, class: "text-muted-foreground")
69
71
  ])
70
72
  end
71
73
  end
@@ -6,7 +6,8 @@ module JobHarbor
6
6
  jobs: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>',
7
7
  queues: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16"/>',
8
8
  workers: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"/>',
9
- search: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>'
9
+ search: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>',
10
+ recurring: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>'
10
11
  }.freeze
11
12
 
12
13
  def initialize(title:, description: nil, icon: :jobs)
@@ -16,10 +17,10 @@ module JobHarbor
16
17
  end
17
18
 
18
19
  def call
19
- content_tag(:div, class: "sqd-empty-state") do
20
+ content_tag(:div, class: "text-center py-12") do
20
21
  safe_join([
21
22
  icon_svg,
22
- content_tag(:h3, @title, class: "sqd-empty-title"),
23
+ content_tag(:h3, @title, class: "text-lg font-semibold mt-4"),
23
24
  description_tag
24
25
  ].compact)
25
26
  end
@@ -29,13 +30,18 @@ module JobHarbor
29
30
 
30
31
  def icon_svg
31
32
  icon_path = ICONS[@icon] || ICONS[:jobs]
32
- content_tag(:svg, icon_path.html_safe, class: "sqd-empty-icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor")
33
+ content_tag(:svg, icon_path.html_safe,
34
+ class: "w-12 h-12 mx-auto text-muted-foreground",
35
+ viewBox: "0 0 24 24",
36
+ fill: "none",
37
+ stroke: "currentColor"
38
+ )
33
39
  end
34
40
 
35
41
  def description_tag
36
42
  return unless @description
37
43
 
38
- content_tag(:p, @description, class: "sqd-empty-description")
44
+ content_tag(:p, @description, class: "text-sm text-muted-foreground mt-1")
39
45
  end
40
46
  end
41
47
  end
@@ -7,7 +7,7 @@ module JobHarbor
7
7
  end
8
8
 
9
9
  def call
10
- content_tag(:div, class: "sqd-card sqd-failure-rates") do
10
+ content_tag(:div, class: "card sqd-failure-rates") do
11
11
  safe_join([
12
12
  header,
13
13
  body
@@ -18,13 +18,13 @@ module JobHarbor
18
18
  private
19
19
 
20
20
  def header
21
- content_tag(:div, class: "sqd-card-header") do
22
- content_tag(:h3, "Failure Rates (24h)", class: "sqd-card-title")
21
+ content_tag(:div, class: "card-header") do
22
+ content_tag(:h3, "Failure Rates (24h)", class: "card-title")
23
23
  end
24
24
  end
25
25
 
26
26
  def body
27
- content_tag(:div, class: "sqd-card-body") do
27
+ content_tag(:div, class: "card-content") do
28
28
  if @stats.empty?
29
29
  empty_state
30
30
  else
@@ -34,7 +34,7 @@ module JobHarbor
34
34
  end
35
35
 
36
36
  def empty_state
37
- content_tag(:p, "No jobs in the last 24 hours", class: "sqd-text-muted")
37
+ content_tag(:p, "No jobs in the last 24 hours", class: "text-sm text-muted-foreground")
38
38
  end
39
39
 
40
40
  def stats_table
@@ -68,7 +68,7 @@ module JobHarbor
68
68
  def stat_row(stat)
69
69
  content_tag(:tr) do
70
70
  safe_join([
71
- content_tag(:td, content_tag(:code, stat[:class_name], class: "sqd-code")),
71
+ content_tag(:td, content_tag(:code, stat[:class_name], class: "text-sm font-mono")),
72
72
  content_tag(:td, stat[:total]),
73
73
  content_tag(:td, stat[:failed]),
74
74
  content_tag(:td, rate_badge(stat[:rate]))
@@ -78,7 +78,7 @@ module JobHarbor
78
78
 
79
79
  def rate_badge(rate)
80
80
  css_class = FailureStats.rate_badge_class(rate)
81
- content_tag(:span, "#{rate}%", class: "sqd-rate-badge #{css_class}")
81
+ content_tag(:span, "#{rate}%", class: "badge #{css_class}")
82
82
  end
83
83
  end
84
84
  end
@@ -12,7 +12,7 @@ module JobHarbor
12
12
  end
13
13
 
14
14
  def call
15
- content_tag(:div, class: "sqd-filters") do
15
+ content_tag(:div, class: "sqd-filters flex gap-4 mb-4 flex-wrap") do
16
16
  safe_join([
17
17
  class_filter,
18
18
  queue_filter
@@ -23,11 +23,11 @@ module JobHarbor
23
23
  private
24
24
 
25
25
  def class_filter
26
- content_tag(:div, class: "sqd-filter-group") do
26
+ content_tag(:div, class: "flex items-center gap-1.5") do
27
27
  safe_join([
28
- content_tag(:label, "Class", class: "sqd-filter-label", for: "class_filter"),
28
+ content_tag(:label, "Class", class: "text-sm text-muted-foreground", for: "class_filter"),
29
29
  content_tag(:select,
30
- class: "sqd-filter-select",
30
+ class: "sqd-filter-select select",
31
31
  id: "class_filter",
32
32
  data: { filter_type: "class_name" }
33
33
  ) do
@@ -41,11 +41,11 @@ module JobHarbor
41
41
  end
42
42
 
43
43
  def queue_filter
44
- content_tag(:div, class: "sqd-filter-group") do
44
+ content_tag(:div, class: "flex items-center gap-1.5") do
45
45
  safe_join([
46
- content_tag(:label, "Queue", class: "sqd-filter-label", for: "queue_filter"),
46
+ content_tag(:label, "Queue", class: "text-sm text-muted-foreground", for: "queue_filter"),
47
47
  content_tag(:select,
48
- class: "sqd-filter-select",
48
+ class: "sqd-filter-select select",
49
49
  id: "queue_filter",
50
50
  data: { filter_type: "queue_name" }
51
51
  ) do
@@ -71,11 +71,9 @@ module JobHarbor
71
71
  def build_filter_url(filter_key, value)
72
72
  query_params = @params.dup
73
73
 
74
- # Preserve the other filter if set
75
74
  query_params[:class_name] = @current_class if @current_class.present? && filter_key != :class_name
76
75
  query_params[:queue_name] = @current_queue if @current_queue.present? && filter_key != :queue_name
77
76
 
78
- # Set the new filter value
79
77
  if value.present?
80
78
  query_params[filter_key] = value
81
79
  else
@@ -23,14 +23,14 @@ module JobHarbor
23
23
 
24
24
  def id_cell
25
25
  content_tag(:td) do
26
- link_to @job.id, job_path(@job), class: "sqd-table-link"
26
+ link_to @job.id, job_path(@job), class: "link font-medium"
27
27
  end
28
28
  end
29
29
 
30
30
  def class_cell
31
31
  content_tag(:td) do
32
32
  safe_join([
33
- content_tag(:code, @job.class_name, class: "sqd-code"),
33
+ content_tag(:code, @job.class_name, class: "text-sm font-mono"),
34
34
  retry_badge_tag
35
35
  ].compact)
36
36
  end
@@ -39,12 +39,12 @@ module JobHarbor
39
39
  def retry_badge_tag
40
40
  return nil unless @job.respond_to?(:retry_badge) && @job.retry_badge.present?
41
41
 
42
- content_tag(:span, @job.retry_badge, class: "sqd-retry-badge")
42
+ content_tag(:span, @job.retry_badge, class: "sqd-retry-badge ml-1")
43
43
  end
44
44
 
45
45
  def queue_cell
46
46
  content_tag(:td) do
47
- link_to @job.queue_name, queue_path(@job.queue_name), class: "sqd-table-link"
47
+ link_to @job.queue_name, queue_path(@job.queue_name), class: "link"
48
48
  end
49
49
  end
50
50
 
@@ -60,7 +60,7 @@ module JobHarbor
60
60
  def running_duration_tag
61
61
  return nil unless @job.respond_to?(:running_duration) && @job.running_duration.present?
62
62
 
63
- content_tag(:span, " (#{@job.running_duration})", class: "sqd-running-duration")
63
+ content_tag(:span, " (#{@job.running_duration})", class: "text-xs text-sky-500")
64
64
  end
65
65
 
66
66
  def scheduled_cell
@@ -75,31 +75,33 @@ module JobHarbor
75
75
 
76
76
  def relative_time_tag
77
77
  if @job.respond_to?(:relative_created_at)
78
- content_tag(:span, @job.relative_created_at, class: "sqd-text-muted sqd-relative-time")
78
+ content_tag(:span, @job.relative_created_at, class: "text-xs text-muted-foreground")
79
79
  else
80
- content_tag(:span, "", class: "sqd-text-muted")
80
+ content_tag(:span, "\u2014", class: "text-muted-foreground")
81
81
  end
82
82
  end
83
83
 
84
84
  def actions_cell
85
- content_tag(:td, class: "sqd-actions") do
86
- safe_join([
87
- retry_button,
88
- discard_button
89
- ].compact)
85
+ content_tag(:td) do
86
+ content_tag(:div, class: "flex items-center gap-1") do
87
+ safe_join([
88
+ retry_button,
89
+ discard_button
90
+ ].compact)
91
+ end
90
92
  end
91
93
  end
92
94
 
93
95
  def retry_button
94
96
  return unless @job.can_retry?
95
97
 
96
- button_to "Retry", retry_job_path(@job), method: :post, class: "sqd-btn sqd-btn-sm sqd-btn-secondary"
98
+ button_to "Retry", retry_job_path(@job), method: :post, class: "btn btn-secondary btn-xs"
97
99
  end
98
100
 
99
101
  def discard_button
100
102
  return unless @job.can_discard?
101
103
 
102
- button_to "Discard", discard_job_path(@job), method: :delete, class: "sqd-btn sqd-btn-sm sqd-btn-danger",
104
+ button_to "Discard", discard_job_path(@job), method: :delete, class: "btn btn-destructive btn-xs",
103
105
  data: { confirm: "Are you sure you want to discard this job?" }
104
106
  end
105
107
  end
@@ -2,14 +2,6 @@
2
2
 
3
3
  module JobHarbor
4
4
  class NavLinkComponent < ApplicationComponent
5
- ICONS = {
6
- dashboard: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>',
7
- jobs: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>',
8
- queues: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16"/>',
9
- workers: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"/>',
10
- recurring: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>'
11
- }.freeze
12
-
13
5
  def initialize(path:, label:, icon:, active: false, badge: nil)
14
6
  @path = path
15
7
  @label = label
@@ -21,8 +13,7 @@ module JobHarbor
21
13
  def call
22
14
  link_to @path, class: css_classes do
23
15
  safe_join([
24
- icon_svg,
25
- content_tag(:span, @label, class: "sqd-nav-label"),
16
+ content_tag(:span, @label),
26
17
  badge_tag
27
18
  ].compact)
28
19
  end
@@ -31,20 +22,15 @@ module JobHarbor
31
22
  private
32
23
 
33
24
  def css_classes
34
- classes = [ "sqd-nav-link" ]
35
- classes << "active" if @active
25
+ classes = [ "sqd-nav-link", "navbar-item" ]
26
+ classes << "active navbar-item-current" if @active
36
27
  classes.join(" ")
37
28
  end
38
29
 
39
- def icon_svg
40
- icon_path = ICONS[@icon] || ICONS[:dashboard]
41
- content_tag(:svg, icon_path.html_safe, class: "sqd-nav-icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor")
42
- end
43
-
44
30
  def badge_tag
45
31
  return nil unless @badge.present? && @badge.to_i > 0
46
32
 
47
- content_tag(:span, @badge, class: "sqd-nav-badge")
33
+ content_tag(:span, @badge, class: "sqd-nav-badge navbar-badge")
48
34
  end
49
35
  end
50
36
  end