solid_bro 0.1.2

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.
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidBro
4
+ class JobsController < ApplicationController
5
+ JOB_SCOPES = %w[all failed in_progress blocked scheduled finished].freeze
6
+
7
+ def index
8
+ @scope = JOB_SCOPES.include?(params[:scope]) ? params[:scope] : "all"
9
+ @counts = job_counts
10
+ base = scope_relation(@scope)
11
+ base = apply_filters(base)
12
+ base = base.preload(:ready_execution, :claimed_execution, :blocked_execution, :scheduled_execution, :failed_execution)
13
+ # Support both Pagy 9.x (items) and Pagy 8+/43.x (limit)
14
+ pagy_options = if defined?(Pagy::DEFAULT) && Pagy::DEFAULT.key?(:items)
15
+ { items: 25 }
16
+ else
17
+ { limit: 25 }
18
+ end
19
+ @pagy, @jobs = pagy(base.order(created_at: :desc), **pagy_options)
20
+ end
21
+
22
+ def show
23
+ @job = SolidQueue::Job.find(params[:id])
24
+ end
25
+
26
+ def retry
27
+ @job = SolidQueue::Job.find(params[:id])
28
+ @job.retry if @job.respond_to?(:retry) && @job.failed?
29
+ redirect_to jobs_path(scope: "failed"), notice: "Job retry requested."
30
+ rescue StandardError => e
31
+ redirect_to job_path(@job), alert: "Retry failed: #{e.message}"
32
+ end
33
+
34
+ def discard
35
+ @job = SolidQueue::Job.find(params[:id])
36
+ @job.discard
37
+ redirect_to jobs_path(scope: params[:scope].presence || "all"), notice: "Job discarded."
38
+ rescue StandardError => e
39
+ redirect_to job_path(@job), alert: "Discard failed: #{e.message}"
40
+ end
41
+
42
+ private
43
+
44
+ def scope_relation(scope)
45
+ case scope
46
+ when "failed" then SolidQueue::Job.failed
47
+ when "in_progress" then SolidQueue::Job.joins(:claimed_execution)
48
+ when "blocked" then SolidQueue::Job.joins(:blocked_execution)
49
+ when "scheduled" then SolidQueue::Job.joins(:scheduled_execution)
50
+ when "finished" then SolidQueue::Job.finished
51
+ else SolidQueue::Job
52
+ end
53
+ end
54
+
55
+ def job_counts
56
+ {
57
+ "all" => SolidQueue::Job.count,
58
+ "failed" => SolidQueue::Job.failed.count,
59
+ "in_progress" => SolidQueue::Job.joins(:claimed_execution).count,
60
+ "blocked" => SolidQueue::Job.joins(:blocked_execution).count,
61
+ "scheduled" => SolidQueue::Job.joins(:scheduled_execution).count,
62
+ "finished" => SolidQueue::Job.finished.count
63
+ }
64
+ end
65
+
66
+ def apply_filters(relation)
67
+ table = relation.respond_to?(:table_name) ? "#{relation.table_name}." : "solid_queue_jobs."
68
+
69
+ if params[:class_name].present?
70
+ term = params[:class_name].to_s.downcase
71
+ term = SolidQueue::Job.sanitize_sql_like(term)
72
+ relation = relation.where("LOWER(class_name) LIKE ?", "%#{term}%")
73
+ end
74
+
75
+ if params[:queue_name].present?
76
+ term = params[:queue_name].to_s.downcase
77
+ term = SolidQueue::Job.sanitize_sql_like(term)
78
+ relation = relation.where("LOWER(queue_name) LIKE ?", "%#{term}%")
79
+ end
80
+
81
+ if @scope == "finished"
82
+ relation = relation.where("finished_at >= ?", parse_datetime(params[:finished_at_start])) if params[:finished_at_start].present?
83
+ relation = relation.where("finished_at <= ?", parse_datetime(params[:finished_at_end])) if params[:finished_at_end].present?
84
+ else
85
+ relation = relation.where("#{table}created_at >= ?", parse_datetime(params[:created_at_start])) if params[:created_at_start].present?
86
+ relation = relation.where("#{table}created_at <= ?", parse_datetime(params[:created_at_end])) if params[:created_at_end].present?
87
+ end
88
+
89
+ relation
90
+ end
91
+
92
+ def parse_datetime(value)
93
+ return nil if value.blank?
94
+ Time.zone.parse(value.to_s)
95
+ rescue ArgumentError
96
+ nil
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,7 @@
1
+ module SolidBro
2
+ class ProcessesController < ApplicationController
3
+ def index
4
+ @processes = SolidQueue::Process.all
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,25 @@
1
+ module SolidBro
2
+ class QueuesController < ApplicationController
3
+ def index
4
+ # Aggregate job counts by queue
5
+ @queues = SolidQueue::Job.group(:queue_name).count
6
+ end
7
+
8
+ def pause
9
+ # Placeholder for pausing queue
10
+ redirect_to queues_path, notice: "Queue pause logic not implemented."
11
+ end
12
+
13
+ def resume
14
+ # Placeholder for resuming queue
15
+ redirect_to queues_path, notice: "Queue resume logic not implemented."
16
+ end
17
+
18
+ def destroy_jobs
19
+ queue_name = params[:id]
20
+ SolidQueue::Job.where(queue_name: queue_name).delete_all
21
+
22
+ redirect_to queues_path, notice: "All jobs in queue '#{queue_name}' deleted."
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidBro
4
+ class RecurringTasksController < ApplicationController
5
+ def index
6
+ @recurring_tasks = SolidQueue::RecurringTask.order(:key)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,38 @@
1
+ module SolidBro
2
+ module ApplicationHelper
3
+ # Support both Pagy 9.x (Frontend) and Pagy 8+/43.x (no separate module needed)
4
+ if defined?(Pagy::Frontend)
5
+ include Pagy::Frontend
6
+ end
7
+
8
+ # Pretty-print job arguments for display
9
+ def format_job_arguments(arguments)
10
+ return "—" if arguments.blank?
11
+ JSON.pretty_generate(arguments)
12
+ rescue StandardError
13
+ # Fallback: break Ruby inspect for readability
14
+ arguments.inspect.gsub("=>", " => ").gsub(", ", ",\n ")
15
+ end
16
+
17
+ # Filter params to preserve when changing job scope tabs
18
+ def filter_params_for_link
19
+ params.permit(:class_name, :queue_name, :created_at_start, :created_at_end, :finished_at_start, :finished_at_end).to_h.compact_blank
20
+ end
21
+
22
+ def jobs_tab_class(scope)
23
+ (params[:scope].presence || "all") == scope ? "active" : ""
24
+ end
25
+
26
+ def jobs_scope_title(scope)
27
+ case scope
28
+ when "all" then "All"
29
+ when "failed" then "Failed"
30
+ when "in_progress" then "In progress"
31
+ when "blocked" then "Blocked"
32
+ when "scheduled" then "Scheduled"
33
+ when "finished" then "Finished"
34
+ else "All"
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,4 @@
1
+ module SolidBro
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module SolidBro
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: "from@example.com"
4
+ layout "mailer"
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module SolidBro
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,56 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>SolidBro Dashboard</title>
7
+ <%= csrf_meta_tags %>
8
+ <%= csp_meta_tag %>
9
+ <%= stylesheet_link_tag "solid_bro/application", media: "all" %>
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
11
+ <%= javascript_include_tag "solid_bro/application", defer: true %>
12
+ </head>
13
+
14
+ <body>
15
+ <header class="top-menu">
16
+ <div class="logo">SolidBro</div>
17
+ <nav>
18
+ <ul>
19
+ <li><%= link_to "Jobs", jobs_path, class: (controller_name == 'jobs' ? 'active' : '') %></li>
20
+ <li><%= link_to "Queues", queues_path, class: (controller_name == 'queues' ? 'active' : '') %></li>
21
+ <li><%= link_to "Workers", processes_path, class: (controller_name == 'processes' ? 'active' : '') %></li>
22
+ <li><%= link_to "Recurring tasks", recurring_tasks_path, class: (controller_name == 'recurring_tasks' ? 'active' : '') %></li>
23
+ </ul>
24
+ </nav>
25
+ <div class="user-profile">
26
+ <span class="avatar">SB</span>
27
+ </div>
28
+ </header>
29
+
30
+ <main class="content">
31
+ <% if notice %>
32
+ <div style="background-color: var(--success-color); color: white; padding: 1rem; margin-bottom: 1rem; border-radius: 6px;">
33
+ <%= notice %>
34
+ </div>
35
+ <% end %>
36
+ <% if alert %>
37
+ <div style="background-color: var(--danger-color); color: white; padding: 1rem; margin-bottom: 1rem; border-radius: 6px;">
38
+ <%= alert %>
39
+ </div>
40
+ <% end %>
41
+
42
+ <%= yield %>
43
+ </main>
44
+
45
+ <footer class="footer">
46
+ <div class="footer-content">
47
+ <div class="copyright">
48
+ &copy; <%= Time.current.year %> SolidBro. All rights reserved. v<%= SolidBro::VERSION %>
49
+ </div>
50
+ <div class="footer-links">
51
+ <!-- Add footer links if needed -->
52
+ </div>
53
+ </div>
54
+ </footer>
55
+ </body>
56
+ </html>
@@ -0,0 +1,139 @@
1
+ <nav class="jobs-tabs" aria-label="Job status">
2
+ <ul class="jobs-tabs-list">
3
+ <li><%= link_to "All", jobs_path(scope: "all", **filter_params_for_link), class: jobs_tab_class("all") %></li>
4
+ <li><%= link_to "Failed jobs (#{@counts['failed']})", jobs_path(scope: "failed", **filter_params_for_link), class: jobs_tab_class("failed") %></li>
5
+ <li><%= link_to "In Progress (#{@counts['in_progress']})", jobs_path(scope: "in_progress", **filter_params_for_link), class: jobs_tab_class("in_progress") %></li>
6
+ <li><%= link_to "Blocked (#{@counts['blocked']})", jobs_path(scope: "blocked", **filter_params_for_link), class: jobs_tab_class("blocked") %></li>
7
+ <li><%= link_to "Scheduled (#{@counts['scheduled']})", jobs_path(scope: "scheduled", **filter_params_for_link), class: jobs_tab_class("scheduled") %></li>
8
+ <li><%= link_to "Finished (#{@counts['finished']})", jobs_path(scope: "finished", **filter_params_for_link), class: jobs_tab_class("finished") %></li>
9
+ </ul>
10
+ </nav>
11
+
12
+ <%= form_with url: jobs_path, method: :get, local: true, class: "jobs-filters" do |f| %>
13
+ <%= hidden_field_tag :scope, @scope %>
14
+ <div class="jobs-filters-grid">
15
+ <div class="jobs-filter-field">
16
+ <%= label_tag :class_name, "Job class name" %>
17
+ <%= text_field_tag :class_name, params[:class_name], placeholder: "Filter by job class...", class: "jobs-filter-input" %>
18
+ </div>
19
+ <div class="jobs-filter-field">
20
+ <%= label_tag :queue_name, "Queue name" %>
21
+ <%= text_field_tag :queue_name, params[:queue_name], placeholder: "Filter by queue name...", class: "jobs-filter-input" %>
22
+ </div>
23
+ <% if @scope == "finished" %>
24
+ <div class="jobs-filter-field">
25
+ <%= label_tag :finished_at_start, "Finished at start" %>
26
+ <%= text_field_tag :finished_at_start, params[:finished_at_start], class: "jobs-filter-input", type: "datetime-local" %>
27
+ </div>
28
+ <div class="jobs-filter-field">
29
+ <%= label_tag :finished_at_end, "Finished at end" %>
30
+ <%= text_field_tag :finished_at_end, params[:finished_at_end], class: "jobs-filter-input", type: "datetime-local" %>
31
+ </div>
32
+ <% else %>
33
+ <div class="jobs-filter-field">
34
+ <%= label_tag :created_at_start, "Created at start" %>
35
+ <%= text_field_tag :created_at_start, params[:created_at_start], class: "jobs-filter-input", type: "datetime-local" %>
36
+ </div>
37
+ <div class="jobs-filter-field">
38
+ <%= label_tag :created_at_end, "Created at end" %>
39
+ <%= text_field_tag :created_at_end, params[:created_at_end], class: "jobs-filter-input", type: "datetime-local" %>
40
+ </div>
41
+ <% end %>
42
+ <div class="jobs-filter-actions">
43
+ <%= submit_tag "Filter", class: "btn-primary" %>
44
+ <%= link_to "Clear", jobs_path(scope: @scope), class: "btn-icon" %>
45
+ </div>
46
+ </div>
47
+ <% end %>
48
+
49
+ <div class="table-container">
50
+ <div class="table-header">
51
+ <h2><%= jobs_scope_title(@scope) %> jobs</h2>
52
+ </div>
53
+ <table>
54
+ <thead>
55
+ <tr>
56
+ <th>ID</th>
57
+ <th>Class / Queue</th>
58
+ <th>Arguments</th>
59
+ <th>Status</th>
60
+ <th><%= @scope == "finished" ? "Finished" : "Created" %> At</th>
61
+ <th class="text-right">Actions</th>
62
+ </tr>
63
+ </thead>
64
+ <tbody>
65
+ <% if @jobs.any? %>
66
+ <% @jobs.each do |job| %>
67
+ <tr>
68
+ <td>
69
+ <%= link_to job.id, job_path(job), class: "link-accent" %>
70
+ </td>
71
+ <td>
72
+ <div class="font-medium"><%= job.class_name %></div>
73
+ <div class="text-muted text-xs"><%= job.queue_name %></div>
74
+ </td>
75
+ <td title="<%= job.arguments.inspect %>">
76
+ <div class="truncate max-w-xs"><%= truncate(job.arguments.inspect, length: 40) %></div>
77
+ </td>
78
+ <td>
79
+ <% status_class = case job.status.to_s
80
+ when "failed" then "status-failed"
81
+ when "finished" then "status-finished"
82
+ when "claimed" then "status-claimed"
83
+ when "ready" then "status-ready"
84
+ else "status-default"
85
+ end %>
86
+ <span class="status-badge <%= status_class %>"><%= job.status %></span>
87
+ </td>
88
+ <td>
89
+ <%= @scope == "finished" && job.finished_at ? "#{time_ago_in_words(job.finished_at)} ago" : "#{time_ago_in_words(job.created_at)} ago" %>
90
+ </td>
91
+ <td class="text-right">
92
+ <div class="job-action-buttons">
93
+ <% if job.status.to_s == "failed" %>
94
+ <%= button_to retry_job_path(job),
95
+ method: :put,
96
+ class: "btn-icon",
97
+ params: { scope: @scope },
98
+ title: "Retry job" do %>
99
+ <svg viewBox="0 0 20 20" aria-hidden="true" class="job-action-icon">
100
+ <path d="M4 4v4h4" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
101
+ <path d="M4.75 10a5.25 5.25 0 0 0 9.16 3.71L15.5 12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
102
+ <path d="M15.25 8A5.25 5.25 0 0 0 6.09 4.29L4.5 6" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
103
+ </svg>
104
+ <% end %>
105
+ <% end %>
106
+
107
+ <%= button_to discard_job_path(job),
108
+ method: :delete,
109
+ class: "btn-icon btn-danger",
110
+ params: { scope: @scope },
111
+ title: "Discard job" do %>
112
+ <svg viewBox="0 0 20 20" aria-hidden="true" class="job-action-icon">
113
+ <path d="M4 6h12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
114
+ <path d="M8 6V4h4v2" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
115
+ <path d="M7 6v9a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1V6" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
116
+ </svg>
117
+ <% end %>
118
+ </div>
119
+ </td>
120
+ </tr>
121
+ <% end %>
122
+ <% else %>
123
+ <tr>
124
+ <td colspan="6" class="text-muted">No jobs found.</td>
125
+ </tr>
126
+ <% end %>
127
+ </tbody>
128
+ </table>
129
+ <% if @pagy.pages > 1 %>
130
+ <div class="pagination-wrapper">
131
+ <%# Support both Pagy 9.x (pagy_nav) and Pagy 8+/43.x (series_nav) %>
132
+ <% if @pagy.respond_to?(:series_nav) %>
133
+ <%== @pagy.series_nav %>
134
+ <% else %>
135
+ <%== pagy_nav(@pagy) %>
136
+ <% end %>
137
+ </div>
138
+ <% end %>
139
+ </div>
@@ -0,0 +1,73 @@
1
+ <div class="job-page-header">
2
+ <div class="job-page-title">
3
+ <h1>Job #<%= @job.id %></h1>
4
+ <p class="job-page-subtitle"><%= @job.class_name %> on <span class="job-queue-name"><%= @job.queue_name %></span></p>
5
+ </div>
6
+ <div class="job-page-actions">
7
+ <% if @job.status.to_s == "failed" %>
8
+ <%= button_to "Retry", retry_job_path(@job), method: :put, class: "btn-icon" %>
9
+ <% end %>
10
+ <%= button_to "Discard", discard_job_path(@job), method: :delete, class: "btn-icon btn-danger" %>
11
+ </div>
12
+ </div>
13
+
14
+ <div class="table-container">
15
+ <div class="table-header">
16
+ <h2>Details</h2>
17
+ </div>
18
+ <table class="job-details-table">
19
+ <tbody>
20
+ <tr>
21
+ <th scope="row">ID</th>
22
+ <td><%= @job.id %></td>
23
+ </tr>
24
+ <tr>
25
+ <th scope="row">Class</th>
26
+ <td><%= @job.class_name %></td>
27
+ </tr>
28
+ <tr>
29
+ <th scope="row">Queue</th>
30
+ <td><%= @job.queue_name %></td>
31
+ </tr>
32
+ <tr>
33
+ <th scope="row">Status</th>
34
+ <td>
35
+ <% status_class = case @job.status.to_s
36
+ when "failed" then "status-failed"
37
+ when "finished" then "status-finished"
38
+ when "claimed" then "status-claimed"
39
+ when "ready" then "status-ready"
40
+ else "status-default"
41
+ end %>
42
+ <span class="status-badge <%= status_class %>"><%= @job.status %></span>
43
+ </td>
44
+ </tr>
45
+ <tr>
46
+ <th scope="row">Created at</th>
47
+ <td><%= @job.created_at ? l(@job.created_at, format: :long) : "—" %></td>
48
+ </tr>
49
+ <tr>
50
+ <th scope="row">Scheduled at</th>
51
+ <td><%= @job.scheduled_at ? l(@job.scheduled_at, format: :long) : "—" %></td>
52
+ </tr>
53
+ <tr>
54
+ <th scope="row">Arguments</th>
55
+ <td>
56
+ <pre class="job-arguments-block"><%= format_job_arguments(@job.arguments) %></pre>
57
+ </td>
58
+ </tr>
59
+ </tbody>
60
+ </table>
61
+ </div>
62
+
63
+ <% if @job.respond_to?(:failed_execution) && @job.failed_execution %>
64
+ <div class="table-container table-container--error">
65
+ <div class="table-header">
66
+ <h2>Exception</h2>
67
+ </div>
68
+ <div class="exception-content">
69
+ <p class="exception-message"><%= @job.failed_execution.error_class %>: <%= @job.failed_execution.error_message %></p>
70
+ <pre class="job-arguments-block job-arguments-block--backtrace"><%= @job.failed_execution.backtrace.join("\n") %></pre>
71
+ </div>
72
+ </div>
73
+ <% end %>
@@ -0,0 +1,37 @@
1
+ <div class="table-container">
2
+ <div class="table-header">
3
+ <h2>Workers</h2>
4
+ </div>
5
+ <table>
6
+ <thead>
7
+ <tr>
8
+ <th>ID</th>
9
+ <th>Name</th>
10
+ <th>Hostname</th>
11
+ <th>Kind</th>
12
+ <th>Last Heartbeat</th>
13
+ </tr>
14
+ </thead>
15
+ <tbody>
16
+ <% @processes.each do |process| %>
17
+ <tr>
18
+ <td>
19
+ <span class="font-medium text-white"><%= process.id %></span>
20
+ </td>
21
+ <td>
22
+ <span class="font-medium text-white"><%= process.name %></span>
23
+ </td>
24
+ <td>
25
+ <span class="text-gray-300"><%= process.hostname %></span>
26
+ </td>
27
+ <td>
28
+ <span class="badge badge-viewer"><%= process.kind %></span>
29
+ </td>
30
+ <td class="text-gray-300">
31
+ <%= process.last_heartbeat_at %>
32
+ </td>
33
+ </tr>
34
+ <% end %>
35
+ </tbody>
36
+ </table>
37
+ </div>
@@ -0,0 +1,44 @@
1
+ <div class="table-container">
2
+ <div class="table-header">
3
+ <h2>Queues</h2>
4
+ </div>
5
+ <table>
6
+ <thead>
7
+ <tr>
8
+ <th>Queue Name</th>
9
+ <th>Jobs Count</th>
10
+ <th>Status</th>
11
+ <th class="text-right">Actions</th>
12
+ </tr>
13
+ </thead>
14
+ <tbody>
15
+ <% @queues.each do |queue_name, count| %>
16
+ <tr>
17
+ <td>
18
+ <span class="font-medium text-white"><%= queue_name %></span>
19
+ </td>
20
+ <td>
21
+ <span class="text-gray-300"><%= count %></span>
22
+ </td>
23
+ <td>
24
+ <span class="status-dot status-active"></span> Active
25
+ </td>
26
+ <td class="text-right">
27
+ <div class="queue-action-buttons">
28
+ <%= button_to "Pause",
29
+ pause_queue_path(queue_name),
30
+ method: :put,
31
+ class: "btn-icon",
32
+ style: "color: var(--warning-color);" %>
33
+ <%= button_to "Delete all",
34
+ destroy_jobs_queue_path(queue_name),
35
+ method: :delete,
36
+ data: { confirm: "Are you sure you want to delete all jobs in this queue?" },
37
+ class: "btn-icon btn-danger" %>
38
+ </div>
39
+ </td>
40
+ </tr>
41
+ <% end %>
42
+ </tbody>
43
+ </table>
44
+ </div>
@@ -0,0 +1,35 @@
1
+ <div class="table-container">
2
+ <div class="table-header">
3
+ <h2>Recurring tasks</h2>
4
+ </div>
5
+ <table>
6
+ <thead>
7
+ <tr>
8
+ <th>Key</th>
9
+ <th>Schedule</th>
10
+ <th>Class / Command</th>
11
+ <th>Queue</th>
12
+ <th>Description</th>
13
+ </tr>
14
+ </thead>
15
+ <tbody>
16
+ <% if @recurring_tasks.any? %>
17
+ <% @recurring_tasks.each do |task| %>
18
+ <tr>
19
+ <td><span class="font-medium"><%= task.key %></span></td>
20
+ <td><code class="code-inline"><%= task.schedule %></code></td>
21
+ <td>
22
+ <%= task.class_name.presence || task.command %>
23
+ </td>
24
+ <td><%= task.queue_name.presence || "—" %></td>
25
+ <td class="text-secondary"><%= task.description.presence || "—" %></td>
26
+ </tr>
27
+ <% end %>
28
+ <% else %>
29
+ <tr>
30
+ <td colspan="5" class="text-secondary">No recurring tasks configured.</td>
31
+ </tr>
32
+ <% end %>
33
+ </tbody>
34
+ </table>
35
+ </div>
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Pagy default configuration for SolidBro
4
+ # Supports Pagy 9.x, 8.x, and 43.x
5
+ # See https://ddnexus.github.io/pagy/resources/initializer/
6
+
7
+ if defined?(Pagy)
8
+ if Pagy.respond_to?(:options)
9
+ # Pagy 8+/43.x uses Pagy.options with 'limit'
10
+ Pagy.options[:limit] = 25
11
+ elsif defined?(Pagy::DEFAULT)
12
+ # Pagy 9.x uses Pagy::DEFAULT with 'items'
13
+ Pagy::DEFAULT[:items] = 25
14
+ end
15
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,21 @@
1
+ SolidBro::Engine.routes.draw do
2
+ root to: "jobs#index"
3
+
4
+ resources :jobs, only: [:index, :show], path: "jobs" do
5
+ member do
6
+ put :retry
7
+ delete :discard
8
+ end
9
+ end
10
+
11
+ resources :queues, only: [:index] do
12
+ member do
13
+ put :pause
14
+ put :resume
15
+ delete :destroy_jobs
16
+ end
17
+ end
18
+
19
+ resources :processes, only: [:index], path: "workers"
20
+ resources :recurring_tasks, only: [:index], path: "recurring-tasks"
21
+ end