solid_web_ui 0.1.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 (57) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +74 -0
  3. data/app/assets/stylesheets/solid_web_ui.css +232 -0
  4. data/app/components/solid_web_ui/ui/page_component.html.erb +15 -0
  5. data/app/components/solid_web_ui/ui/page_component.rb +22 -0
  6. data/app/components/solid_web_ui/ui/paginator_component.html.erb +21 -0
  7. data/app/components/solid_web_ui/ui/paginator_component.rb +23 -0
  8. data/app/components/solid_web_ui/ui/stat_card_component.html.erb +11 -0
  9. data/app/components/solid_web_ui/ui/stat_card_component.rb +24 -0
  10. data/app/components/solid_web_ui/ui/status_badge_component.rb +37 -0
  11. data/app/components/solid_web_ui/ui/table_component.html.erb +18 -0
  12. data/app/components/solid_web_ui/ui/table_component.rb +19 -0
  13. data/app/controllers/solid_web_ui/cable/application_controller.rb +14 -0
  14. data/app/controllers/solid_web_ui/cable/channels_controller.rb +13 -0
  15. data/app/controllers/solid_web_ui/cable/dashboard_controller.rb +13 -0
  16. data/app/controllers/solid_web_ui/cable/messages_controller.rb +18 -0
  17. data/app/controllers/solid_web_ui/cache/application_controller.rb +14 -0
  18. data/app/controllers/solid_web_ui/cache/dashboard_controller.rb +13 -0
  19. data/app/controllers/solid_web_ui/cache/entries_controller.rb +24 -0
  20. data/app/controllers/solid_web_ui/queue/application_controller.rb +17 -0
  21. data/app/controllers/solid_web_ui/queue/dashboard_controller.rb +18 -0
  22. data/app/controllers/solid_web_ui/queue/failed_executions_controller.rb +34 -0
  23. data/app/controllers/solid_web_ui/queue/jobs_controller.rb +24 -0
  24. data/app/controllers/solid_web_ui/queue/processes_controller.rb +9 -0
  25. data/app/controllers/solid_web_ui/queue/queues_controller.rb +27 -0
  26. data/app/controllers/solid_web_ui/queue/recurring_tasks_controller.rb +9 -0
  27. data/app/helpers/solid_web_ui/cable/application_helper.rb +22 -0
  28. data/app/helpers/solid_web_ui/cache/application_helper.rb +23 -0
  29. data/app/helpers/solid_web_ui/component_helper.rb +28 -0
  30. data/app/helpers/solid_web_ui/queue/application_helper.rb +38 -0
  31. data/app/views/layouts/solid_web_ui.html.erb +33 -0
  32. data/app/views/solid_web_ui/cable/channels/index.html.erb +12 -0
  33. data/app/views/solid_web_ui/cable/dashboard/index.html.erb +24 -0
  34. data/app/views/solid_web_ui/cache/dashboard/index.html.erb +15 -0
  35. data/app/views/solid_web_ui/cache/entries/index.html.erb +15 -0
  36. data/app/views/solid_web_ui/queue/dashboard/index.html.erb +13 -0
  37. data/app/views/solid_web_ui/queue/failed_executions/index.html.erb +26 -0
  38. data/app/views/solid_web_ui/queue/jobs/index.html.erb +23 -0
  39. data/app/views/solid_web_ui/queue/processes/index.html.erb +14 -0
  40. data/app/views/solid_web_ui/queue/queues/index.html.erb +25 -0
  41. data/app/views/solid_web_ui/queue/recurring_tasks/index.html.erb +17 -0
  42. data/lib/solid_web_ui/cable/engine.rb +13 -0
  43. data/lib/solid_web_ui/cable/routes.rb +7 -0
  44. data/lib/solid_web_ui/cable.rb +18 -0
  45. data/lib/solid_web_ui/cache/engine.rb +13 -0
  46. data/lib/solid_web_ui/cache/routes.rb +7 -0
  47. data/lib/solid_web_ui/cache.rb +17 -0
  48. data/lib/solid_web_ui/configurable.rb +20 -0
  49. data/lib/solid_web_ui/engine.rb +13 -0
  50. data/lib/solid_web_ui/paginator.rb +49 -0
  51. data/lib/solid_web_ui/queue/engine.rb +15 -0
  52. data/lib/solid_web_ui/queue/routes.rb +18 -0
  53. data/lib/solid_web_ui/queue.rb +19 -0
  54. data/lib/solid_web_ui/theme.rb +71 -0
  55. data/lib/solid_web_ui/version.rb +5 -0
  56. data/lib/solid_web_ui.rb +33 -0
  57. metadata +193 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 733e4f6cdd63c6b22b5e3ca4166576ebfe2db2a482d6b2fa7e25035e0a12c7a9
4
+ data.tar.gz: e1b1e0755169b3ba6f74d576f155bcce21bf029f5d1a2de3e833be7713d806e5
5
+ SHA512:
6
+ metadata.gz: 66d72bca337446f1df7ae48b7faebe2c760c56e1079eba0a280deb1047bd6ef0a0719bcd64137609a0df7207bc7632239eff6920aa30f16fb28775f7d4b97fa9
7
+ data.tar.gz: ae241b3d25a1108b038b88a92e81e36da9137c5c3af126a1abd4c6767ca135e46b9f3200641239cc162a74c690bb73eaa3ad6145450a6b61231e60717afb5348
data/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # solid_web_ui
2
+
3
+ Web dashboards for Rails' **Solid Queue**, **Solid Cache** and **Solid Cable** — one gem
4
+ (`solid_web_ui`) with three independently mountable Rails engines sharing one design system.
5
+
6
+ > The repository is named `solid-web`; the gem it ships is `solid_web_ui`.
7
+
8
+ | Engine | Mount | What it does |
9
+ |--------|-------|--------------|
10
+ | `SolidWebUi::Queue::Engine` | `/admin/queue` | Solid Queue: dashboard, queues (pause/resume), jobs by status, failed jobs (retry/discard), processes, recurring tasks. |
11
+ | `SolidWebUi::Cache::Engine` | `/admin/cache` | Solid Cache: entry/size statistics, entry browser, clear. |
12
+ | `SolidWebUi::Cable::Engine` | `/admin/cable` | Solid Cable: message/channel activity, volume, retention trim. |
13
+
14
+ The shared core (`SolidWebUi`) provides the layout, ViewComponents, design-token theming and a
15
+ dry-configurable base. The engines are plain Rails mountable engines — **no ActiveAdmin required**;
16
+ host authentication is inherited through a configurable `base_controller_class`.
17
+
18
+ ## Install
19
+
20
+ ```ruby
21
+ # Gemfile
22
+ gem "solid_web_ui"
23
+ ```
24
+
25
+ ```ruby
26
+ # config/routes.rb — mount only the parts you want
27
+ mount SolidWebUi::Queue::Engine => "/admin/queue"
28
+ mount SolidWebUi::Cache::Engine => "/admin/cache"
29
+ mount SolidWebUi::Cable::Engine => "/admin/cable"
30
+ ```
31
+
32
+ ```ruby
33
+ # config/initializers/solid_web_ui.rb — protect the dashboards behind your auth
34
+ SolidWebUi::Queue.config.base_controller_class = "Admin::BaseController"
35
+ SolidWebUi::Cache.config.base_controller_class = "Admin::BaseController"
36
+ SolidWebUi::Cable.config.base_controller_class = "Admin::BaseController"
37
+ ```
38
+
39
+ ## Development
40
+
41
+ A single dummy Rails app under `spec/dummy` (SQLite, schema loaded from the Solid* gems) exercises
42
+ all three engines.
43
+
44
+ ```bash
45
+ bundle install
46
+ bundle exec rspec # full suite
47
+ bundle exec rake assets:build # rebuild the precompiled Tailwind stylesheet
48
+ ```
49
+
50
+ > Requires the Ruby pinned for this workspace. In a non-interactive shell use mise:
51
+ > `mise exec -- bundle exec rspec`.
52
+
53
+ ## Releasing
54
+
55
+ Pushing a version tag builds and releases the gem via
56
+ [`.github/workflows/release.yml`](.github/workflows/release.yml):
57
+
58
+ ```bash
59
+ git tag v0.1.0
60
+ git push origin v0.1.0
61
+ ```
62
+
63
+ The workflow builds `solid_web_ui.gem`, attaches it to a GitHub Release, and publishes it to RubyGems
64
+ via **OIDC Trusted Publishing** (no API key / secret — MFA-compatible).
65
+
66
+ One-time setup on [rubygems.org](https://rubygems.org): register a trusted publisher for `solid_web_ui`
67
+ (repo `doromones/solid-web`, workflow `release.yml`). Before the first release use a *pending* trusted
68
+ publisher (`https://rubygems.org/profile/oidc/pending_trusted_publishers/new`).
69
+
70
+ ## Documentation
71
+
72
+ - [Configuration reference](docs/configuration.md)
73
+ - [Theming — projecting the host design](docs/theming.md)
74
+ - [Authentication](docs/authentication.md)
@@ -0,0 +1,232 @@
1
+ /*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.com */
2
+ @layer theme, components, utilities;
3
+ @layer utilities {
4
+ .static {
5
+ position: static;
6
+ }
7
+ .block {
8
+ display: block;
9
+ }
10
+ .contents {
11
+ display: contents;
12
+ }
13
+ }
14
+ @layer components {
15
+ .solid-web-ui {
16
+ color: var(--swui-color-text);
17
+ background: var(--swui-color-bg);
18
+ font-family: var(--swui-font);
19
+ line-height: 1.5;
20
+ -webkit-font-smoothing: antialiased;
21
+ }
22
+ .solid-web-ui *, .solid-web-ui *::before, .solid-web-ui *::after {
23
+ box-sizing: border-box;
24
+ }
25
+ .solid-web-ui .swui-page {
26
+ max-width: 72rem;
27
+ margin-inline: auto;
28
+ padding: 1.5rem;
29
+ }
30
+ .solid-web-ui .swui-page__header {
31
+ display: flex;
32
+ flex-wrap: wrap;
33
+ align-items: center;
34
+ justify-content: space-between;
35
+ gap: 1rem;
36
+ margin-bottom: 1.5rem;
37
+ border-bottom: 1px solid var(--swui-color-border);
38
+ padding-bottom: 1rem;
39
+ }
40
+ .solid-web-ui .swui-page__title {
41
+ font-size: 1.5rem;
42
+ font-weight: 700;
43
+ margin: 0;
44
+ }
45
+ .solid-web-ui .swui-nav {
46
+ display: flex;
47
+ flex-wrap: wrap;
48
+ gap: 0.25rem;
49
+ }
50
+ .solid-web-ui .swui-nav__link {
51
+ display: inline-block;
52
+ padding: 0.4rem 0.75rem;
53
+ border-radius: var(--swui-radius);
54
+ color: var(--swui-color-muted);
55
+ text-decoration: none;
56
+ font-size: 0.9rem;
57
+ font-weight: 500;
58
+ }
59
+ .solid-web-ui .swui-nav__link:hover {
60
+ background: var(--swui-color-primary);
61
+ @supports (color: color-mix(in lab, red, red)) {
62
+ background: color-mix(in srgb, var(--swui-color-primary) 10%, transparent);
63
+ }
64
+ color: var(--swui-color-text);
65
+ }
66
+ .solid-web-ui .swui-nav__link--active {
67
+ background: var(--swui-color-primary);
68
+ color: var(--swui-color-primary-contrast);
69
+ }
70
+ .solid-web-ui .swui-grid {
71
+ display: grid;
72
+ grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
73
+ gap: 1rem;
74
+ margin-bottom: 1.5rem;
75
+ }
76
+ .solid-web-ui .swui-card {
77
+ display: flex;
78
+ flex-direction: column;
79
+ gap: 0.35rem;
80
+ padding: 1rem 1.1rem;
81
+ background: var(--swui-color-surface);
82
+ border: 1px solid var(--swui-color-border);
83
+ border-left: 3px solid var(--swui-color-border);
84
+ border-radius: var(--swui-radius);
85
+ text-decoration: none;
86
+ color: inherit;
87
+ }
88
+ .solid-web-ui a.swui-card:hover {
89
+ border-color: var(--swui-color-primary);
90
+ }
91
+ .solid-web-ui .swui-card__label {
92
+ font-size: 0.8rem;
93
+ color: var(--swui-color-muted);
94
+ text-transform: uppercase;
95
+ letter-spacing: 0.03em;
96
+ }
97
+ .solid-web-ui .swui-card__value {
98
+ font-size: 1.75rem;
99
+ font-weight: 700;
100
+ }
101
+ .solid-web-ui .swui-card--primary {
102
+ border-left-color: var(--swui-color-primary);
103
+ }
104
+ .solid-web-ui .swui-card--success {
105
+ border-left-color: var(--swui-color-success);
106
+ }
107
+ .solid-web-ui .swui-card--warning {
108
+ border-left-color: var(--swui-color-warning);
109
+ }
110
+ .solid-web-ui .swui-card--danger {
111
+ border-left-color: var(--swui-color-danger);
112
+ }
113
+ .solid-web-ui .swui-badge {
114
+ display: inline-block;
115
+ padding: 0.15rem 0.5rem;
116
+ border-radius: 999px;
117
+ font-size: 0.75rem;
118
+ font-weight: 600;
119
+ background: var(--swui-color-muted);
120
+ @supports (color: color-mix(in lab, red, red)) {
121
+ background: color-mix(in srgb, var(--swui-color-muted) 18%, transparent);
122
+ }
123
+ color: var(--swui-color-text);
124
+ }
125
+ .solid-web-ui .swui-badge--info {
126
+ background: var(--swui-color-primary);
127
+ @supports (color: color-mix(in lab, red, red)) {
128
+ background: color-mix(in srgb, var(--swui-color-primary) 18%, transparent);
129
+ }
130
+ }
131
+ .solid-web-ui .swui-badge--success {
132
+ background: var(--swui-color-success);
133
+ @supports (color: color-mix(in lab, red, red)) {
134
+ background: color-mix(in srgb, var(--swui-color-success) 22%, transparent);
135
+ }
136
+ }
137
+ .solid-web-ui .swui-badge--warning {
138
+ background: var(--swui-color-warning);
139
+ @supports (color: color-mix(in lab, red, red)) {
140
+ background: color-mix(in srgb, var(--swui-color-warning) 24%, transparent);
141
+ }
142
+ }
143
+ .solid-web-ui .swui-badge--danger {
144
+ background: var(--swui-color-danger);
145
+ @supports (color: color-mix(in lab, red, red)) {
146
+ background: color-mix(in srgb, var(--swui-color-danger) 22%, transparent);
147
+ }
148
+ }
149
+ .solid-web-ui .swui-table {
150
+ width: 100%;
151
+ border-collapse: collapse;
152
+ background: var(--swui-color-surface);
153
+ border: 1px solid var(--swui-color-border);
154
+ border-radius: var(--swui-radius);
155
+ overflow: hidden;
156
+ font-size: 0.9rem;
157
+ }
158
+ .solid-web-ui .swui-table th, .solid-web-ui .swui-table td {
159
+ text-align: left;
160
+ padding: 0.6rem 0.85rem;
161
+ border-bottom: 1px solid var(--swui-color-border);
162
+ }
163
+ .solid-web-ui .swui-table th {
164
+ font-size: 0.75rem;
165
+ text-transform: uppercase;
166
+ letter-spacing: 0.03em;
167
+ color: var(--swui-color-muted);
168
+ background: var(--swui-color-muted);
169
+ @supports (color: color-mix(in lab, red, red)) {
170
+ background: color-mix(in srgb, var(--swui-color-muted) 6%, transparent);
171
+ }
172
+ }
173
+ .solid-web-ui .swui-table tr:last-child td {
174
+ border-bottom: 0;
175
+ }
176
+ .solid-web-ui .swui-table__empty td {
177
+ text-align: center;
178
+ color: var(--swui-color-muted);
179
+ padding: 1.5rem;
180
+ }
181
+ .solid-web-ui .swui-btn {
182
+ display: inline-block;
183
+ padding: 0.35rem 0.7rem;
184
+ border: 1px solid var(--swui-color-border);
185
+ border-radius: var(--swui-radius);
186
+ background: var(--swui-color-surface);
187
+ color: var(--swui-color-text);
188
+ font-size: 0.85rem;
189
+ font-weight: 500;
190
+ cursor: pointer;
191
+ text-decoration: none;
192
+ }
193
+ .solid-web-ui .swui-btn:hover {
194
+ border-color: var(--swui-color-primary);
195
+ }
196
+ .solid-web-ui .swui-btn--danger {
197
+ color: var(--swui-color-danger);
198
+ border-color: var(--swui-color-danger);
199
+ }
200
+ .solid-web-ui .swui-pagination {
201
+ display: flex;
202
+ flex-wrap: wrap;
203
+ gap: 0.25rem;
204
+ margin-top: 1rem;
205
+ }
206
+ .solid-web-ui .swui-pagination__item {
207
+ display: inline-block;
208
+ min-width: 2rem;
209
+ text-align: center;
210
+ padding: 0.3rem 0.55rem;
211
+ border: 1px solid var(--swui-color-border);
212
+ border-radius: var(--swui-radius);
213
+ color: var(--swui-color-text);
214
+ text-decoration: none;
215
+ font-size: 0.85rem;
216
+ }
217
+ .solid-web-ui .swui-pagination__item--current {
218
+ background: var(--swui-color-primary);
219
+ color: var(--swui-color-primary-contrast);
220
+ border-color: var(--swui-color-primary);
221
+ }
222
+ .solid-web-ui .swui-pagination__item--disabled {
223
+ color: var(--swui-color-muted);
224
+ opacity: 0.5;
225
+ pointer-events: none;
226
+ }
227
+ .solid-web-ui .swui-section-title {
228
+ font-size: 1.05rem;
229
+ font-weight: 600;
230
+ margin: 1.5rem 0 0.75rem;
231
+ }
232
+ }
@@ -0,0 +1,15 @@
1
+ <div class="swui-page">
2
+ <header class="swui-page__header">
3
+ <h1 class="swui-page__title"><%= title %></h1>
4
+ <% if nav.any? %>
5
+ <nav class="swui-nav">
6
+ <% nav.each do |item| %>
7
+ <%= link_to item[:label], item[:href], class: nav_link_class(item) %>
8
+ <% end %>
9
+ </nav>
10
+ <% end %>
11
+ </header>
12
+ <main class="swui-page__body">
13
+ <%= content %>
14
+ </main>
15
+ </div>
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidWebUi
4
+ module Ui
5
+ # Page chrome shared by every dashboard screen: a title, an optional nav bar
6
+ # (array of { label:, href:, active: }) and the page body as content.
7
+ class PageComponent < ViewComponent::Base
8
+ def initialize(title:, nav: [])
9
+ @title = title
10
+ @nav = nav || []
11
+ end
12
+
13
+ private
14
+
15
+ attr_reader :title, :nav
16
+
17
+ def nav_link_class(item)
18
+ [ "swui-nav__link", item[:active] ? "swui-nav__link--active" : nil ].compact.join(" ")
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ <nav class="swui-pagination" aria-label="Pagination">
2
+ <% if paginator.prev_page %>
3
+ <%= link_to "‹ Prev", page_url.call(paginator.prev_page), class: "swui-pagination__item" %>
4
+ <% else %>
5
+ <span class="swui-pagination__item swui-pagination__item--disabled">‹ Prev</span>
6
+ <% end %>
7
+
8
+ <% (1..paginator.total_pages).each do |number| %>
9
+ <% if number == paginator.current_page %>
10
+ <span class="swui-pagination__item swui-pagination__item--current" aria-current="page"><%= number %></span>
11
+ <% else %>
12
+ <%= link_to number, page_url.call(number), class: "swui-pagination__item" %>
13
+ <% end %>
14
+ <% end %>
15
+
16
+ <% if paginator.next_page %>
17
+ <%= link_to "Next ›", page_url.call(paginator.next_page), class: "swui-pagination__item" %>
18
+ <% else %>
19
+ <span class="swui-pagination__item swui-pagination__item--disabled">Next ›</span>
20
+ <% end %>
21
+ </nav>
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidWebUi
4
+ module Ui
5
+ # Renders pagination controls for a SolidWebUi::Paginator. `page_url` is a
6
+ # callable mapping a page number to a URL (so the component stays decoupled
7
+ # from any engine's routes). Hidden entirely when there is only one page.
8
+ class PaginatorComponent < ViewComponent::Base
9
+ def initialize(paginator:, page_url:)
10
+ @paginator = paginator
11
+ @page_url = page_url
12
+ end
13
+
14
+ def render?
15
+ @paginator.total_pages > 1
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :paginator, :page_url
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,11 @@
1
+ <% if href %>
2
+ <%= link_to href, class: css_classes do %>
3
+ <span class="swui-card__label"><%= label %></span>
4
+ <span class="swui-card__value"><%= value %></span>
5
+ <% end %>
6
+ <% else %>
7
+ <div class="<%= css_classes %>">
8
+ <span class="swui-card__label"><%= label %></span>
9
+ <span class="swui-card__value"><%= value %></span>
10
+ </div>
11
+ <% end %>
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidWebUi
4
+ module Ui
5
+ # A single dashboard metric: a label and a (usually numeric) value, optionally
6
+ # toned (neutral/primary/success/warning/danger) and linkable.
7
+ class StatCardComponent < ViewComponent::Base
8
+ def initialize(label:, value:, tone: :neutral, href: nil)
9
+ @label = label
10
+ @value = value
11
+ @tone = tone
12
+ @href = href
13
+ end
14
+
15
+ private
16
+
17
+ attr_reader :label, :value, :href
18
+
19
+ def css_classes
20
+ [ "swui-card", "swui-card--#{@tone}" ].join(" ")
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidWebUi
4
+ module Ui
5
+ # A small pill conveying a record's status. Maps a domain status symbol to one
6
+ # of the shared tone classes; unknown statuses degrade to a neutral pill.
7
+ class StatusBadgeComponent < ViewComponent::Base
8
+ TONES = {
9
+ ready: :info,
10
+ scheduled: :info,
11
+ in_progress: :info,
12
+ claimed: :info,
13
+ finished: :success,
14
+ succeeded: :success,
15
+ failed: :danger,
16
+ blocked: :warning,
17
+ paused: :warning,
18
+ stale: :warning
19
+ }.freeze
20
+
21
+ def initialize(label:, status: nil)
22
+ @label = label
23
+ @status = status
24
+ end
25
+
26
+ def call
27
+ content_tag(:span, @label, class: "swui-badge swui-badge--#{tone}")
28
+ end
29
+
30
+ private
31
+
32
+ def tone
33
+ TONES.fetch(@status&.to_sym, :neutral)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,18 @@
1
+ <table class="swui-table">
2
+ <thead>
3
+ <tr>
4
+ <% headers.each do |header| %>
5
+ <th scope="col"><%= header %></th>
6
+ <% end %>
7
+ </tr>
8
+ </thead>
9
+ <tbody>
10
+ <% if content.present? %>
11
+ <%= content %>
12
+ <% else %>
13
+ <tr class="swui-table__empty">
14
+ <td colspan="<%= headers.size %>"><%= empty_message %></td>
15
+ </tr>
16
+ <% end %>
17
+ </tbody>
18
+ </table>
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidWebUi
4
+ module Ui
5
+ # A styled data table. Pass column `headers`; render the body rows as the
6
+ # component's content (a block of <tr> rows). Shows an empty state when no
7
+ # rows are given.
8
+ class TableComponent < ViewComponent::Base
9
+ def initialize(headers:, empty_message: "Nothing to show.")
10
+ @headers = headers
11
+ @empty_message = empty_message
12
+ end
13
+
14
+ private
15
+
16
+ attr_reader :headers, :empty_message
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidWebUi::Cable
4
+ class ApplicationController < SolidWebUi.resolve_base_controller(SolidWebUi::Cable.config.base_controller_class)
5
+ layout "solid_web_ui"
6
+ helper SolidWebUi::ComponentHelper
7
+
8
+ private
9
+
10
+ def trimmable_scope
11
+ SolidCable::Message.where(created_at: ...SolidWebUi::Cable.config.retention.ago)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidWebUi::Cable
4
+ class ChannelsController < ApplicationController
5
+ def index
6
+ counts = SolidCable::Message.group(:channel).count
7
+ last_seen = SolidCable::Message.group(:channel).maximum(:created_at)
8
+ @channels = counts
9
+ .map { |channel, count| { name: channel, count: count, last: last_seen[channel] } }
10
+ .sort_by { |row| -row[:count] }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidWebUi::Cable
4
+ class DashboardController < ApplicationController
5
+ def index
6
+ @total = SolidCable::Message.count
7
+ @channel_count = SolidCable::Message.distinct.count(:channel)
8
+ @trimmable = trimmable_scope.count
9
+ @last_hour = SolidCable::Message.where(created_at: 1.hour.ago..).count
10
+ @top_channels = SolidCable::Message.group(:channel).count.max_by(10) { |_channel, count| count }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidWebUi::Cable
4
+ class MessagesController < ApplicationController
5
+ before_action :ensure_trim_enabled, only: :trim
6
+
7
+ def trim
8
+ deleted = trimmable_scope.delete_all
9
+ redirect_to root_path, notice: "Trimmed #{deleted} old message(s)."
10
+ end
11
+
12
+ private
13
+
14
+ def ensure_trim_enabled
15
+ head :forbidden unless SolidWebUi::Cable.config.enable_trim
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidWebUi::Cache
4
+ class ApplicationController < SolidWebUi.resolve_base_controller(SolidWebUi::Cache.config.base_controller_class)
5
+ layout "solid_web_ui"
6
+ helper SolidWebUi::ComponentHelper
7
+
8
+ private
9
+
10
+ def per_page
11
+ SolidWebUi::Cache.config.per_page
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidWebUi::Cache
4
+ class DashboardController < ApplicationController
5
+ def index
6
+ @count = SolidCache::Entry.count
7
+ @total_bytes = SolidCache::Entry.sum(:byte_size)
8
+ @avg_bytes = SolidCache::Entry.average(:byte_size).to_f.round
9
+ @oldest = SolidCache::Entry.minimum(:created_at)
10
+ @newest = SolidCache::Entry.maximum(:created_at)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidWebUi::Cache
4
+ class EntriesController < ApplicationController
5
+ before_action :ensure_clear_enabled, only: :clear
6
+
7
+ def index
8
+ scope = SolidCache::Entry.order(id: :desc)
9
+ @paginator = SolidWebUi::Paginator.new(scope, page: params[:page], per_page: per_page)
10
+ @entries = @paginator.records
11
+ end
12
+
13
+ def clear
14
+ SolidCache::Entry.delete_all
15
+ redirect_to root_path, notice: "Cache cleared."
16
+ end
17
+
18
+ private
19
+
20
+ def ensure_clear_enabled
21
+ head :forbidden unless SolidWebUi::Cache.config.enable_clear
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidWebUi::Queue
4
+ # Inherits from the host-configured controller (default ActionController::Base)
5
+ # so host authentication/authorization applies. Resolved lazily at autoload
6
+ # time, after host initializers have set `base_controller_class`.
7
+ class ApplicationController < SolidWebUi.resolve_base_controller(SolidWebUi::Queue.config.base_controller_class)
8
+ layout "solid_web_ui"
9
+ helper SolidWebUi::ComponentHelper
10
+
11
+ private
12
+
13
+ def per_page
14
+ SolidWebUi::Queue.config.per_page
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidWebUi::Queue
4
+ class DashboardController < ApplicationController
5
+ def index
6
+ @counts = {
7
+ ready: SolidQueue::ReadyExecution.count,
8
+ scheduled: SolidQueue::ScheduledExecution.count,
9
+ in_progress: SolidQueue::ClaimedExecution.count,
10
+ blocked: SolidQueue::BlockedExecution.count,
11
+ failed: SolidQueue::FailedExecution.count,
12
+ finished: SolidQueue::Job.where.not(finished_at: nil).count
13
+ }
14
+ @queue_count = SolidQueue::Queue.all.size
15
+ @process_count = SolidQueue::Process.count
16
+ end
17
+ end
18
+ end