solid_stack_web 1.5.0 → 1.6.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +157 -3
  3. data/app/assets/stylesheets/solid_stack_web/_02_layout.css +12 -0
  4. data/app/assets/stylesheets/solid_stack_web/_07_dashboard.css +4 -3
  5. data/app/controllers/solid_stack_web/application_controller.rb +10 -0
  6. data/app/controllers/solid_stack_web/audit_controller.rb +1 -1
  7. data/app/controllers/solid_stack_web/cable/channel_purges_controller.rb +1 -1
  8. data/app/controllers/solid_stack_web/cable/purges_controller.rb +1 -1
  9. data/app/controllers/solid_stack_web/cache/flushes_controller.rb +1 -1
  10. data/app/controllers/solid_stack_web/cache_entries_controller.rb +1 -1
  11. data/app/controllers/solid_stack_web/failed_jobs/arguments_controller.rb +3 -3
  12. data/app/controllers/solid_stack_web/failed_jobs/selections_controller.rb +4 -4
  13. data/app/controllers/solid_stack_web/failed_jobs_controller.rb +2 -2
  14. data/app/controllers/solid_stack_web/jobs/selections_controller.rb +2 -2
  15. data/app/controllers/solid_stack_web/jobs_controller.rb +2 -2
  16. data/app/controllers/solid_stack_web/recurring_tasks/runs_controller.rb +4 -4
  17. data/app/controllers/solid_stack_web/scheduled_jobs_controller.rb +5 -5
  18. data/app/helpers/solid_stack_web/application_helper.rb +30 -17
  19. data/app/views/layouts/solid_stack_web/application.html.erb +28 -24
  20. data/app/views/solid_stack_web/audit/index.html.erb +18 -18
  21. data/app/views/solid_stack_web/cable/index.html.erb +22 -19
  22. data/app/views/solid_stack_web/cable_messages/index.html.erb +15 -14
  23. data/app/views/solid_stack_web/cache/index.html.erb +19 -19
  24. data/app/views/solid_stack_web/cache_entries/index.html.erb +16 -15
  25. data/app/views/solid_stack_web/cache_entries/show.html.erb +11 -11
  26. data/app/views/solid_stack_web/dashboard/index.html.erb +54 -33
  27. data/app/views/solid_stack_web/errors/internal_server_error.html.erb +4 -4
  28. data/app/views/solid_stack_web/errors/not_found.html.erb +4 -4
  29. data/app/views/solid_stack_web/failed_jobs/destroy.turbo_stream.erb +2 -2
  30. data/app/views/solid_stack_web/failed_jobs/errors/index.html.erb +10 -10
  31. data/app/views/solid_stack_web/failed_jobs/index.html.erb +22 -22
  32. data/app/views/solid_stack_web/failed_jobs/show.html.erb +16 -16
  33. data/app/views/solid_stack_web/history/index.html.erb +19 -18
  34. data/app/views/solid_stack_web/jobs/_empty.html.erb +8 -8
  35. data/app/views/solid_stack_web/jobs/index.html.erb +35 -34
  36. data/app/views/solid_stack_web/jobs/show.html.erb +16 -16
  37. data/app/views/solid_stack_web/processes/index.html.erb +8 -8
  38. data/app/views/solid_stack_web/queues/index.html.erb +13 -13
  39. data/app/views/solid_stack_web/queues/show.html.erb +16 -16
  40. data/app/views/solid_stack_web/recurring_tasks/index.html.erb +16 -16
  41. data/app/views/solid_stack_web/shared/_locale_switcher.html.erb +14 -0
  42. data/app/views/solid_stack_web/stats/index.html.erb +13 -13
  43. data/config/locales/en.yml +395 -0
  44. data/config/locales/es.yml +395 -0
  45. data/lib/solid_stack_web/engine.rb +1 -0
  46. data/lib/solid_stack_web/version.rb +1 -1
  47. data/lib/solid_stack_web.rb +14 -1
  48. metadata +4 -1
@@ -1,104 +1,104 @@
1
1
  <%= turbo_frame_tag "sqw-dashboard", target: "_top",
2
2
  data: { controller: "refresh", refresh_interval_value: SolidStackWeb.dashboard_refresh_interval } do %>
3
3
  <div class="sqw-page-header">
4
- <h1 class="sqw-page-title">Overview</h1>
4
+ <h1 class="sqw-page-title"><%= t("solid_stack_web.dashboard.title") %></h1>
5
5
  </div>
6
6
 
7
7
  <div class="sqw-gem-grid">
8
8
  <div class="sqw-gem-card sqw-gem-card--queue">
9
9
  <div class="sqw-gem-card__header">
10
- <span class="sqw-gem-card__title">Solid Queue</span>
11
- <%= link_to "View Jobs →", jobs_path, class: "sqw-gem-card__link" %>
10
+ <span class="sqw-gem-card__title"><%= t("solid_stack_web.dashboard.solid_queue") %></span>
11
+ <%= link_to t("solid_stack_web.dashboard.view_jobs"), jobs_path, class: "sqw-gem-card__link" %>
12
12
  </div>
13
13
  <div class="sqw-gem-card__body">
14
14
  <a href="<%= jobs_path(status: "ready") %>" class="sqw-inline-stat sqw-inline-stat--ready">
15
- <span class="sqw-inline-stat__label">Ready</span>
15
+ <span class="sqw-inline-stat__label"><%= t("solid_stack_web.dashboard.ready") %></span>
16
16
  <span class="sqw-inline-stat__value"><%= @queue_stats[:ready] %></span>
17
17
  </a>
18
18
  <a href="<%= jobs_path(status: "scheduled") %>" class="sqw-inline-stat sqw-inline-stat--scheduled">
19
- <span class="sqw-inline-stat__label">Scheduled</span>
19
+ <span class="sqw-inline-stat__label"><%= t("solid_stack_web.dashboard.scheduled") %></span>
20
20
  <span class="sqw-inline-stat__value"><%= @queue_stats[:scheduled] %></span>
21
21
  </a>
22
22
  <a href="<%= jobs_path(status: "claimed") %>" class="sqw-inline-stat sqw-inline-stat--claimed">
23
- <span class="sqw-inline-stat__label">Running</span>
23
+ <span class="sqw-inline-stat__label"><%= t("solid_stack_web.dashboard.running") %></span>
24
24
  <span class="sqw-inline-stat__value"><%= @queue_stats[:claimed] %></span>
25
25
  </a>
26
26
  <a href="<%= jobs_path(status: "blocked") %>" class="sqw-inline-stat sqw-inline-stat--blocked">
27
- <span class="sqw-inline-stat__label">Blocked</span>
27
+ <span class="sqw-inline-stat__label"><%= t("solid_stack_web.dashboard.blocked") %></span>
28
28
  <span class="sqw-inline-stat__value"><%= @queue_stats[:blocked] %></span>
29
29
  </a>
30
30
  <a href="<%= failed_jobs_path %>" class="sqw-inline-stat sqw-inline-stat--failed">
31
- <span class="sqw-inline-stat__label">Failed</span>
31
+ <span class="sqw-inline-stat__label"><%= t("solid_stack_web.dashboard.failed") %></span>
32
32
  <span class="sqw-inline-stat__value"><%= @queue_stats[:failed] %></span>
33
33
  </a>
34
34
  <a href="<%= history_path(period: "1h") %>" class="sqw-inline-stat sqw-inline-stat--neutral">
35
- <span class="sqw-inline-stat__label">Done (1h)</span>
35
+ <span class="sqw-inline-stat__label"><%= t("solid_stack_web.dashboard.done_1h") %></span>
36
36
  <span class="sqw-inline-stat__value"><%= @queue_stats[:done_1h] %></span>
37
37
  </a>
38
38
  <a href="<%= history_path(period: "24h") %>" class="sqw-inline-stat sqw-inline-stat--neutral">
39
- <span class="sqw-inline-stat__label">Done (24h)</span>
39
+ <span class="sqw-inline-stat__label"><%= t("solid_stack_web.dashboard.done_24h") %></span>
40
40
  <span class="sqw-inline-stat__value"><%= @queue_stats[:done_24h] %></span>
41
41
  </a>
42
42
  <% if @queue_stats.key?(:slow_jobs) %>
43
43
  <a href="<%= stats_path %>" class="sqw-inline-stat sqw-inline-stat--<%= @queue_stats[:slow_jobs] > 0 ? "failed" : "neutral" %>">
44
- <span class="sqw-inline-stat__label">Slow (24h)</span>
44
+ <span class="sqw-inline-stat__label"><%= t("solid_stack_web.dashboard.slow_24h") %></span>
45
45
  <span class="sqw-inline-stat__value"><%= @queue_stats[:slow_jobs] %></span>
46
46
  </a>
47
47
  <% end %>
48
48
  <a href="<%= processes_path %>" class="sqw-inline-stat sqw-inline-stat--neutral">
49
- <span class="sqw-inline-stat__label">Healthy</span>
49
+ <span class="sqw-inline-stat__label"><%= t("solid_stack_web.dashboard.healthy") %></span>
50
50
  <span class="sqw-inline-stat__value"><%= @queue_stats[:processes_healthy] %></span>
51
51
  </a>
52
52
  <% if @queue_stats[:processes_stale] > 0 %>
53
53
  <a href="<%= processes_path %>" class="sqw-inline-stat sqw-inline-stat--failed">
54
- <span class="sqw-inline-stat__label">Stale</span>
54
+ <span class="sqw-inline-stat__label"><%= t("solid_stack_web.dashboard.stale") %></span>
55
55
  <span class="sqw-inline-stat__value"><%= @queue_stats[:processes_stale] %></span>
56
56
  </a>
57
57
  <% end %>
58
58
  </div>
59
59
  <div class="sqw-sparkline-wrap" data-controller="sparkline-tooltip">
60
- <span class="sqw-sparkline-label">Throughput last 12 hours</span>
60
+ <span class="sqw-sparkline-label"><%= t("solid_stack_web.dashboard.throughput_label") %></span>
61
61
  <div class="sqw-sparkline-positioner">
62
62
  <%= throughput_sparkline_svg(@throughput) %>
63
63
  <div class="sqw-sparkline-tip" data-sparkline-tooltip-target="tip" hidden></div>
64
64
  </div>
65
65
  <div class="sqw-sparkline-axis">
66
- <span>12h ago</span>
67
- <span>6h ago</span>
68
- <span>now</span>
66
+ <span><%= t("solid_stack_web.dashboard.axis.twelve_h_ago") %></span>
67
+ <span><%= t("solid_stack_web.dashboard.axis.six_h_ago") %></span>
68
+ <span><%= t("solid_stack_web.dashboard.axis.now") %></span>
69
69
  </div>
70
70
  </div>
71
71
  <div class="sqw-sparkline-wrap sqw-sparkline-wrap--failures" data-controller="sparkline-tooltip">
72
- <span class="sqw-sparkline-label">Failures last 12 hours</span>
72
+ <span class="sqw-sparkline-label"><%= t("solid_stack_web.dashboard.failures_label") %></span>
73
73
  <div class="sqw-sparkline-positioner">
74
74
  <%= failed_job_sparkline_svg(@failures) %>
75
75
  <div class="sqw-sparkline-tip" data-sparkline-tooltip-target="tip" hidden></div>
76
76
  </div>
77
77
  <div class="sqw-sparkline-axis">
78
- <span>12h ago</span>
79
- <span>6h ago</span>
80
- <span>now</span>
78
+ <span><%= t("solid_stack_web.dashboard.axis.twelve_h_ago") %></span>
79
+ <span><%= t("solid_stack_web.dashboard.axis.six_h_ago") %></span>
80
+ <span><%= t("solid_stack_web.dashboard.axis.now") %></span>
81
81
  </div>
82
82
  </div>
83
83
  </div>
84
84
 
85
85
  <div class="sqw-gem-card sqw-gem-card--cache">
86
86
  <div class="sqw-gem-card__header">
87
- <span class="sqw-gem-card__title">Solid Cache</span>
88
- <%= link_to "View Cache →", cache_path, class: "sqw-gem-card__link" %>
87
+ <span class="sqw-gem-card__title"><%= t("solid_stack_web.dashboard.solid_cache") %></span>
88
+ <%= link_to t("solid_stack_web.dashboard.view_cache"), cache_path, class: "sqw-gem-card__link" %>
89
89
  </div>
90
90
  <div class="sqw-gem-card__body">
91
91
  <%= link_to cache_entries_path, class: "sqw-inline-stat sqw-inline-stat--cache" do %>
92
- <span class="sqw-inline-stat__label">Entries</span>
92
+ <span class="sqw-inline-stat__label"><%= t("solid_stack_web.dashboard.entries") %></span>
93
93
  <span class="sqw-inline-stat__value"><%= @cache_stats[:entries] %></span>
94
94
  <% end %>
95
95
  <div class="sqw-inline-stat sqw-inline-stat--cache">
96
- <span class="sqw-inline-stat__label">Size</span>
96
+ <span class="sqw-inline-stat__label"><%= t("solid_stack_web.dashboard.size") %></span>
97
97
  <span class="sqw-inline-stat__value"><%= number_to_human_size(@cache_stats[:byte_size]) %></span>
98
98
  </div>
99
99
  <% if @cache_stats[:oldest_entry] %>
100
100
  <div class="sqw-inline-stat sqw-inline-stat--cache">
101
- <span class="sqw-inline-stat__label">Oldest</span>
101
+ <span class="sqw-inline-stat__label"><%= t("solid_stack_web.dashboard.oldest") %></span>
102
102
  <span class="sqw-inline-stat__value sqw-inline-stat__value--sm"><%= local_time(@cache_stats[:oldest_entry], format: :relative) %></span>
103
103
  </div>
104
104
  <% end %>
@@ -107,32 +107,32 @@
107
107
 
108
108
  <div class="sqw-gem-card sqw-gem-card--cable">
109
109
  <div class="sqw-gem-card__header">
110
- <span class="sqw-gem-card__title">Solid Cable</span>
111
- <%= link_to "View Cable →", cable_path, class: "sqw-gem-card__link" %>
110
+ <span class="sqw-gem-card__title"><%= t("solid_stack_web.dashboard.solid_cable") %></span>
111
+ <%= link_to t("solid_stack_web.dashboard.view_cable"), cable_path, class: "sqw-gem-card__link" %>
112
112
  </div>
113
113
  <div class="sqw-gem-card__body">
114
114
  <%= link_to cable_path, class: "sqw-inline-stat sqw-inline-stat--cable" do %>
115
- <span class="sqw-inline-stat__label">Messages</span>
115
+ <span class="sqw-inline-stat__label"><%= t("solid_stack_web.dashboard.messages") %></span>
116
116
  <span class="sqw-inline-stat__value"><%= @cable_stats[:messages] %></span>
117
117
  <% end %>
118
118
  <div class="sqw-inline-stat sqw-inline-stat--cable">
119
- <span class="sqw-inline-stat__label">Channels</span>
119
+ <span class="sqw-inline-stat__label"><%= t("solid_stack_web.dashboard.channels") %></span>
120
120
  <span class="sqw-inline-stat__value"><%= @cable_stats[:channels] %></span>
121
121
  </div>
122
122
  <div class="sqw-inline-stat sqw-inline-stat--cable">
123
- <span class="sqw-inline-stat__label">Msg/hr</span>
123
+ <span class="sqw-inline-stat__label"><%= t("solid_stack_web.dashboard.msg_per_hr") %></span>
124
124
  <span class="sqw-inline-stat__value"><%= @cable_stats[:messages_per_hour] %></span>
125
125
  </div>
126
126
  <% if @cable_stats[:oldest_message] %>
127
127
  <div class="sqw-inline-stat sqw-inline-stat--cable">
128
- <span class="sqw-inline-stat__label">Oldest</span>
128
+ <span class="sqw-inline-stat__label"><%= t("solid_stack_web.dashboard.oldest") %></span>
129
129
  <span class="sqw-inline-stat__value sqw-inline-stat__value--sm"><%= local_time(@cable_stats[:oldest_message], format: :relative) %></span>
130
130
  </div>
131
131
  <% end %>
132
132
  </div>
133
133
  <% if @cable_stats[:top_channels].any? %>
134
134
  <div class="sqw-card-list">
135
- <span class="sqw-card-list__label">Top channels</span>
135
+ <span class="sqw-card-list__label"><%= t("solid_stack_web.dashboard.top_channels") %></span>
136
136
  <% @cable_stats[:top_channels].each do |channel, count| %>
137
137
  <div class="sqw-card-list__row">
138
138
  <span class="sqw-card-list__name" title="<%= channel %>"><%= channel %></span>
@@ -142,5 +142,26 @@
142
142
  </div>
143
143
  <% end %>
144
144
  </div>
145
+
146
+ <% SolidStackWeb.dashboard_cards.each do |card| %>
147
+ <div class="sqw-gem-card sqw-gem-card--custom">
148
+ <div class="sqw-gem-card__header">
149
+ <span class="sqw-gem-card__title"><%= card[:title] %></span>
150
+ <% if card[:link] %>
151
+ <%= link_to card[:link][:label], card[:link][:url], class: "sqw-gem-card__link" %>
152
+ <% end %>
153
+ </div>
154
+ <% if card[:stats] %>
155
+ <div class="sqw-gem-card__body">
156
+ <% card[:stats].call.each do |label, value| %>
157
+ <div class="sqw-inline-stat sqw-inline-stat--neutral">
158
+ <span class="sqw-inline-stat__label"><%= label %></span>
159
+ <span class="sqw-inline-stat__value"><%= value %></span>
160
+ </div>
161
+ <% end %>
162
+ </div>
163
+ <% end %>
164
+ </div>
165
+ <% end %>
145
166
  </div>
146
167
  <% end %>
@@ -1,8 +1,8 @@
1
1
  <div class="sqw-page-header">
2
- <h1 class="sqw-page-title">Something Went Wrong</h1>
2
+ <h1 class="sqw-page-title"><%= t("solid_stack_web.errors.server_error_title") %></h1>
3
3
  </div>
4
4
 
5
5
  <div class="sqw-empty">
6
- <p class="sqw-empty__title">500 &mdash; An unexpected error occurred.</p>
7
- <p class="sqw-empty__hint"><%= link_to "Back to Dashboard", root_path, class: "sqw-btn sqw-btn--secondary" %></p>
8
- </div>
6
+ <p class="sqw-empty__title"><%= t("solid_stack_web.errors.server_error_message") %></p>
7
+ <p class="sqw-empty__hint"><%= link_to t("solid_stack_web.shared.back_to_dashboard"), root_path, class: "sqw-btn sqw-btn--secondary" %></p>
8
+ </div>
@@ -1,8 +1,8 @@
1
1
  <div class="sqw-page-header">
2
- <h1 class="sqw-page-title">Not Found</h1>
2
+ <h1 class="sqw-page-title"><%= t("solid_stack_web.errors.not_found_title") %></h1>
3
3
  </div>
4
4
 
5
5
  <div class="sqw-empty">
6
- <p class="sqw-empty__title">404 &mdash; The record you&rsquo;re looking for doesn&rsquo;t exist or has been removed.</p>
7
- <p class="sqw-empty__hint"><%= link_to "Back to Dashboard", root_path, class: "sqw-btn sqw-btn--secondary" %></p>
8
- </div>
6
+ <p class="sqw-empty__title"><%= t("solid_stack_web.errors.not_found_message") %></p>
7
+ <p class="sqw-empty__hint"><%= link_to t("solid_stack_web.shared.back_to_dashboard"), root_path, class: "sqw-btn sqw-btn--secondary" %></p>
8
+ </div>
@@ -6,7 +6,7 @@
6
6
  <% else %>
7
7
  <%= turbo_stream.replace "sqw-jobs-table" do %>
8
8
  <div class="sqw-empty">
9
- <p>No failed jobs.</p>
9
+ <p><%= t("solid_stack_web.failed_jobs.empty_title") %></p>
10
10
  </div>
11
11
  <% end %>
12
- <% end %>
12
+ <% end %>
@@ -1,7 +1,7 @@
1
1
  <div class="sqw-page-header sqw-page-header--split">
2
- <h1 class="sqw-page-title">Error Summary</h1>
2
+ <h1 class="sqw-page-title"><%= t("solid_stack_web.failed_jobs.errors_title") %></h1>
3
3
  <div class="sqw-header-actions">
4
- <%= link_to "← Failed Jobs", failed_jobs_path, class: "sqw-btn sqw-btn--muted sqw-btn--sm" %>
4
+ <%= link_to t("solid_stack_web.failed_jobs.back_to_failed_jobs"), failed_jobs_path, class: "sqw-btn sqw-btn--muted sqw-btn--sm" %>
5
5
  </div>
6
6
  </div>
7
7
 
@@ -9,10 +9,10 @@
9
9
  <table class="sqw-table">
10
10
  <thead>
11
11
  <tr>
12
- <th scope="col">Error Class</th>
13
- <th scope="col">Message</th>
14
- <th scope="col">Count</th>
15
- <th scope="col"><span class="sqw-sr-only">Actions</span></th>
12
+ <th scope="col"><%= t("solid_stack_web.failed_jobs.errors_col_class") %></th>
13
+ <th scope="col"><%= t("solid_stack_web.failed_jobs.errors_col_message") %></th>
14
+ <th scope="col"><%= t("solid_stack_web.failed_jobs.errors_col_count") %></th>
15
+ <th scope="col"><span class="sqw-sr-only"><%= t("solid_stack_web.shared.actions") %></span></th>
16
16
  </tr>
17
17
  </thead>
18
18
  <tbody>
@@ -33,7 +33,7 @@
33
33
  </td>
34
34
  <td><%= group.count %></td>
35
35
  <td class="sqw-actions">
36
- <%= link_to "View Jobs", failed_jobs_path(error_class: group.exception_class),
36
+ <%= link_to t("solid_stack_web.failed_jobs.view_jobs"), failed_jobs_path(error_class: group.exception_class),
37
37
  class: "sqw-btn sqw-btn--muted sqw-btn--sm" %>
38
38
  </td>
39
39
  </tr>
@@ -42,7 +42,7 @@
42
42
  </table>
43
43
  <% else %>
44
44
  <div class="sqw-empty">
45
- <p class="sqw-empty__title">No failed jobs</p>
46
- <p class="sqw-empty__hint">All clear — your jobs are running without errors.</p>
45
+ <p class="sqw-empty__title"><%= t("solid_stack_web.failed_jobs.empty_title") %></p>
46
+ <p class="sqw-empty__hint"><%= t("solid_stack_web.failed_jobs.empty_hint") %></p>
47
47
  </div>
48
- <% end %>
48
+ <% end %>
@@ -1,16 +1,16 @@
1
1
  <div class="sqw-page-header sqw-page-header--split">
2
2
  <div>
3
- <h1 class="sqw-page-title">Failed Jobs</h1>
3
+ <h1 class="sqw-page-title"><%= t("solid_stack_web.failed_jobs.title") %></h1>
4
4
  <% if @error_class %>
5
5
  <div class="sqw-breadcrumb">
6
- Filtered by <span class="sqw-monospace"><%= @error_class %></span>
7
- — <%= link_to "Clear filter", failed_jobs_path %>
6
+ <%= t("solid_stack_web.failed_jobs.filtered_by") %> <span class="sqw-monospace"><%= @error_class %></span>
7
+ — <%= link_to t("solid_stack_web.failed_jobs.clear_filter"), failed_jobs_path %>
8
8
  </div>
9
9
  <% end %>
10
10
  </div>
11
11
  <div class="sqw-header-actions">
12
- <%= link_to "Error Summary", failed_job_errors_path, class: "sqw-btn sqw-btn--muted sqw-btn--sm" %>
13
- <%= link_to "Export CSV", failed_jobs_path(format: :csv),
12
+ <%= link_to t("solid_stack_web.failed_jobs.error_summary"), failed_job_errors_path, class: "sqw-btn sqw-btn--muted sqw-btn--sm" %>
13
+ <%= link_to t("solid_stack_web.failed_jobs.export_csv"), failed_jobs_path(format: :csv),
14
14
  class: "sqw-btn sqw-btn--muted sqw-btn--sm", data: { turbo: false } %>
15
15
  </div>
16
16
  </div>
@@ -22,31 +22,31 @@
22
22
  <% end %>
23
23
 
24
24
  <%= form_with url: failed_job_selection_path, method: :delete, id: "discard-selection-form",
25
- data: { turbo_confirm: "Discard selected failed jobs? This cannot be undone." } do |f| %>
25
+ data: { turbo_confirm: t("solid_stack_web.failed_jobs.confirm_discard_selected") } do |f| %>
26
26
  <% end %>
27
27
 
28
28
  <div class="sqw-selection-bar" data-selection-target="bar" style="display: none;">
29
- <span class="sqw-muted"><span data-selection-target="count">0</span> selected</span>
29
+ <span class="sqw-muted"><span data-selection-target="count">0</span> <%= t("solid_stack_web.jobs.selected_label") %></span>
30
30
  <button type="button" class="sqw-btn sqw-btn--sm"
31
31
  data-action="click->selection#submit"
32
- data-selection-form-id-param="retry-selection-form">Retry Selected</button>
32
+ data-selection-form-id-param="retry-selection-form"><%= t("solid_stack_web.failed_jobs.retry_selected") %></button>
33
33
  <button type="button" class="sqw-btn sqw-btn--danger sqw-btn--sm"
34
34
  data-action="click->selection#submit"
35
- data-selection-form-id-param="discard-selection-form">Discard Selected</button>
35
+ data-selection-form-id-param="discard-selection-form"><%= t("solid_stack_web.failed_jobs.discard_selected") %></button>
36
36
  </div>
37
37
 
38
38
  <table class="sqw-table">
39
39
  <thead>
40
40
  <tr>
41
- <th scope="col"><input type="checkbox" class="sqw-checkbox" aria-label="Select all"
41
+ <th scope="col"><input type="checkbox" class="sqw-checkbox" aria-label="<%= t("solid_stack_web.shared.select_all") %>"
42
42
  data-selection-target="selectAll"
43
43
  data-action="change->selection#selectAll"></th>
44
44
  <% sort_url = ->(p) { failed_jobs_path(error_class: @error_class, **p) } %>
45
- <%= sort_header_th("Job Class", "class_name", sort_url, current_sort: @sort, current_dir: @direction) %>
46
- <%= sort_header_th("Queue", "queue_name", sort_url, current_sort: @sort, current_dir: @direction) %>
47
- <th scope="col">Error</th>
48
- <%= sort_header_th("Failed At", "created_at", sort_url, current_sort: @sort, current_dir: @direction) %>
49
- <th scope="col"><span class="sqw-sr-only">Actions</span></th>
45
+ <%= sort_header_th(t("solid_stack_web.failed_jobs.col_job_class"), "class_name", sort_url, current_sort: @sort, current_dir: @direction) %>
46
+ <%= sort_header_th(t("solid_stack_web.failed_jobs.col_queue"), "queue_name", sort_url, current_sort: @sort, current_dir: @direction) %>
47
+ <th scope="col"><%= t("solid_stack_web.failed_jobs.col_error") %></th>
48
+ <%= sort_header_th(t("solid_stack_web.failed_jobs.col_failed_at"), "created_at", sort_url, current_sort: @sort, current_dir: @direction) %>
49
+ <th scope="col"><span class="sqw-sr-only"><%= t("solid_stack_web.shared.actions") %></span></th>
50
50
  </tr>
51
51
  </thead>
52
52
  <tbody>
@@ -62,22 +62,22 @@
62
62
  <td class="sqw-muted sqw-truncate" title="<%= execution.exception_class %>: <%= execution.message %>"><%= execution.exception_class %></td>
63
63
  <td class="sqw-muted"><%= local_time(execution.created_at) %></td>
64
64
  <td class="sqw-actions">
65
- <%= button_to "Retry", retry_failed_job_path(execution),
65
+ <%= button_to t("solid_stack_web.failed_jobs.retry"), retry_failed_job_path(execution),
66
66
  method: :post, class: "sqw-btn sqw-btn--sm" %>
67
- <%= button_to "Discard", failed_job_path(execution),
67
+ <%= button_to t("solid_stack_web.failed_jobs.discard"), failed_job_path(execution),
68
68
  method: :delete, class: "sqw-btn sqw-btn--danger sqw-btn--sm",
69
- data: { turbo_confirm: "Discard this job?" } %>
69
+ data: { turbo_confirm: t("solid_stack_web.failed_jobs.confirm_discard") } %>
70
70
  </td>
71
71
  </tr>
72
72
  <% end %>
73
73
  </tbody>
74
74
  </table>
75
- <%== @pagy.series_nav(aria_label: "Pagination") if @pagy.pages > 1 %>
75
+ <%== @pagy.series_nav(aria_label: t("solid_stack_web.shared.pagination")) if @pagy.pages > 1 %>
76
76
  </div>
77
77
  <% else %>
78
78
  <div class="sqw-empty">
79
- <p class="sqw-empty__title">No failed jobs</p>
80
- <p class="sqw-empty__hint">All clear — your jobs are running without errors.</p>
79
+ <p class="sqw-empty__title"><%= t("solid_stack_web.failed_jobs.empty_title") %></p>
80
+ <p class="sqw-empty__hint"><%= t("solid_stack_web.failed_jobs.empty_hint") %></p>
81
81
  </div>
82
82
  <% end %>
83
- </div>
83
+ </div>
@@ -1,58 +1,58 @@
1
1
  <div class="sqw-page-header sqw-page-header--split">
2
2
  <div>
3
3
  <div class="sqw-breadcrumb">
4
- <%= link_to "Failed Jobs", failed_jobs_path %> &rsaquo; Detail
4
+ <%= link_to t("solid_stack_web.failed_jobs.title"), failed_jobs_path %> &rsaquo; <%= t("solid_stack_web.failed_jobs.detail") %>
5
5
  </div>
6
6
  <h1 class="sqw-page-title sqw-monospace"><%= @execution.job.class_name %></h1>
7
7
  </div>
8
8
 
9
9
  <div class="sqw-detail-actions">
10
- <%= button_to "Retry", retry_failed_job_path(@execution),
10
+ <%= button_to t("solid_stack_web.failed_jobs.retry"), retry_failed_job_path(@execution),
11
11
  method: :post, class: "sqw-btn sqw-btn--sm" %>
12
- <%= button_to "Discard", failed_job_path(@execution),
12
+ <%= button_to t("solid_stack_web.failed_jobs.discard"), failed_job_path(@execution),
13
13
  method: :delete, class: "sqw-btn sqw-btn--danger",
14
- data: { turbo_confirm: "Discard this job?" } %>
14
+ data: { turbo_confirm: t("solid_stack_web.failed_jobs.confirm_discard") } %>
15
15
  </div>
16
16
  </div>
17
17
 
18
18
  <div class="sqw-detail-grid">
19
19
  <div class="sqw-detail-card sqw-detail-section">
20
- <h2 class="sqw-section-title">Details</h2>
20
+ <h2 class="sqw-section-title"><%= t("solid_stack_web.failed_jobs.section_details") %></h2>
21
21
  <dl class="sqw-dl">
22
- <dt>Queue</dt>
22
+ <dt><%= t("solid_stack_web.failed_jobs.field_queue") %></dt>
23
23
  <dd class="sqw-monospace"><%= @execution.job.queue_name %></dd>
24
24
 
25
- <dt>Priority</dt>
25
+ <dt><%= t("solid_stack_web.failed_jobs.field_priority") %></dt>
26
26
  <dd><%= @execution.job.priority %></dd>
27
27
 
28
- <dt>Active Job ID</dt>
28
+ <dt><%= t("solid_stack_web.failed_jobs.field_active_job_id") %></dt>
29
29
  <dd class="sqw-monospace sqw-truncate" title="<%= @execution.job.active_job_id %>"><%= @execution.job.active_job_id.presence || "—" %></dd>
30
30
 
31
- <dt>Failed At</dt>
31
+ <dt><%= t("solid_stack_web.failed_jobs.field_failed_at") %></dt>
32
32
  <dd class="sqw-monospace"><%= local_time(@execution.created_at, format: :long) %></dd>
33
33
 
34
- <dt>Error</dt>
34
+ <dt><%= t("solid_stack_web.failed_jobs.field_error") %></dt>
35
35
  <dd class="sqw-monospace"><%= @execution.exception_class %></dd>
36
36
 
37
- <dt>Message</dt>
37
+ <dt><%= t("solid_stack_web.failed_jobs.field_message") %></dt>
38
38
  <dd class="sqw-muted"><%= @execution.message %></dd>
39
39
  </dl>
40
40
  </div>
41
41
 
42
42
  <div class="sqw-detail-card sqw-detail-section">
43
- <h2 class="sqw-section-title">Backtrace</h2>
43
+ <h2 class="sqw-section-title"><%= t("solid_stack_web.failed_jobs.section_backtrace") %></h2>
44
44
  <pre class="sqw-code-block"><%= Array(@execution.backtrace).first(10).join("\n").presence || "—" %></pre>
45
45
  </div>
46
46
  </div>
47
47
 
48
48
  <div class="sqw-detail-card sqw-detail-section sqw-detail-card--mt">
49
- <h2 class="sqw-section-title">Arguments</h2>
49
+ <h2 class="sqw-section-title"><%= t("solid_stack_web.failed_jobs.section_arguments") %></h2>
50
50
  <%= form_with url: failed_job_arguments_path(@execution), method: :patch do |f| %>
51
51
  <%= f.text_area :arguments, value: @arguments, rows: 12,
52
- class: "sqw-code-input", aria: { label: "Job arguments JSON" },
52
+ class: "sqw-code-input", aria: { label: t("solid_stack_web.failed_jobs.arguments_aria_label") },
53
53
  spellcheck: false %>
54
54
  <div style="margin-top: 0.75rem;">
55
- <%= f.submit "Update &amp; Retry".html_safe, class: "sqw-btn sqw-btn--sm" %>
55
+ <%= f.submit t("solid_stack_web.failed_jobs.update_retry"), class: "sqw-btn sqw-btn--sm" %>
56
56
  </div>
57
57
  <% end %>
58
- </div>
58
+ </div>
@@ -1,10 +1,10 @@
1
1
  <%= turbo_frame_tag "sqw-history-table",
2
2
  data: { controller: "refresh", refresh_interval_value: SolidStackWeb.default_refresh_interval } do %>
3
3
  <div class="sqw-page-header sqw-page-header--split">
4
- <h1 class="sqw-page-title">Job History</h1>
4
+ <h1 class="sqw-page-title"><%= t("solid_stack_web.history.title") %></h1>
5
5
  <div class="sqw-header-actions">
6
6
  <% if @jobs&.any? %>
7
- <%= link_to "Export CSV", history_path(format: :csv, queue: @queue, q: @search, period: @period),
7
+ <%= link_to t("solid_stack_web.history.export_csv"), history_path(format: :csv, queue: @queue, q: @search, period: @period),
8
8
  class: "sqw-btn sqw-btn--muted sqw-btn--sm", data: { turbo: false } %>
9
9
  <% end %>
10
10
  </div>
@@ -20,13 +20,14 @@
20
20
  <input type="hidden" name="sort" value="<%= @sort %>">
21
21
  <input type="hidden" name="direction" value="<%= @direction %>">
22
22
  <input class="sqw-search-input" type="search" name="q" value="<%= @search %>"
23
- placeholder="Filter by job class…" autocomplete="off" aria-label="Filter by job class"
23
+ placeholder="<%= t("solid_stack_web.history.placeholder_filter") %>" autocomplete="off"
24
+ aria-label="<%= t("solid_stack_web.history.aria_filter") %>"
24
25
  data-action="input->search#filter">
25
26
  <% if @search.present? %>
26
- <%= link_to "Clear", history_path(queue: @queue, period: @period), class: "sqw-btn sqw-btn--muted sqw-btn--sm" %>
27
+ <%= link_to t("solid_stack_web.shared.clear"), history_path(queue: @queue, period: @period), class: "sqw-btn sqw-btn--muted sqw-btn--sm" %>
27
28
  <% end %>
28
- <div class="sqw-period-filter" role="group" aria-label="Time period">
29
- <%= link_to "All", history_path(queue: @queue, q: @search),
29
+ <div class="sqw-period-filter" role="group" aria-label="<%= t("solid_stack_web.shared.period_filter.label") %>">
30
+ <%= link_to t("solid_stack_web.shared.period_filter.all"), history_path(queue: @queue, q: @search),
30
31
  class: "sqw-period-btn #{"sqw-period-btn--active" if @period.nil?}" %>
31
32
  <%= link_to "1h", history_path(queue: @queue, q: @search, period: "1h"),
32
33
  class: "sqw-period-btn #{"sqw-period-btn--active" if @period == "1h"}" %>
@@ -39,8 +40,8 @@
39
40
 
40
41
  <% if @queue.present? %>
41
42
  <p class="sqw-muted" style="font-size: 13px; margin-bottom: 0.75rem;">
42
- Filtering by queue: <strong><%= @queue %></strong> &mdash;
43
- <%= link_to "Clear filter", history_path(q: @search, period: @period) %>
43
+ <%= t("solid_stack_web.history.filtering_by_queue") %> <strong><%= @queue %></strong> &mdash;
44
+ <%= link_to t("solid_stack_web.history.clear_filter"), history_path(q: @search, period: @period) %>
44
45
  </p>
45
46
  <% end %>
46
47
 
@@ -50,10 +51,10 @@
50
51
  <thead>
51
52
  <% sort_url = ->(p) { history_path(queue: @queue, q: @search, period: @period, **p) } %>
52
53
  <tr>
53
- <%= sort_header_th("Job Class", "class_name", sort_url, current_sort: @sort, current_dir: @direction) %>
54
- <%= sort_header_th("Queue", "queue_name", sort_url, current_sort: @sort, current_dir: @direction) %>
55
- <th scope="col">Duration</th>
56
- <%= sort_header_th("Finished At", "finished_at", sort_url, current_sort: @sort, current_dir: @direction) %>
54
+ <%= sort_header_th(t("solid_stack_web.history.col_job_class"), "class_name", sort_url, current_sort: @sort, current_dir: @direction) %>
55
+ <%= sort_header_th(t("solid_stack_web.history.col_queue"), "queue_name", sort_url, current_sort: @sort, current_dir: @direction) %>
56
+ <th scope="col"><%= t("solid_stack_web.history.col_duration") %></th>
57
+ <%= sort_header_th(t("solid_stack_web.history.col_finished_at"), "finished_at", sort_url, current_sort: @sort, current_dir: @direction) %>
57
58
  </tr>
58
59
  </thead>
59
60
  <tbody>
@@ -71,17 +72,17 @@
71
72
  <% end %>
72
73
  </tbody>
73
74
  </table>
74
- <%== @pagy.series_nav(aria_label: "Pagination") if @pagy.pages > 1 %>
75
+ <%== @pagy.series_nav(aria_label: t("solid_stack_web.shared.pagination")) if @pagy.pages > 1 %>
75
76
  </div>
76
77
  <% else %>
77
78
  <div class="sqw-empty">
78
79
  <% if @search.present? || @queue.present? || @period.present? %>
79
- <p class="sqw-empty__title">No finished jobs match your filters</p>
80
- <p class="sqw-empty__hint"><%= link_to "Clear filters", history_path %></p>
80
+ <p class="sqw-empty__title"><%= t("solid_stack_web.history.no_match_title") %></p>
81
+ <p class="sqw-empty__hint"><%= link_to t("solid_stack_web.history.no_match_hint"), history_path %></p>
81
82
  <% else %>
82
- <p class="sqw-empty__title">No finished jobs yet</p>
83
- <p class="sqw-empty__hint">Completed jobs will appear here once workers process them.</p>
83
+ <p class="sqw-empty__title"><%= t("solid_stack_web.history.empty_title") %></p>
84
+ <p class="sqw-empty__hint"><%= t("solid_stack_web.history.empty_hint") %></p>
84
85
  <% end %>
85
86
  </div>
86
87
  <% end %>
87
- <% end %>
88
+ <% end %>
@@ -1,20 +1,20 @@
1
1
  <div id="sqw-empty" class="sqw-empty">
2
2
  <% if @search.present? %>
3
- <p class="sqw-empty__title">No <strong><%= @status %></strong> jobs matching &ldquo;<%= @search %>&rdquo;</p>
4
- <p class="sqw-empty__hint"><%= link_to "Clear search", jobs_path(status: @status) %></p>
3
+ <p class="sqw-empty__title"><%= t("solid_stack_web.jobs.no_jobs_matching", status: @status, search: @search) %></p>
4
+ <p class="sqw-empty__hint"><%= link_to t("solid_stack_web.jobs.clear_search"), jobs_path(status: @status) %></p>
5
5
  <% else %>
6
- <p class="sqw-empty__title">No <strong><%= @status %></strong> jobs</p>
6
+ <p class="sqw-empty__title"><%= t("solid_stack_web.jobs.no_jobs", status: @status) %></p>
7
7
  <p class="sqw-empty__hint">
8
8
  <% case @status %>
9
9
  <% when "ready" %>
10
- Jobs will appear here once they are enqueued and ready to be picked up.
10
+ <%= t("solid_stack_web.jobs.hint_ready") %>
11
11
  <% when "scheduled" %>
12
- No jobs are scheduled to run in the future.
12
+ <%= t("solid_stack_web.jobs.hint_scheduled") %>
13
13
  <% when "claimed" %>
14
- No jobs are currently being processed by a worker.
14
+ <%= t("solid_stack_web.jobs.hint_claimed") %>
15
15
  <% when "blocked" %>
16
- No jobs are blocked by concurrency controls.
16
+ <%= t("solid_stack_web.jobs.hint_blocked") %>
17
17
  <% end %>
18
18
  </p>
19
19
  <% end %>
20
- </div>
20
+ </div>