tramway 4.0.2 → 4.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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +47 -0
  3. data/app/assets/javascripts/tramway/tramway.js +58 -2
  4. data/app/controllers/tramway/plugins/solid_queue/application_controller.rb +28 -0
  5. data/app/controllers/tramway/plugins/solid_queue/jobs_controller.rb +82 -0
  6. data/app/controllers/tramway/plugins/solid_queue/queues_controller.rb +38 -0
  7. data/app/controllers/tramway/plugins/solid_queue/recurring_tasks_controller.rb +26 -0
  8. data/app/helpers/tramway/plugins/solid_queue/jobs_helper.rb +27 -0
  9. data/app/helpers/tramway/plugins/solid_queue/queues_helper.rb +27 -0
  10. data/app/jobs/tramway/solid_queue/application_job.rb +14 -0
  11. data/app/jobs/tramway/solid_queue/bulk_destroy_job.rb +13 -0
  12. data/app/jobs/tramway/solid_queue/bulk_discard_job.rb +13 -0
  13. data/app/jobs/tramway/solid_queue/bulk_retry_job.rb +13 -0
  14. data/app/jobs/tramway/solid_queue/destroy_all_job.rb +13 -0
  15. data/app/models/tramway/solid_queue/jobs_filter.rb +53 -0
  16. data/app/views/tramway/plugins/solid_queue/_subnav.html.haml +7 -0
  17. data/app/views/tramway/plugins/solid_queue/jobs/index.html.haml +89 -0
  18. data/app/views/tramway/plugins/solid_queue/jobs/show.html.haml +90 -0
  19. data/app/views/tramway/plugins/solid_queue/queues/index.html.haml +46 -0
  20. data/app/views/tramway/plugins/solid_queue/recurring_tasks/index.html.haml +33 -0
  21. data/config/locales/en.yml +62 -0
  22. data/config/routes.rb +10 -0
  23. data/config/tailwind.config.js +1 -0
  24. data/lib/generators/tramway/install/install_generator.rb +12 -0
  25. data/lib/generators/tramway/install/solid_queue_helpers.rb +54 -0
  26. data/lib/tramway/config.rb +7 -1
  27. data/lib/tramway/configs/plugins.rb +66 -0
  28. data/lib/tramway/errors.rb +36 -0
  29. data/lib/tramway/navbar.rb +23 -0
  30. data/lib/tramway/plugins/base.rb +33 -0
  31. data/lib/tramway/plugins/solid_queue/config.rb +13 -0
  32. data/lib/tramway/plugins/solid_queue/plugin.rb +37 -0
  33. data/lib/tramway/plugins/solid_queue/routes.rb +50 -0
  34. data/lib/tramway/plugins/solid_queue.rb +7 -0
  35. data/lib/tramway/plugins.rb +37 -0
  36. data/lib/tramway/version.rb +1 -1
  37. metadata +26 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d1254075cd4e025f5a4f46d4951fc391bd8c90a4c1a486b3bbe471b0d79cfebb
4
- data.tar.gz: d37cb6b0408b55f635f75b8cc8f0fb90d5d5efa454a1db5c4303fdf800e45726
3
+ metadata.gz: a20badcbb5b3d0d460bbadafb95816d4932ce5e8af0d41a9ce2315507220bed0
4
+ data.tar.gz: 800c9136a41a012e3b8581611afa70e666f81c4dab9d331cc7c094d096cb2b86
5
5
  SHA512:
6
- metadata.gz: 29d854a96abfcadda07911e326932a2347c509a0465dc5e6d4c19533dd7b5299f05aa011d3d928490dc6a17969143dc0490f81206618ecd13bd37040bc5306a3
7
- data.tar.gz: d176134cef60ad20d9c2617067d0e4c498a966fef4119ef182dd2d13b32ae7919cce4ab2a3d18d076b0a20a7a6a08bda88b667190c072bfd41a2021ffa84c1af
6
+ metadata.gz: 84e42fc4f2acf64a31007add38185688e84d9728873a528a1d1b787b5b7383e94b5b7ff22ee62c0040eb75f0daae50d0d1c182f68424a39919e3f4ee2ef90a92
7
+ data.tar.gz: de23a5e5b76877268ce42d2371f5db9907c3ce6a517b15ea7b234a2ecc944c6a04fb3b70001b098ab06b07d52f25fdeae1a36ec9112eb2530be0394f719354d2
data/README.md CHANGED
@@ -1,6 +1,11 @@
1
1
  # Tramway
2
2
  Unite Ruby on Rails brilliance. Streamline development with Tramway.
3
3
 
4
+ > **AI agents: read this first.** Before writing or changing any code in a Rails app that uses Tramway, install and
5
+ > use the [`tramway-skill`](https://github.com/Purple-Magic/tramway-skill/) skill — it is mandatory for Tramway work
6
+ > and is the authoritative, up-to-date playbook for Tramway's conventions (CRUD, Forms, Decorators, Navbar, Plugins,
7
+ > and more). Do not generate Tramway-related code from memory alone.
8
+
4
9
  Tramway ships with full CRUD (index, show, create, update, destroy) out of the box. Its install generator also adds a
5
10
  Codex instruction that points agents to the Tramway skill for Tramway-native code generation.
6
11
 
@@ -1766,6 +1771,48 @@ Tramway.configure do |config|
1766
1771
  end
1767
1772
  ```
1768
1773
 
1774
+ ## Plugins
1775
+
1776
+ Plugins add optional, ready-made dashboards to your app, mounted and styled with Tramway. Enable one by name and,
1777
+ optionally, configure it:
1778
+
1779
+ ```ruby
1780
+ Tramway.configure do |config|
1781
+ config.plugins = [:solid_queue]
1782
+ config.plugins.solid_queue = {
1783
+ path: '/jobs'
1784
+ }
1785
+ end
1786
+ ```
1787
+
1788
+ Enabling a plugin also adds a navbar entry for it automatically (see [Tramway
1789
+ Navbar](#tramway-navbar)).
1790
+
1791
+ ### SolidQueue
1792
+
1793
+ The `:solid_queue` plugin is a dashboard for inspecting and managing [SolidQueue](https://github.com/rails/solid_queue)
1794
+ jobs. It requires the `solid_queue` gem in your Gemfile — if it's missing, Tramway raises a clear error explaining
1795
+ how to fix it instead of failing deep inside a controller.
1796
+
1797
+ It gives you:
1798
+
1799
+ - A paginated, filterable jobs list (by status, queue, class name, or free-text search by class name/active job
1800
+ ID/numeric ID), with per-status counts.
1801
+ - A job detail page, including failure details (exception class, message, backtrace) for failed jobs.
1802
+ - Retry, discard and destroy for a single job, or in bulk for a selection of jobs — or "Destroy all" to destroy
1803
+ every job matching the currently active filters at once. Bulk actions and "Destroy all" run in the background
1804
+ (you'll see a flash message confirming the action was queued, not completed instantly) on their own dedicated
1805
+ worker queue, which `tramway:install` sets up for you.
1806
+ - A queues list with size/running/latency/throughput, and pause/resume/clear actions per queue. "Size" only counts
1807
+ jobs waiting to run — "Running" shows jobs currently executing on that queue, so a queue processing one big job
1808
+ (e.g. a large "Destroy all") doesn't look idle just because nothing is waiting behind it.
1809
+ - A recurring tasks list with the ability to enqueue a task immediately.
1810
+ - Jobs, queues, and recurring tasks lists (and the job detail page) refresh themselves in the background every few
1811
+ seconds, so job status changes show up without reloading the page.
1812
+
1813
+ See [docs/users/tramway_solid_queue_plugin.md](docs/users/tramway_solid_queue_plugin.md) for more on what "discard"
1814
+ does and other SolidQueue-specific behavior.
1815
+
1769
1816
  ## Lantern Color Palette
1770
1817
 
1771
1818
  | Type | Color |
@@ -1,5 +1,5 @@
1
1
  import { Controller } from "@hotwired/stimulus"
2
- /* global console */
2
+ /* global console, fetch, setInterval, clearInterval, DOMParser */
3
3
 
4
4
  class TramwaySelect extends Controller {
5
5
  static targets = ["dropdown", "showSelectedArea", "hiddenInput", "caretDown", "caretUp"]
@@ -564,4 +564,60 @@ class Collapsible extends Controller {
564
564
  }
565
565
  }
566
566
 
567
- export { TramwaySelect, UiCheckbox, Tooltip, Navbar, Collapsible }
567
+ // Periodically re-fetches the current page and swaps the innerHTML of each marked
568
+ // "refreshable" target by id, so pages reflect background changes (e.g. job status moving
569
+ // through SolidQueue) without a full reload. A target is skipped while it has a checked
570
+ // checkbox or focus inside it, so an in-progress bulk selection or form entry isn't clobbered.
571
+ class AutoRefresh extends Controller {
572
+ static targets = ["refreshable"]
573
+
574
+ static values = {
575
+ interval: { type: Number, default: 4000 }
576
+ }
577
+
578
+ connect() {
579
+ this.refreshing = false
580
+ this.timer = setInterval(() => this.refresh(), this.intervalValue)
581
+ }
582
+
583
+ disconnect() {
584
+ clearInterval(this.timer)
585
+ }
586
+
587
+ async refresh() {
588
+ if (this.refreshing || document.hidden) return
589
+
590
+ this.refreshing = true
591
+
592
+ try {
593
+ const response = await fetch(window.location.href, { headers: { 'X-Requested-With': 'XMLHttpRequest' } })
594
+
595
+ if (!response.ok) return
596
+
597
+ const next = new DOMParser().parseFromString(await response.text(), 'text/html')
598
+
599
+ this.refreshableTargets.forEach((target) => this.updateTarget(target, next))
600
+ } finally {
601
+ this.refreshing = false
602
+ }
603
+ }
604
+
605
+ updateTarget(target, next) {
606
+ if (!target.id || this.hasPendingInput(target)) return
607
+
608
+ const replacement = next.getElementById(target.id)
609
+
610
+ if (replacement) {
611
+ target.innerHTML = replacement.innerHTML
612
+ }
613
+ }
614
+
615
+ hasPendingInput(target) {
616
+ const focused = target.contains(document.activeElement) &&
617
+ document.activeElement.matches('input, textarea, select')
618
+
619
+ return target.querySelector('input:checked') !== null || focused
620
+ }
621
+ }
622
+
623
+ export { TramwaySelect, UiCheckbox, Tooltip, Navbar, Collapsible, AutoRefresh }
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tramway/plugins'
4
+
5
+ module Tramway
6
+ module Plugins
7
+ module SolidQueue
8
+ # Base controller for the solid_queue plugin dashboard
9
+ class ApplicationController < Tramway.config.application_controller.constantize
10
+ prepend_view_path "#{Gem::Specification.find_by_name('tramway').gem_dir}/app/views"
11
+
12
+ layout 'tramway/layouts/application'
13
+
14
+ helper Tramway::ApplicationHelper
15
+ helper Tramway::Plugins::SolidQueue::JobsHelper
16
+ include Rails.application.routes.url_helpers
17
+
18
+ before_action :ensure_solid_queue_available!
19
+
20
+ private
21
+
22
+ def ensure_solid_queue_available!
23
+ Tramway::Plugins.fetch(:solid_queue).ensure_available!
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway
4
+ module Plugins
5
+ module SolidQueue
6
+ # Lists, filters and manages SolidQueue jobs for the plugin dashboard
7
+ class JobsController < ApplicationController
8
+ def index
9
+ @jobs = jobs_filter.jobs.order(id: :desc).page(params[:page])
10
+ @status_counts = jobs_filter.status_counts
11
+ @queue_names = ::SolidQueue::Job.distinct.order(:queue_name).pluck(:queue_name)
12
+ @class_names = ::SolidQueue::Job.distinct.order(:class_name).pluck(:class_name)
13
+ end
14
+
15
+ def show
16
+ @job = job
17
+ end
18
+
19
+ def destroy
20
+ job.destroy
21
+
22
+ redirect_to jobs_path, notice: t('tramway.plugins.solid_queue.notices.destroyed')
23
+ end
24
+
25
+ def retry
26
+ job.retry
27
+
28
+ redirect_to jobs_path, notice: t('tramway.plugins.solid_queue.notices.retried')
29
+ end
30
+
31
+ def discard
32
+ job.discard
33
+
34
+ redirect_to jobs_path, notice: t('tramway.plugins.solid_queue.notices.discarded')
35
+ end
36
+
37
+ def bulk_retry
38
+ Tramway::SolidQueue::BulkRetryJob.perform_later(selected_job_ids)
39
+
40
+ redirect_to jobs_path, notice: t('tramway.plugins.solid_queue.notices.bulk_retry_enqueued')
41
+ end
42
+
43
+ def bulk_discard
44
+ Tramway::SolidQueue::BulkDiscardJob.perform_later(selected_job_ids)
45
+
46
+ redirect_to jobs_path, notice: t('tramway.plugins.solid_queue.notices.bulk_discard_enqueued')
47
+ end
48
+
49
+ def bulk_destroy
50
+ Tramway::SolidQueue::BulkDestroyJob.perform_later(selected_job_ids)
51
+
52
+ redirect_to jobs_path, notice: t('tramway.plugins.solid_queue.notices.bulk_destroy_enqueued')
53
+ end
54
+
55
+ def destroy_all
56
+ Tramway::SolidQueue::DestroyAllJob.perform_later(filter_params)
57
+
58
+ redirect_to jobs_path(request.query_parameters.except('page')),
59
+ notice: t('tramway.plugins.solid_queue.notices.destroy_all_enqueued')
60
+ end
61
+
62
+ private
63
+
64
+ def job
65
+ @job ||= ::SolidQueue::Job.find(params.expect(:id))
66
+ end
67
+
68
+ def jobs_filter
69
+ @jobs_filter ||= Tramway::SolidQueue::JobsFilter.new(filter_params)
70
+ end
71
+
72
+ def filter_params
73
+ params.permit(:status, :queue_name, :class_name, :query).to_h
74
+ end
75
+
76
+ def selected_job_ids
77
+ Array(params[:job_ids]).map(&:to_i)
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway
4
+ module Plugins
5
+ module SolidQueue
6
+ # Lists SolidQueue queues and lets the user pause/resume/clear them
7
+ class QueuesController < ApplicationController
8
+ def index
9
+ @queues = ::SolidQueue::Queue.all.sort_by(&:name)
10
+ end
11
+
12
+ def pause
13
+ queue.pause
14
+
15
+ redirect_to queues_path, notice: t('tramway.plugins.solid_queue.notices.paused')
16
+ end
17
+
18
+ def resume
19
+ queue.resume
20
+
21
+ redirect_to queues_path, notice: t('tramway.plugins.solid_queue.notices.resumed')
22
+ end
23
+
24
+ def clear
25
+ queue.clear
26
+
27
+ redirect_to queues_path, notice: t('tramway.plugins.solid_queue.notices.cleared')
28
+ end
29
+
30
+ private
31
+
32
+ def queue
33
+ @queue ||= ::SolidQueue::Queue.find_by_name(params.expect(:name))
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway
4
+ module Plugins
5
+ module SolidQueue
6
+ # Lists SolidQueue recurring tasks and lets the user enqueue one immediately
7
+ class RecurringTasksController < ApplicationController
8
+ def index
9
+ @recurring_tasks = ::SolidQueue::RecurringTask.order(:key)
10
+ end
11
+
12
+ def enqueue
13
+ recurring_task.enqueue(at: Time.current)
14
+
15
+ redirect_to recurring_tasks_path, notice: t('tramway.plugins.solid_queue.notices.enqueued')
16
+ end
17
+
18
+ private
19
+
20
+ def recurring_task
21
+ @recurring_task ||= ::SolidQueue::RecurringTask.find_by!(key: params.expect(:key))
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway
4
+ module Plugins
5
+ module SolidQueue
6
+ # View helpers for rendering SolidQueue job statuses in the plugin dashboard
7
+ module JobsHelper
8
+ STATUS_BADGE_TYPES = {
9
+ ready: :hope,
10
+ claimed: :love,
11
+ scheduled: :fear,
12
+ blocked: :warning,
13
+ failed: :rage,
14
+ finished: :will
15
+ }.freeze
16
+
17
+ def solid_queue_status_badge_type(status)
18
+ STATUS_BADGE_TYPES.fetch(status&.to_sym, :default)
19
+ end
20
+
21
+ def solid_queue_status_label(status)
22
+ t("tramway.plugins.solid_queue.statuses.#{status}", default: status.to_s.humanize)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway
4
+ module Plugins
5
+ module SolidQueue
6
+ # View helpers for rendering SolidQueue queue throughput and running-job counts in the plugin dashboard
7
+ module QueuesHelper
8
+ THROUGHPUT_WINDOW = 5.minutes
9
+
10
+ def solid_queue_throughput_label(queue)
11
+ t('tramway.plugins.solid_queue.queues.throughput_value', default: '%<rate>s/min',
12
+ rate: solid_queue_throughput(queue))
13
+ end
14
+
15
+ def solid_queue_throughput(queue)
16
+ finished_count = ::SolidQueue::Job.where(queue_name: queue.name, finished_at: THROUGHPUT_WINDOW.ago..).count
17
+
18
+ (finished_count / THROUGHPUT_WINDOW.in_minutes).round(1)
19
+ end
20
+
21
+ def solid_queue_running_count(queue)
22
+ ::SolidQueue::Job.where(queue_name: queue.name).joins(:claimed_execution).count
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway
4
+ module SolidQueue
5
+ # Base job for the plugin's bulk jobs actions (retry/discard/destroy), routed to their
6
+ # own dedicated worker queue so a large bulk action can't starve other application jobs.
7
+ # The `tramway:install` generator configures this queue with 20 worker threads.
8
+ class ApplicationJob < ActiveJob::Base
9
+ BATCH_SIZE = 1000
10
+
11
+ queue_as :tramway_solid_queue_bulk_actions
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway
4
+ module SolidQueue
5
+ # Destroys the given jobs, in batches so a large selection doesn't load/destroy
6
+ # everything in a single pass.
7
+ class BulkDestroyJob < ApplicationJob
8
+ def perform(job_ids)
9
+ ::SolidQueue::Job.where(id: job_ids).in_batches(of: BATCH_SIZE, &:destroy_all)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway
4
+ module SolidQueue
5
+ # Discards the given jobs, in batches so a large selection doesn't load everything
6
+ # in a single pass. `discard` is a per-record SolidQueue operation, not a bulk SQL one.
7
+ class BulkDiscardJob < ApplicationJob
8
+ def perform(job_ids)
9
+ ::SolidQueue::Job.where(id: job_ids).find_each(batch_size: BATCH_SIZE, &:discard)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway
4
+ module SolidQueue
5
+ # Retries the given jobs, in batches so a large selection doesn't load everything
6
+ # in a single pass. `retry` is a per-record SolidQueue operation, not a bulk SQL one.
7
+ class BulkRetryJob < ApplicationJob
8
+ def perform(job_ids)
9
+ ::SolidQueue::Job.where(id: job_ids).find_each(batch_size: BATCH_SIZE, &:retry)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway
4
+ module SolidQueue
5
+ # Destroys every job matching the given filters, in batches so a large match set
6
+ # doesn't load/destroy everything in a single pass.
7
+ class DestroyAllJob < ApplicationJob
8
+ def perform(filters = {})
9
+ Tramway::SolidQueue::JobsFilter.new(filters).jobs.in_batches(of: BATCH_SIZE, &:destroy_all)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway
4
+ module SolidQueue
5
+ # Shared status/queue/class/search filtering for SolidQueue jobs, used by both the
6
+ # jobs controller (for the index page) and the background jobs that process a
7
+ # filtered set of jobs (e.g. "Destroy all"), since a job's `perform` only receives
8
+ # plain serializable arguments, not a live `ActionController::Parameters`.
9
+ class JobsFilter
10
+ STATUS_FILTERS = {
11
+ 'ready' => ->(relation) { relation.joins(:ready_execution) },
12
+ 'claimed' => ->(relation) { relation.joins(:claimed_execution) },
13
+ 'scheduled' => ->(relation) { relation.joins(:scheduled_execution) },
14
+ 'blocked' => ->(relation) { relation.joins(:blocked_execution) },
15
+ 'failed' => ->(relation) { relation.joins(:failed_execution) },
16
+ 'finished' => ->(relation) { relation.finished }
17
+ }.freeze
18
+
19
+ def initialize(filters = {})
20
+ @filters = filters.to_h.stringify_keys
21
+ end
22
+
23
+ def jobs
24
+ relation = apply_status_filter(::SolidQueue::Job.all)
25
+ relation = relation.where(queue_name: filters['queue_name']) if filters['queue_name'].present?
26
+ relation = relation.where(class_name: filters['class_name']) if filters['class_name'].present?
27
+
28
+ apply_search(relation).distinct
29
+ end
30
+
31
+ def status_counts
32
+ STATUS_FILTERS.transform_values { |scope| scope.call(::SolidQueue::Job.all).count }
33
+ end
34
+
35
+ private
36
+
37
+ attr_reader :filters
38
+
39
+ def apply_status_filter(relation)
40
+ STATUS_FILTERS.fetch(filters['status']) { ->(rel) { rel } }.call(relation)
41
+ end
42
+
43
+ def apply_search(relation)
44
+ query = filters['query'].to_s.strip
45
+
46
+ return relation if query.blank?
47
+ return relation.where(id: query) if query.match?(/\A\d+\z/)
48
+
49
+ relation.where('class_name LIKE :query OR active_job_id LIKE :query', query: "%#{query}%")
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,7 @@
1
+ .flex.gap-2.mt-2
2
+ = tramway_button tag: :a, text: t('tramway.plugins.solid_queue.jobs.title'), path: jobs_path,
3
+ type: (current_page?(jobs_path) ? :primary : :inverse), size: :small
4
+ = tramway_button tag: :a, text: t('tramway.plugins.solid_queue.queues.title'), path: queues_path,
5
+ type: (current_page?(queues_path) ? :primary : :inverse), size: :small
6
+ = tramway_button tag: :a, text: t('tramway.plugins.solid_queue.recurring_tasks.title'), path: recurring_tasks_path,
7
+ type: (current_page?(recurring_tasks_path) ? :primary : :inverse), size: :small
@@ -0,0 +1,89 @@
1
+ - content_for :title, t('tramway.plugins.solid_queue.jobs.title', default: 'Background Jobs')
2
+
3
+ = tramway_container do
4
+ .flex.justify-between.items-center
5
+ = tramway_title do
6
+ = content_for :title
7
+
8
+ = render 'tramway/plugins/solid_queue/subnav'
9
+
10
+ %div{ data: { controller: 'tramway--auto-refresh' } }
11
+ #tramway-solid-queue-jobs-status-filters.flex.flex-wrap.gap-2.mt-2{ data: { 'tramway--auto-refresh-target': 'refreshable' } }
12
+ - @status_counts.each do |status, count|
13
+ - status_params = request.query_parameters.except('page').merge(status:)
14
+ - button_type = status == params[:status] ? :primary : :inverse
15
+ = tramway_button text: "#{solid_queue_status_label(status)} · #{count}",
16
+ path: jobs_path(status_params), type: button_type, size: :small
17
+
18
+ - if params[:status].present?
19
+ - clear_params = request.query_parameters.except('page', 'status')
20
+ = tramway_button tag: :a, path: jobs_path(clear_params), type: :inverse,
21
+ size: :small, tooltip: { text: t('tramway.actions.clear_filter'), event: :hover } do
22
+ %i.fa.fa-times{ 'aria-hidden' => 'true' }
23
+
24
+ - all_queues_option = [t('tramway.plugins.solid_queue.jobs.all_queues', default: 'All queues'), '']
25
+ - queue_name_options = [all_queues_option] + @queue_names.map { |name| [name, name] }
26
+ - all_classes_option = [t('tramway.plugins.solid_queue.jobs.all_classes', default: 'All classes'), '']
27
+ - class_name_options = [all_classes_option] + @class_names.map { |name| [name, name] }
28
+
29
+ = form_with url: jobs_path, method: :get, local: true, builder: Tramway::Form::Builder, horizontal: true do |f|
30
+ .flex.flex-wrap.items-center.gap-4.mt-2
31
+ = f.select :queue_name, queue_name_options, label: false, selected: params[:queue_name].to_s
32
+ = f.select :class_name, class_name_options, label: false, selected: params[:class_name].to_s
33
+ = f.text_field :query, label: false, value: params[:query], placeholder: t('tramway.actions.search')
34
+ = f.submit nil, 'aria-label' => t('tramway.actions.search') do
35
+ %i.fa.fa-search{ 'aria-hidden' => 'true' }
36
+
37
+ .flex.justify-between.gap-2.mt-4
38
+ = tramway_button text: t('tramway.actions.destroy_all'), path: destroy_all_jobs_path(request.query_parameters.except('page')),
39
+ method: :post, type: :rage, size: :small
40
+
41
+ = form_with url: bulk_destroy_jobs_path, method: :post, id: 'tramway-solid-queue-bulk-jobs-form', local: true do
42
+ .flex.gap-2
43
+ = tramway_button text: t('tramway.actions.retry'), tag: :button, type: :will, size: :small,
44
+ formaction: bulk_retry_jobs_path
45
+ = tramway_button text: t('tramway.actions.discard'), tag: :button, type: :warning, size: :small,
46
+ formaction: bulk_discard_jobs_path
47
+ = tramway_button text: t('tramway.actions.destroy'), tag: :button, type: :rage, size: :small,
48
+ formaction: bulk_destroy_jobs_path
49
+
50
+ #tramway-solid-queue-jobs-table{ data: { 'tramway--auto-refresh-target': 'refreshable' } }
51
+ = tramway_table class: 'mt-4' do
52
+ = tramway_header headers: ['', 'ID', t('tramway.plugins.solid_queue.jobs.class_name', default: 'Class'),
53
+ t('tramway.plugins.solid_queue.jobs.queue_name', default: 'Queue'),
54
+ t('tramway.plugins.solid_queue.jobs.status', default: 'Status'),
55
+ t('tramway.plugins.solid_queue.jobs.scheduled_at', default: 'Scheduled at'),
56
+ t('tramway.table.actions_header')]
57
+ - @jobs.each do |job|
58
+ = tramway_row do
59
+ = tramway_cell do
60
+ = check_box_tag 'job_ids[]', job.id, false, id: "tramway-solid-queue-job-#{job.id}",
61
+ form: 'tramway-solid-queue-bulk-jobs-form'
62
+ = tramway_cell do
63
+ = job.id
64
+ = tramway_cell do
65
+ = job.class_name
66
+ = tramway_cell do
67
+ = job.queue_name
68
+ = tramway_cell do
69
+ = tramway_badge text: solid_queue_status_label(job.status),
70
+ type: solid_queue_status_badge_type(job.status)
71
+ = tramway_cell do
72
+ = job.scheduled_at
73
+ = tramway_cell do
74
+ .flex.gap-2
75
+ = tramway_button tag: :a, path: job_path(job.id), type: :inverse, size: :small do
76
+ %i.fa.fa-eye{ 'aria-hidden' => 'true' }
77
+ - if job.status == :failed
78
+ = tramway_button path: retry_job_path(job.id), method: :post, type: :will, size: :small do
79
+ %i.fa.fa-redo{ 'aria-hidden' => 'true' }
80
+ - if job.status != :finished
81
+ = tramway_button path: discard_job_path(job.id), method: :post, type: :warning, size: :small do
82
+ %i.fa.fa-ban{ 'aria-hidden' => 'true' }
83
+ = tramway_button path: job_path(job.id), method: :delete, type: :rage, size: :small do
84
+ %i.fa.fa-trash{ 'aria-hidden' => 'true' }
85
+
86
+ .flex.flex-col.items-center.mt-4.gap-2
87
+ = paginate @jobs
88
+ %p.sm:hidden.text-xs.text-zinc-500
89
+ = page_entries_info @jobs