solid_stack_web 0.1.0 → 0.3.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +22 -2
  3. data/app/assets/stylesheets/solid_stack_web/_02_layout.css +8 -0
  4. data/app/assets/stylesheets/solid_stack_web/_04_table.css +1 -0
  5. data/app/assets/stylesheets/solid_stack_web/_07_dashboard.css +13 -0
  6. data/app/assets/stylesheets/solid_stack_web/_08_filters.css +59 -0
  7. data/app/assets/stylesheets/solid_stack_web/_09_detail.css +85 -0
  8. data/app/controllers/solid_stack_web/application_controller.rb +5 -1
  9. data/app/controllers/solid_stack_web/failed_jobs/arguments_controller.rb +17 -0
  10. data/app/controllers/solid_stack_web/failed_jobs/selections_controller.rb +28 -0
  11. data/app/controllers/solid_stack_web/failed_jobs_controller.rb +35 -4
  12. data/app/controllers/solid_stack_web/history_controller.rb +42 -0
  13. data/app/controllers/solid_stack_web/jobs/selections_controller.rb +24 -0
  14. data/app/controllers/solid_stack_web/jobs_controller.rb +60 -21
  15. data/app/controllers/solid_stack_web/queues/pauses_controller.rb +13 -0
  16. data/app/controllers/solid_stack_web/queues_controller.rb +9 -8
  17. data/app/controllers/solid_stack_web/recurring_tasks/runs_controller.rb +18 -0
  18. data/app/controllers/solid_stack_web/recurring_tasks_controller.rb +7 -0
  19. data/app/controllers/solid_stack_web/scheduled_jobs_controller.rb +52 -0
  20. data/app/helpers/solid_stack_web/application_helper.rb +8 -0
  21. data/app/javascript/solid_stack_web/application.js +6 -0
  22. data/app/javascript/solid_stack_web/selection_controller.js +42 -0
  23. data/app/models/solid_stack_web/job.rb +21 -0
  24. data/app/views/layouts/solid_stack_web/application.html.erb +5 -0
  25. data/app/views/solid_stack_web/failed_jobs/destroy.turbo_stream.erb +9 -0
  26. data/app/views/solid_stack_web/failed_jobs/index.html.erb +61 -30
  27. data/app/views/solid_stack_web/failed_jobs/show.html.erb +58 -0
  28. data/app/views/solid_stack_web/history/index.html.erb +73 -0
  29. data/app/views/solid_stack_web/jobs/destroy.turbo_stream.erb +2 -2
  30. data/app/views/solid_stack_web/jobs/index.html.erb +138 -34
  31. data/app/views/solid_stack_web/jobs/show.html.erb +57 -0
  32. data/app/views/solid_stack_web/queues/index.html.erb +4 -4
  33. data/app/views/solid_stack_web/queues/show.html.erb +67 -0
  34. data/app/views/solid_stack_web/recurring_tasks/index.html.erb +67 -0
  35. data/app/views/solid_stack_web/scheduled_jobs/update.turbo_stream.erb +9 -0
  36. data/config/importmap.rb +2 -0
  37. data/config/routes.rb +23 -7
  38. data/lib/solid_stack_web/engine.rb +15 -0
  39. data/lib/solid_stack_web/version.rb +1 -1
  40. metadata +64 -1
@@ -0,0 +1,57 @@
1
+ <div class="sqw-page-header sqw-page-header--split">
2
+ <div>
3
+ <div class="sqw-breadcrumb">
4
+ <%= link_to "Jobs", jobs_path(status: params[:status]) %> &rsaquo; Detail
5
+ </div>
6
+ <h1 class="sqw-page-title sqw-monospace"><%= @execution.job.class_name %></h1>
7
+ </div>
8
+
9
+ <% if SolidStackWeb::Job::DISCARDABLE.include?(@status) %>
10
+ <div class="sqw-detail-actions">
11
+ <%= button_to "Discard Job", job_path(@execution.id, status: @status),
12
+ method: :delete, class: "sqw-btn sqw-btn--danger",
13
+ data: { turbo_confirm: "Discard this job?" } %>
14
+ </div>
15
+ <% end %>
16
+ </div>
17
+
18
+ <div class="sqw-detail-grid">
19
+ <div class="sqw-detail-card sqw-detail-section">
20
+ <h2 class="sqw-section-title">Details</h2>
21
+ <dl class="sqw-dl">
22
+ <dt>Status</dt>
23
+ <dd><span class="sqw-badge sqw-badge--<%= @status %>"><%= SolidStackWeb::Job::TAB_LABELS[@status] %></span></dd>
24
+
25
+ <dt>Queue</dt>
26
+ <dd class="sqw-monospace"><%= @execution.job.queue_name %></dd>
27
+
28
+ <dt>Priority</dt>
29
+ <dd><%= @execution.job.priority %></dd>
30
+
31
+ <dt>Active Job ID</dt>
32
+ <dd class="sqw-monospace sqw-truncate" title="<%= @execution.job.active_job_id %>"><%= @execution.job.active_job_id.presence || "—" %></dd>
33
+
34
+ <dt>Concurrency Key</dt>
35
+ <dd class="sqw-monospace"><%= @execution.job.concurrency_key.presence || "—" %></dd>
36
+
37
+ <% if @status == "blocked" %>
38
+ <dt>Blocked Until</dt>
39
+ <dd class="sqw-monospace"><%= @execution.expires_at ? @execution.expires_at.strftime("%Y-%m-%d %H:%M:%S UTC") : "—" %></dd>
40
+ <% end %>
41
+
42
+ <dt>Enqueued At</dt>
43
+ <dd class="sqw-monospace"><%= @execution.job.created_at.strftime("%Y-%m-%d %H:%M:%S UTC") %></dd>
44
+
45
+ <dt>Scheduled At</dt>
46
+ <dd class="sqw-monospace"><%= @execution.job.scheduled_at ? @execution.job.scheduled_at.strftime("%Y-%m-%d %H:%M:%S UTC") : "—" %></dd>
47
+
48
+ <dt>Finished At</dt>
49
+ <dd class="sqw-monospace"><%= @execution.job.finished_at ? @execution.job.finished_at.strftime("%Y-%m-%d %H:%M:%S UTC") : "—" %></dd>
50
+ </dl>
51
+ </div>
52
+
53
+ <div class="sqw-detail-card sqw-detail-section">
54
+ <h2 class="sqw-section-title">Arguments</h2>
55
+ <pre class="sqw-code-block"><%= @arguments ? JSON.pretty_generate(@arguments) : (@execution.job.arguments || "—") %></pre>
56
+ </div>
57
+ </div>
@@ -15,8 +15,8 @@
15
15
  <tbody>
16
16
  <% @queues.each do |queue| %>
17
17
  <tr>
18
- <td class="sqw-monospace"><%= queue[:name] %></td>
19
- <td><%= queue[:size] %></td>
18
+ <td class="sqw-monospace"><%= link_to queue[:name], queue_path(queue[:name]) %></td>
19
+ <td><%= link_to queue[:size], queue_path(queue[:name]) %></td>
20
20
  <td>
21
21
  <% if queue[:paused] %>
22
22
  <span class="sqw-badge sqw-badge--paused">Paused</span>
@@ -26,10 +26,10 @@
26
26
  </td>
27
27
  <td class="sqw-actions">
28
28
  <% if queue[:paused] %>
29
- <%= button_to "Resume", resume_queue_path(queue[:name]),
29
+ <%= button_to "Resume", queue_pause_path(queue[:name]),
30
30
  method: :delete, class: "sqw-btn sqw-btn--sm" %>
31
31
  <% else %>
32
- <%= button_to "Pause", pause_queue_path(queue[:name]),
32
+ <%= button_to "Pause", queue_pause_path(queue[:name]),
33
33
  method: :post, class: "sqw-btn sqw-btn--sm" %>
34
34
  <% end %>
35
35
  </td>
@@ -0,0 +1,67 @@
1
+ <div class="sqw-page-header sqw-page-header--split">
2
+ <div>
3
+ <div class="sqw-breadcrumb">
4
+ <%= link_to "Queues", queues_path %> &rsaquo; <%= @queue_name %>
5
+ </div>
6
+ <div class="sqw-page-title-row">
7
+ <h1 class="sqw-page-title sqw-monospace"><%= @queue_name %></h1>
8
+ <% if @paused %>
9
+ <span class="sqw-badge sqw-badge--paused">Paused</span>
10
+ <% else %>
11
+ <span class="sqw-badge sqw-badge--ready">Running</span>
12
+ <% end %>
13
+ </div>
14
+ </div>
15
+ <div class="sqw-header-actions">
16
+ <% if @paused %>
17
+ <%= button_to "Resume", queue_pause_path(@queue_name),
18
+ method: :delete, class: "sqw-btn sqw-btn--sm" %>
19
+ <% else %>
20
+ <%= button_to "Pause", queue_pause_path(@queue_name),
21
+ method: :post, class: "sqw-btn sqw-btn--sm" %>
22
+ <% end %>
23
+ <% if @executions.any? %>
24
+ <%= button_to "Discard All Ready (#{@pagy.count})",
25
+ discard_all_jobs_path(status: "ready", queue: @queue_name),
26
+ method: :post,
27
+ class: "sqw-btn sqw-btn--danger sqw-btn--sm",
28
+ data: { turbo_confirm: "Discard all #{@pagy.count} ready jobs in #{@queue_name}? This cannot be undone.",
29
+ turbo_frame: "_top" } %>
30
+ <% end %>
31
+ </div>
32
+ </div>
33
+
34
+ <% if @executions.any? %>
35
+ <table class="sqw-table">
36
+ <thead>
37
+ <tr>
38
+ <th>Job Class</th>
39
+ <th>Priority</th>
40
+ <th>Enqueued At</th>
41
+ <th></th>
42
+ </tr>
43
+ </thead>
44
+ <tbody>
45
+ <% @executions.each do |execution| %>
46
+ <tr>
47
+ <td class="sqw-monospace">
48
+ <%= link_to execution.job.class_name, job_path(execution.id, status: "ready"),
49
+ data: { turbo_frame: "_top" } %>
50
+ </td>
51
+ <td><%= execution.job.priority %></td>
52
+ <td class="sqw-muted"><%= execution.created_at.strftime("%b %d %H:%M") %></td>
53
+ <td class="sqw-actions">
54
+ <%= button_to "Discard", job_path(execution, status: "ready", queue: @queue_name),
55
+ method: :delete, class: "sqw-btn sqw-btn--danger sqw-btn--sm",
56
+ data: { turbo_confirm: "Discard this job?" } %>
57
+ </td>
58
+ </tr>
59
+ <% end %>
60
+ </tbody>
61
+ </table>
62
+ <%== pagy_nav(@pagy) if @pagy.pages > 1 %>
63
+ <% else %>
64
+ <div class="sqw-empty">
65
+ <p>No ready jobs in <strong><%= @queue_name %></strong>.</p>
66
+ </div>
67
+ <% end %>
@@ -0,0 +1,67 @@
1
+ <div class="sqw-page-header">
2
+ <h1 class="sqw-page-title">Recurring Tasks</h1>
3
+ </div>
4
+
5
+ <% if @recurring_tasks.any? %>
6
+ <table class="sqw-table">
7
+ <thead>
8
+ <tr>
9
+ <th>Key</th>
10
+ <th>Schedule</th>
11
+ <th>Job / Command</th>
12
+ <th>Queue</th>
13
+ <th>Next Run</th>
14
+ <th>Last Run</th>
15
+ <th>Type</th>
16
+ <th></th>
17
+ </tr>
18
+ </thead>
19
+ <tbody>
20
+ <% @recurring_tasks.each do |task| %>
21
+ <tr>
22
+ <td class="sqw-monospace"><%= task.key %></td>
23
+ <td class="sqw-monospace"><%= task.schedule %></td>
24
+ <td>
25
+ <% if task.class_name.present? %>
26
+ <span class="sqw-monospace"><%= task.class_name %></span>
27
+ <% if task.arguments.present? %>
28
+ <div class="sqw-muted sqw-monospace" style="font-size: 11px;"><%= task.arguments.inspect %></div>
29
+ <% end %>
30
+ <% else %>
31
+ <span class="sqw-monospace sqw-muted"><%= task.command %></span>
32
+ <% end %>
33
+ <% if task.description.present? %>
34
+ <div class="sqw-muted" style="font-size: 12px; margin-top: 0.2rem;"><%= task.description %></div>
35
+ <% end %>
36
+ </td>
37
+ <td><span class="sqw-badge sqw-badge--queue"><%= task.queue_name.presence || "default" %></span></td>
38
+ <td class="sqw-muted sqw-monospace">
39
+ <% next_run = begin; task.next_time.strftime("%b %d %H:%M"); rescue; nil; end %>
40
+ <%= next_run || "—" %>
41
+ </td>
42
+ <td class="sqw-muted sqw-monospace">
43
+ <% last_run = task.last_enqueued_time %>
44
+ <%= last_run ? last_run.strftime("%b %d %H:%M") : "—" %>
45
+ </td>
46
+ <td>
47
+ <% if task.static? %>
48
+ <span class="sqw-badge sqw-badge--scheduled">Static</span>
49
+ <% else %>
50
+ <span class="sqw-badge sqw-badge--blocked">Dynamic</span>
51
+ <% end %>
52
+ </td>
53
+ <td class="sqw-actions">
54
+ <%= button_to "Run Now", recurring_task_run_path(task.key),
55
+ method: :post,
56
+ class: "sqw-btn sqw-btn--sm",
57
+ data: { turbo_confirm: "Run \"#{task.key}\" immediately?" } %>
58
+ </td>
59
+ </tr>
60
+ <% end %>
61
+ </tbody>
62
+ </table>
63
+ <% else %>
64
+ <div class="sqw-empty">
65
+ <p>No recurring tasks configured.</p>
66
+ </div>
67
+ <% end %>
@@ -0,0 +1,9 @@
1
+ <% if @run_now %>
2
+ <%= turbo_stream.remove "execution_#{@execution.id}" %>
3
+ <% else %>
4
+ <%= turbo_stream.replace "scheduled_at_#{@execution.id}" do %>
5
+ <td id="scheduled_at_<%= @execution.id %>" class="sqw-muted">
6
+ <%= @execution.scheduled_at.strftime("%b %d %H:%M") %>
7
+ </td>
8
+ <% end %>
9
+ <% end %>
@@ -0,0 +1,2 @@
1
+ pin "solid_stack_web", to: "solid_stack_web/application.js"
2
+ pin "solid_stack_web/selection_controller", to: "solid_stack_web/selection_controller.js"
data/config/routes.rb CHANGED
@@ -1,21 +1,37 @@
1
1
  SolidStackWeb::Engine.routes.draw do
2
2
  root to: "dashboard#index"
3
3
 
4
- resources :jobs, only: [:index, :destroy]
4
+ resource :job_selection, path: "jobs/selection", only: [:destroy], controller: "jobs/selections"
5
+ resource :failed_job_selection, path: "failed_jobs/selection", only: [:create, :destroy], controller: "failed_jobs/selections"
5
6
 
6
- resources :failed_jobs, only: [:index, :destroy] do
7
- member { post :retry }
7
+ resources :recurring_tasks, only: [:index], param: :key do
8
+ resource :run, only: [:create], controller: "recurring_tasks/runs"
8
9
  end
9
10
 
10
- resources :queues, only: [:index] do
11
- member do
12
- post :pause
13
- delete :resume
11
+ resources :scheduled_jobs, only: [:update] do
12
+ collection do
13
+ post :run_all_now, action: :create
14
14
  end
15
15
  end
16
16
 
17
+ resources :jobs, only: [:index, :show, :destroy] do
18
+ collection do
19
+ post :discard_all, action: :destroy
20
+ end
21
+ end
22
+
23
+ resources :failed_jobs, only: [:index, :show, :destroy] do
24
+ member { post :retry }
25
+ resource :arguments, only: [:update], controller: "failed_jobs/arguments"
26
+ end
27
+
28
+ resources :queues, only: [:index, :show] do
29
+ resource :pause, only: [:create, :destroy], controller: "queues/pauses"
30
+ end
31
+
17
32
  resources :processes, only: [:index]
18
33
 
34
+ get "history", to: "history#index", as: :history
19
35
  get "cache", to: "cache#index", as: :cache
20
36
  get "cable", to: "cable#index", as: :cable
21
37
  end
@@ -3,6 +3,8 @@ require "pagy/toolbox/paginators/method"
3
3
  require "solid_queue"
4
4
  require "solid_cache"
5
5
  require "solid_cable"
6
+ require "turbo-rails"
7
+ require "importmap-rails"
6
8
 
7
9
  module SolidStackWeb
8
10
  class Engine < ::Rails::Engine
@@ -10,6 +12,19 @@ module SolidStackWeb
10
12
 
11
13
  config.i18n.load_path += Gem.find_files("pagy/locales/en.yml")
12
14
 
15
+ initializer "solid_stack_web.assets" do |app|
16
+ if app.config.respond_to?(:assets)
17
+ app.config.assets.paths << root.join("app/javascript")
18
+ end
19
+ end
20
+
21
+ initializer "solid_stack_web.importmap", before: "importmap" do |app|
22
+ if app.config.respond_to?(:importmap)
23
+ app.config.importmap.paths << root.join("config/importmap.rb")
24
+ app.config.importmap.cache_sweepers << root.join("app/javascript")
25
+ end
26
+ end
27
+
13
28
  initializer "solid_stack_web.pagy" do |app|
14
29
  app.config.after_initialize do
15
30
  Pagy::OPTIONS[:limit] = SolidStackWeb.page_size
@@ -1,3 +1,3 @@
1
1
  module SolidStackWeb
2
- VERSION = "0.1.0"
2
+ VERSION = "0.3.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solid_stack_web
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chuck Smith
@@ -79,6 +79,48 @@ dependencies:
79
79
  - - ">="
80
80
  - !ruby/object:Gem::Version
81
81
  version: '1.0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: turbo-rails
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '2.0'
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '2.0'
96
+ - !ruby/object:Gem::Dependency
97
+ name: importmap-rails
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '1.2'
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '1.2'
110
+ - !ruby/object:Gem::Dependency
111
+ name: csv
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '3.0'
117
+ type: :runtime
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '3.0'
82
124
  description: Mount SolidStackWeb in any Rails app using the Solid Stack to get a single
83
125
  dashboard covering Solid Queue job monitoring, Solid Cache statistics, and Solid
84
126
  Cable connection observability — all without leaving your app.
@@ -98,26 +140,47 @@ files:
98
140
  - app/assets/stylesheets/solid_stack_web/_05_badges.css
99
141
  - app/assets/stylesheets/solid_stack_web/_06_buttons.css
100
142
  - app/assets/stylesheets/solid_stack_web/_07_dashboard.css
143
+ - app/assets/stylesheets/solid_stack_web/_08_filters.css
144
+ - app/assets/stylesheets/solid_stack_web/_09_detail.css
101
145
  - app/assets/stylesheets/solid_stack_web/application.css
102
146
  - app/controllers/solid_stack_web/application_controller.rb
103
147
  - app/controllers/solid_stack_web/cable_controller.rb
104
148
  - app/controllers/solid_stack_web/cache_controller.rb
105
149
  - app/controllers/solid_stack_web/dashboard_controller.rb
150
+ - app/controllers/solid_stack_web/failed_jobs/arguments_controller.rb
151
+ - app/controllers/solid_stack_web/failed_jobs/selections_controller.rb
106
152
  - app/controllers/solid_stack_web/failed_jobs_controller.rb
153
+ - app/controllers/solid_stack_web/history_controller.rb
154
+ - app/controllers/solid_stack_web/jobs/selections_controller.rb
107
155
  - app/controllers/solid_stack_web/jobs_controller.rb
108
156
  - app/controllers/solid_stack_web/processes_controller.rb
157
+ - app/controllers/solid_stack_web/queues/pauses_controller.rb
109
158
  - app/controllers/solid_stack_web/queues_controller.rb
159
+ - app/controllers/solid_stack_web/recurring_tasks/runs_controller.rb
160
+ - app/controllers/solid_stack_web/recurring_tasks_controller.rb
161
+ - app/controllers/solid_stack_web/scheduled_jobs_controller.rb
110
162
  - app/helpers/solid_stack_web/application_helper.rb
163
+ - app/javascript/solid_stack_web/application.js
164
+ - app/javascript/solid_stack_web/selection_controller.js
165
+ - app/models/solid_stack_web/job.rb
111
166
  - app/views/layouts/solid_stack_web/application.html.erb
112
167
  - app/views/solid_stack_web/cable/index.html.erb
113
168
  - app/views/solid_stack_web/cache/index.html.erb
114
169
  - app/views/solid_stack_web/dashboard/index.html.erb
170
+ - app/views/solid_stack_web/failed_jobs/destroy.turbo_stream.erb
115
171
  - app/views/solid_stack_web/failed_jobs/index.html.erb
172
+ - app/views/solid_stack_web/failed_jobs/show.html.erb
173
+ - app/views/solid_stack_web/history/index.html.erb
116
174
  - app/views/solid_stack_web/jobs/_empty.html.erb
117
175
  - app/views/solid_stack_web/jobs/destroy.turbo_stream.erb
118
176
  - app/views/solid_stack_web/jobs/index.html.erb
177
+ - app/views/solid_stack_web/jobs/show.html.erb
119
178
  - app/views/solid_stack_web/processes/index.html.erb
120
179
  - app/views/solid_stack_web/queues/index.html.erb
180
+ - app/views/solid_stack_web/queues/show.html.erb
181
+ - app/views/solid_stack_web/recurring_tasks/index.html.erb
182
+ - app/views/solid_stack_web/scheduled_jobs/update.turbo_stream.erb
183
+ - config/importmap.rb
121
184
  - config/routes.rb
122
185
  - lib/solid_stack_web.rb
123
186
  - lib/solid_stack_web/engine.rb