karafka-web 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/Gemfile.lock +13 -13
  4. data/lib/karafka/web/management/actions/enable.rb +74 -10
  5. data/lib/karafka/web/pro/ui/app.rb +13 -0
  6. data/lib/karafka/web/pro/ui/controllers/base_controller.rb +4 -0
  7. data/lib/karafka/web/pro/ui/controllers/cluster_controller.rb +66 -4
  8. data/lib/karafka/web/pro/ui/controllers/consumers/consumers_controller.rb +9 -2
  9. data/lib/karafka/web/pro/ui/controllers/consumers/controls_controller.rb +6 -0
  10. data/lib/karafka/web/pro/ui/controllers/consumers/jobs_controller.rb +8 -2
  11. data/lib/karafka/web/pro/ui/controllers/dlq_controller.rb +6 -0
  12. data/lib/karafka/web/pro/ui/controllers/explorer/explorer_controller.rb +6 -0
  13. data/lib/karafka/web/pro/ui/controllers/health_controller.rb +19 -3
  14. data/lib/karafka/web/pro/ui/controllers/jobs_controller.rb +25 -0
  15. data/lib/karafka/web/pro/ui/controllers/recurring_tasks_controller.rb +10 -1
  16. data/lib/karafka/web/pro/ui/controllers/requests/params.rb +67 -0
  17. data/lib/karafka/web/pro/ui/controllers/routing_controller.rb +23 -1
  18. data/lib/karafka/web/pro/ui/controllers/scheduled_messages/schedules_controller.rb +6 -0
  19. data/lib/karafka/web/pro/ui/controllers/topics/configs_controller.rb +6 -1
  20. data/lib/karafka/web/pro/ui/controllers/topics/distributions_controller.rb +1 -1
  21. data/lib/karafka/web/pro/ui/controllers/topics/offsets_controller.rb +1 -1
  22. data/lib/karafka/web/pro/ui/controllers/topics/replications_controller.rb +1 -1
  23. data/lib/karafka/web/pro/ui/controllers/topics/topics_controller.rb +6 -0
  24. data/lib/karafka/web/pro/ui/helpers/filtering_helper.rb +173 -0
  25. data/lib/karafka/web/pro/ui/helpers/topics_filtering_helper.rb +80 -0
  26. data/lib/karafka/web/pro/ui/lib/filtering.rb +128 -0
  27. data/lib/karafka/web/pro/ui/lib/search/matchers/base.rb +2 -2
  28. data/lib/karafka/web/pro/ui/routes/cluster.rb +0 -2
  29. data/lib/karafka/web/pro/ui/views/cluster/_brokers_table.erb +41 -0
  30. data/lib/karafka/web/pro/ui/views/cluster/_configs_table.erb +41 -0
  31. data/lib/karafka/web/pro/ui/views/cluster/_partition.erb +2 -2
  32. data/lib/karafka/web/pro/ui/views/cluster/index.erb +12 -20
  33. data/lib/karafka/web/pro/ui/views/cluster/replication.erb +39 -0
  34. data/lib/karafka/web/pro/ui/views/cluster/show.erb +13 -21
  35. data/lib/karafka/web/pro/ui/views/consumers/consumers/_consumer_performances_table.erb +56 -0
  36. data/lib/karafka/web/pro/ui/views/consumers/consumers/_consumers_table.erb +43 -0
  37. data/lib/karafka/web/pro/ui/views/consumers/consumers/consumer/_subscription_group.erb +10 -2
  38. data/lib/karafka/web/pro/ui/views/consumers/consumers/index.erb +8 -24
  39. data/lib/karafka/web/pro/ui/views/consumers/consumers/performance.erb +8 -37
  40. data/lib/karafka/web/pro/ui/views/consumers/consumers/subscriptions.erb +20 -6
  41. data/lib/karafka/web/pro/ui/views/consumers/controls/_processes_table.erb +45 -0
  42. data/lib/karafka/web/pro/ui/views/consumers/controls/index.erb +14 -27
  43. data/lib/karafka/web/pro/ui/views/consumers/jobs/_jobs_table.erb +48 -0
  44. data/lib/karafka/web/pro/ui/views/consumers/jobs/pending.erb +8 -29
  45. data/lib/karafka/web/pro/ui/views/consumers/jobs/running.erb +8 -29
  46. data/lib/karafka/web/pro/ui/views/dlq/index.erb +9 -1
  47. data/lib/karafka/web/pro/ui/views/explorer/explorer/index.erb +9 -1
  48. data/lib/karafka/web/pro/ui/views/health/_changes_table.erb +59 -0
  49. data/lib/karafka/web/pro/ui/views/health/_cluster_lags_table.erb +43 -0
  50. data/lib/karafka/web/pro/ui/views/health/_lags_table.erb +60 -0
  51. data/lib/karafka/web/pro/ui/views/health/_no_data.erb +5 -1
  52. data/lib/karafka/web/pro/ui/views/health/_offsets_table.erb +63 -0
  53. data/lib/karafka/web/pro/ui/views/health/_overview_table.erb +83 -0
  54. data/lib/karafka/web/pro/ui/views/health/_tabs.erb +6 -0
  55. data/lib/karafka/web/pro/ui/views/health/changes.erb +13 -43
  56. data/lib/karafka/web/pro/ui/views/health/cluster_lags.erb +11 -25
  57. data/lib/karafka/web/pro/ui/views/health/lags.erb +13 -44
  58. data/lib/karafka/web/pro/ui/views/health/offsets.erb +13 -47
  59. data/lib/karafka/web/pro/ui/views/health/overview.erb +13 -67
  60. data/lib/karafka/web/pro/ui/views/jobs/_jobs_table.erb +48 -0
  61. data/lib/karafka/web/pro/ui/views/jobs/pending.erb +8 -30
  62. data/lib/karafka/web/pro/ui/views/jobs/running.erb +8 -30
  63. data/lib/karafka/web/pro/ui/views/recurring_tasks/_tasks_table.erb +43 -0
  64. data/lib/karafka/web/pro/ui/views/recurring_tasks/schedule.erb +8 -24
  65. data/lib/karafka/web/pro/ui/views/routing/_consumer_group.erb +36 -32
  66. data/lib/karafka/web/pro/ui/views/routing/_details_table.erb +43 -0
  67. data/lib/karafka/web/pro/ui/views/routing/index.erb +18 -6
  68. data/lib/karafka/web/pro/ui/views/routing/show.erb +12 -40
  69. data/lib/karafka/web/pro/ui/views/scheduled_messages/schedules/index.erb +7 -1
  70. data/lib/karafka/web/pro/ui/views/shared/_filter_box.erb +61 -0
  71. data/lib/karafka/web/pro/ui/views/topics/configs/_configs_table.erb +42 -0
  72. data/lib/karafka/web/pro/ui/views/topics/configs/index.erb +13 -22
  73. data/lib/karafka/web/pro/ui/views/topics/replications/_partition.erb +2 -2
  74. data/lib/karafka/web/pro/ui/views/topics/replications/show.erb +12 -2
  75. data/lib/karafka/web/pro/ui/views/topics/topics/index.erb +9 -1
  76. data/lib/karafka/web/tracking/consumers/listeners/errors.rb +80 -14
  77. data/lib/karafka/web/ui/base.rb +4 -0
  78. data/lib/karafka/web/ui/controllers/base_controller.rb +1 -1
  79. data/lib/karafka/web/ui/controllers/cluster_controller.rb +21 -22
  80. data/lib/karafka/web/ui/controllers/consumers_controller.rb +1 -1
  81. data/lib/karafka/web/ui/controllers/jobs_controller.rb +23 -27
  82. data/lib/karafka/web/ui/controllers/requests/hookable.rb +5 -0
  83. data/lib/karafka/web/ui/controllers/routing_controller.rb +15 -1
  84. data/lib/karafka/web/ui/helpers/application_helper.rb +3 -278
  85. data/lib/karafka/web/ui/helpers/badges_helper.rb +66 -0
  86. data/lib/karafka/web/ui/helpers/formatting_helper.rb +77 -0
  87. data/lib/karafka/web/ui/helpers/partitions_helper.rb +167 -0
  88. data/lib/karafka/web/ui/helpers/sorting_helper.rb +78 -0
  89. data/lib/karafka/web/ui/lib/filter.rb +377 -0
  90. data/lib/karafka/web/ui/lib/hash_flattener.rb +37 -0
  91. data/lib/karafka/web/ui/lib/sorter.rb +3 -1
  92. data/lib/karafka/web/ui/models/jobs.rb +3 -2
  93. data/lib/karafka/web/ui/models/process.rb +12 -0
  94. data/lib/karafka/web/ui/public/javascripts/application.min.js.gz +0 -0
  95. data/lib/karafka/web/ui/public/stylesheets/application.css +9 -0
  96. data/lib/karafka/web/ui/public/stylesheets/application.min.css +4230 -1258
  97. data/lib/karafka/web/ui/public/stylesheets/application.min.css.br +0 -0
  98. data/lib/karafka/web/ui/public/stylesheets/application.min.css.gz +0 -0
  99. data/lib/karafka/web/ui/public/stylesheets/libs/highlight_dark.min.css.gz +0 -0
  100. data/lib/karafka/web/ui/public/stylesheets/libs/highlight_light.min.css.gz +0 -0
  101. data/lib/karafka/web/ui/views/cluster/_brokers_table.erb +21 -0
  102. data/lib/karafka/web/ui/views/cluster/_partition.erb +2 -2
  103. data/lib/karafka/web/ui/views/cluster/_partitions_table.erb +31 -0
  104. data/lib/karafka/web/ui/views/cluster/brokers.erb +1 -21
  105. data/lib/karafka/web/ui/views/cluster/replication.erb +1 -23
  106. data/lib/karafka/web/ui/views/consumers/_consumers_table.erb +22 -0
  107. data/lib/karafka/web/ui/views/consumers/index.erb +1 -22
  108. data/lib/karafka/web/ui/views/errors/_table.erb +28 -0
  109. data/lib/karafka/web/ui/views/errors/index.erb +1 -28
  110. data/lib/karafka/web/ui/views/jobs/_jobs_table.erb +22 -0
  111. data/lib/karafka/web/ui/views/jobs/pending.erb +1 -22
  112. data/lib/karafka/web/ui/views/jobs/running.erb +1 -22
  113. data/lib/karafka/web/ui/views/routing/_consumer_group.erb +18 -12
  114. data/lib/karafka/web/ui/views/routing/_details_table.erb +23 -0
  115. data/lib/karafka/web/ui/views/routing/show.erb +1 -29
  116. data/lib/karafka/web/ui/views/shared/_no_filter_results.erb +6 -0
  117. data/lib/karafka/web/version.rb +1 -1
  118. data/package-lock.json +98 -61
  119. metadata +35 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 36ff8195b930f55c0a0780a7009c1940ca807f24e2b202ce0c483dedda9d4f0b
4
- data.tar.gz: 7989106b1ee525e3a7b3736bb9cea7f4db0409f2ecb23787d787262305b9f876
3
+ metadata.gz: 53d7f8aa3ebaa18166dbcc4ef988d88687a22c3862d77dd3d582850afe59e5d1
4
+ data.tar.gz: bb4066f370418c911e71108643f08c9c07f1b7ea073a4736c0fdc4805670741f
5
5
  SHA512:
6
- metadata.gz: 43087044c2b58ad9a07d6fc5f216bff82d1123efb8a8a90aabf9e43d33c7322899a7bbd9eaf0cfb7b78ceed9071862bcd9cd65abaf1604ef13e957fe67a875e3
7
- data.tar.gz: b26e48d1dd301f59fac2093e7fa91ae54b036430130e7f2c2a39a9f1ad284467eb20c7cf9990df33bc233df7db68c65445e3b4a0eb4781ed43f748ae851057ac
6
+ metadata.gz: 70ed8bcf8786cef729d9888dc7054de6a53abbd0f10d1c871c32f594dd68332f13889e9a808334c83495535b668bb2700ceb2bcdc87114a3ed98bbe340c2376d
7
+ data.tar.gz: 45cf0c1e9a621e4fbd718e70e9b791b976caef4faa83bd953cb4659f78ed0c962259f463c90939b160b006f49b7688bd586019da854032b05762bd40f0fb67fa
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Karafka Web Changelog
2
2
 
3
+ ## 1.0.1 (2026-08-24)
4
+ - **[Feature]** Add a generic keyword filtering (search) box to the data-heavy Web UI listings (consumers, controls, jobs, routing, per-process subscriptions, cluster, configs, recurring tasks, the health views and the topic lists), so a specific consumer, topic or job can be found without scrolling. Flat listings also include a field selector to scope the search to a chosen attribute (Pro) (#1073).
5
+ - [Enhancement] Track producer errors from every WaterDrop producer, not only `Karafka.producer` and `Karafka::Web.producer`. Web UI now subscribes its producer error listeners via WaterDrop's class-level (global) monitor, attaching them to each producer as it is configured, so errors from any additional producer a user creates (secondary, transactional, etc.) reach the Web UI. Attaching is idempotent per monitor and skips any listener a user already subscribed themselves, so errors are never counted twice. This also fixes a pre-existing double-counting bug where the default producer's errors were recorded twice, because the old `Karafka.producer == Karafka::Web.producer` guard never matched (the Web producer is a delegator/variant that shares the default producer's monitor), so the same monitor was subscribed twice (#952).
6
+ - [Enhancement] Report the details Karafka publishes on a forceful shutdown (which listeners were still active and which jobs were still in processing, with their blocking status) in the Web UI error details, so a `ForcefulShutdownError` shows what was still blocking (#979).
7
+ - [Enhancement] Show the actual replica and in-sync (ISR) broker ids (not just their counts) in the cluster and topic replication partition views. The leader is emphasized and replicas that have fallen out of sync are highlighted, turning the replication views into an at-a-glance health signal. In Pro, each broker badge links to its broker details page (#1084).
8
+ - [Maintenance] Bump `simplecov` to `1.1.1` and stop parallel test workers from clobbering each other's `coverage/coverage.json`. simplecov `1.1.1` warns (which the test suite escalates to a hard failure) when it detects a concurrent overwrite of the JSON report, and every parallel worker shares one `coverage/` directory. Parallel workers now use the non-writing `SimpleCov::Formatter::SimpleFormatter` and only persist their raw `.resultset.json`; the human-readable report is still produced once by the single `bin/check_coverage` collation step. Non-parallel local runs keep the default HTML formatter (#1214).
9
+ - [Maintenance] Split the oversized `ApplicationHelper` into focused helper modules (`SortingHelper`, `FormattingHelper`, `BadgesHelper`, `PartitionsHelper`), leaving `ApplicationHelper` with only app/layout/hash utilities.
10
+ - [Fix] Force `border-collapse` on the data tables so their 1px cell borders no longer render as a doubled 2px border under daisyUI 5.7 (which switched `.table` to `border-collapse: separate`).
11
+ - [Fix] Make columns that render a sort link actually sortable. Some tables exposed sortable headers for columns missing from the controller's allow-list, so clicking them silently did nothing: the cluster replication view (topic name, partition, leader, replicas, in-sync) and the broker config `value` column in Pro, and the low-offset column in the health offsets view.
12
+
3
13
  ## 1.0.0 (2026-08-05)
4
14
  - **[Breaking]** Namespace the commanding pause configuration under `config.commanding.pause`. The flat `config.commanding.pause_timeout` setting has been removed in favor of the nested `config.commanding.pause.timeout` namespace, mirroring the pause configuration namespacing introduced in Karafka.
5
15
  - [Enhancement] Link the topic, partition and offset in the Explorer's single-message metadata table to the Explorer, matching the linking on the Errors detail view. The offset points at the message currently being viewed but is kept linked so the whole topic/partition/offset coordinate is navigable (#1179).
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- karafka-web (1.0.0)
4
+ karafka-web (1.0.1)
5
5
  erubi (~> 1.4)
6
6
  karafka (>= 2.6.0, < 2.7.0)
7
7
  karafka-core (>= 2.6.2, < 2.7.0)
@@ -13,10 +13,10 @@ GEM
13
13
  specs:
14
14
  byebug (13.0.0)
15
15
  reline (>= 0.6.0)
16
- concurrent-ruby (1.3.7)
16
+ concurrent-ruby (1.3.8)
17
17
  drb (2.2.3)
18
18
  erubi (1.13.1)
19
- et-orbi (1.4.0)
19
+ et-orbi (1.4.1)
20
20
  tzinfo
21
21
  ffi (1.17.4)
22
22
  ffi (1.17.4-aarch64-linux-gnu)
@@ -32,8 +32,8 @@ GEM
32
32
  fugit (1.13.0)
33
33
  et-orbi (~> 1.4)
34
34
  raabro (~> 1.4)
35
- io-console (0.8.2)
36
- json (2.20.0)
35
+ io-console (0.9.2)
36
+ json (2.21.2)
37
37
  karafka (2.6.0)
38
38
  karafka-core (>= 2.6.2, < 2.7.0)
39
39
  karafka-rdkafka (>= 0.28.0)
@@ -106,31 +106,31 @@ GEM
106
106
  racc (~> 1.4)
107
107
  ostruct (0.6.3)
108
108
  prism (1.9.0)
109
- raabro (1.4.0)
109
+ raabro (1.5.0)
110
110
  racc (1.8.1)
111
- rack (3.2.6)
111
+ rack (3.2.7)
112
112
  rack-test (2.2.0)
113
113
  rack (>= 1.3)
114
114
  rackup (0.2.3)
115
115
  rack (>= 3.0.0.beta1)
116
116
  webrick
117
117
  rake (13.4.2)
118
- reline (0.6.3)
118
+ reline (0.7.0)
119
119
  io-console (~> 0.5)
120
- roda (3.105.0)
120
+ roda (3.107.0)
121
121
  rack
122
122
  ruby2_keywords (0.0.5)
123
- simplecov (1.0.3)
124
- tilt (2.7.0)
123
+ simplecov (1.1.1)
124
+ tilt (2.9.0)
125
125
  tzinfo (2.0.6)
126
126
  concurrent-ruby (~> 1.0)
127
127
  warning (1.6.0)
128
- waterdrop (2.10.2)
128
+ waterdrop (2.10.3)
129
129
  karafka-core (>= 2.5.12, < 3.0.0)
130
130
  karafka-rdkafka (>= 0.24.0)
131
131
  zeitwerk (~> 2.3)
132
132
  webrick (1.9.2)
133
- zeitwerk (2.8.2)
133
+ zeitwerk (2.8.3)
134
134
 
135
135
  PLATFORMS
136
136
  aarch64-linux-gnu
@@ -151,16 +151,9 @@ module Karafka
151
151
  ::Karafka.monitor.subscribe(listener)
152
152
  end
153
153
 
154
- # Installs all the producer related listeners into Karafka default listener and
155
- # into Karafka::Web listener in case it would be different than the Karafka one
156
- ::Karafka::Web.config.tracking.producers.listeners.each do |listener|
157
- ::Karafka.producer.monitor.subscribe(listener)
158
-
159
- # Do not instrument twice in case only one default producer is used
160
- next if ::Karafka.producer == ::Karafka::Web.producer
161
-
162
- ::Karafka::Web.producer.monitor.subscribe(listener)
163
- end
154
+ # Installs all the producer related listeners so that we track errors from every
155
+ # producer and not only the default and the Web UI ones
156
+ subscribe_to_producers
164
157
 
165
158
  # Installs all the UI related listeners for tracking errors from web processes
166
159
  # These listen on Karafka monitor to catch instrumented UI errors
@@ -169,6 +162,77 @@ module Karafka
169
162
  end
170
163
  end
171
164
 
165
+ # Subscribes the producer tracking listeners to every WaterDrop producer.
166
+ #
167
+ # Historically we only instrumented `Karafka.producer` and `Karafka::Web.producer`, so
168
+ # errors published by any other producer a user created (a secondary producer, a
169
+ # transactional one, etc.) never reached the Web UI. WaterDrop's class-level monitor
170
+ # announces every producer as it is configured, so we hook into it and attach our
171
+ # listeners to each producer's own monitor. Producers that already exist by the time Web
172
+ # is enabled are not announced again, so we also attach to them explicitly.
173
+ def subscribe_to_producers
174
+ @producers_tracking_mutex = Mutex.new
175
+
176
+ # A forked child inherits this Enable instance (captured by the subscriptions below)
177
+ # together with the tracking mutex. Had the parent been mid-subscription while forking,
178
+ # the child would inherit a locked mutex and deadlock on its first producer
179
+ # subscription. Karafka forks single-threaded, so we swap in a fresh mutex right after
180
+ # the fork, before any producer can be announced in the child.
181
+ ::Karafka.monitor.subscribe("swarm.node.after_fork") do
182
+ @producers_tracking_mutex = Mutex.new
183
+ end
184
+
185
+ # Any producer configured from now on is announced here; attach to it as it appears
186
+ ::WaterDrop.monitor.subscribe("producer.configured") do |event|
187
+ subscribe_producer_listeners(event[:producer])
188
+ end
189
+
190
+ # Producers that already exist will not be re-announced, so attach to them directly.
191
+ # `Karafka::Web.producer` is either the default producer or a variant of it, so it
192
+ # shares the default producer's monitor; the per-listener guard below keeps us from
193
+ # subscribing the same monitor twice.
194
+ subscribe_producer_listeners(::Karafka.producer)
195
+ subscribe_producer_listeners(::Karafka::Web.producer)
196
+ end
197
+
198
+ # Subscribes the producer tracking listeners to a single producer's monitor, skipping any
199
+ # listener that is already subscribed to it. This one guard covers every double-subscribe
200
+ # case: a variant sharing its parent's monitor, the default and Web producers being the
201
+ # same monitor, a producer re-announced by the global monitor, and a user having wired
202
+ # the Web UI producer tracking to their own producer by hand. Subscribing twice would
203
+ # track each of the producer's events twice.
204
+ #
205
+ # @param producer [WaterDrop::Producer, WaterDrop::Producer::Variant] producer whose
206
+ # monitor we want to instrument
207
+ def subscribe_producer_listeners(producer)
208
+ monitor = producer.monitor
209
+
210
+ @producers_tracking_mutex.synchronize do
211
+ ::Karafka::Web.config.tracking.producers.listeners.each do |listener|
212
+ next if producer_listener_subscribed?(monitor, listener)
213
+
214
+ monitor.subscribe(listener)
215
+ end
216
+ end
217
+ end
218
+
219
+ # @param monitor [Object] producer monitor
220
+ # @param listener [Object] producer tracking listener
221
+ # @return [Boolean] true if the listener is already subscribed to any of the monitor
222
+ # events, false otherwise
223
+ def producer_listener_subscribed?(monitor, listener)
224
+ # The default monitor exposes its subscribers, but a non-default notifications backend
225
+ # may not. When we cannot inspect them, we subscribe rather than risk missing errors.
226
+ return false unless monitor.respond_to?(:listeners)
227
+
228
+ # Snapshot the per-event listener arrays (`#values`) before scanning them, so a
229
+ # concurrent subscription to the same monitor from outside this class cannot mutate
230
+ # what we iterate.
231
+ monitor.listeners.values.any? do |event_listeners|
232
+ event_listeners.include?(listener)
233
+ end
234
+ end
235
+
172
236
  # In most cases we want to close the producer if possible.
173
237
  # While we cannot do it easily in user processes and we should rely on WaterDrop
174
238
  # finalization logic, we can do it in `karafka server` on terminate
@@ -36,6 +36,11 @@ module Karafka
36
36
  module Ui
37
37
  # Main Roda Web App that servers all the metrics and stats
38
38
  class App < Web::Ui::Base
39
+ # Filtering (search) is a Pro-only feature, so its helpers are mixed in here rather than in
40
+ # the shared OSS base
41
+ include Helpers::FilteringHelper
42
+ include Helpers::TopicsFilteringHelper
43
+
39
44
  opts[:root] = Karafka::Web.gem_root.join("lib/karafka/web/pro/ui")
40
45
 
41
46
  instance_exec(&CONTEXT_DETAILS)
@@ -62,6 +67,14 @@ module Karafka
62
67
  raise(Errors::Ui::ForbiddenError)
63
68
  end
64
69
 
70
+ # Builds the Pro request params, which add the filtering (search) readers on top of the
71
+ # OSS ones. Overriding here keeps the filtering param parsing out of the shared params.
72
+ #
73
+ # @return [Controllers::Requests::Params] curated Pro params
74
+ def params
75
+ Controllers::Requests::Params.new(request.params)
76
+ end
77
+
65
78
  # Sub-routes for given pieces of the Web UI
66
79
  SUB_ROUTES = [
67
80
  # Asset handling is exactly the same in both cases
@@ -36,6 +36,10 @@ module Karafka
36
36
  module Controllers
37
37
  # Base Pro controller
38
38
  class BaseController < Web::Ui::Controllers::BaseController
39
+ # Filtering (search) is a Pro-only feature, so it is mixed in here rather than in the
40
+ # shared OSS base controller
41
+ include Lib::Filtering
42
+
39
43
  private
40
44
 
41
45
  # @return [Karafka::Web::Pro::Ui::Lib::Features] features fetcher
@@ -34,20 +34,39 @@ module Karafka
34
34
  module Ui
35
35
  module Controllers
36
36
  # Cluster details controller
37
- class ClusterController < Web::Ui::Controllers::ClusterController
37
+ class ClusterController < BaseController
38
+ # A single controller serves the nodes (index), broker config (show) and replication
39
+ # listings, so the sortable set is the union of every column those tables expose
38
40
  self.sortable_attributes = %w[
39
41
  id
40
42
  name
43
+ port
44
+ value
41
45
  default?
42
46
  read_only?
43
47
  synonym?
44
48
  sensitive?
45
- port
49
+ topic_name
50
+ partition_id
51
+ leader
52
+ replica_count
53
+ in_sync_replica_brokers
46
54
  ].freeze
47
55
 
56
+ # Each action renders different columns, so we scope the filterable fields per action to
57
+ # what it actually displays
58
+ self.filterable_attributes = {
59
+ index: %i[id name],
60
+ show: %i[name value],
61
+ replication: %i[topic_name]
62
+ }.freeze
63
+
64
+ # Cluster state should always be fresh and not from cache
65
+ before { cache.clear }
66
+
48
67
  # Lists available brokers in the cluster
49
68
  def index
50
- @brokers = refine(Models::Broker.all)
69
+ @brokers = filter(sort(Models::Broker.all))
51
70
 
52
71
  render
53
72
  end
@@ -58,10 +77,53 @@ module Karafka
58
77
  def show(broker_id)
59
78
  @broker = Models::Broker.find(broker_id)
60
79
 
61
- @configs = refine(@broker.configs)
80
+ @configs = filter(sort(@broker.configs))
81
+
82
+ render
83
+ end
84
+
85
+ # List partitions replication details (same as OSS, plus Pro filtering)
86
+ def replication
87
+ @partitions, last_page = Paginators::Arrays.call(
88
+ filter(sort(replication_partitions)),
89
+ @params.current_page
90
+ )
91
+
92
+ paginate(@params.current_page, !last_page)
62
93
 
63
94
  render
64
95
  end
96
+
97
+ private
98
+
99
+ # Expands the displayable cluster topics into one flat row per partition, which the
100
+ # replication table renders, sorts, filters and paginates. Each row is a partition
101
+ # carrying its topic (and topic name, so it can be sorted/filtered by name).
102
+ #
103
+ # @return [Array<Hash>] partition rows
104
+ def replication_partitions
105
+ displayable_topics.flat_map do |topic|
106
+ topic[:partitions].map do |partition|
107
+ partition.merge(
108
+ topic: topic,
109
+ topic_name: topic.fetch(:topic_name)
110
+ )
111
+ end
112
+ end
113
+ end
114
+
115
+ # @return [Array<Hash>] cluster topics to display in an alphabetical order, with
116
+ # internal topics excluded unless the visibility config allows them
117
+ def displayable_topics
118
+ all = Models::ClusterInfo
119
+ .fetch
120
+ .topics
121
+ .sort_by { |topic| topic[:topic_name] }
122
+
123
+ return all if ::Karafka::Web.config.ui.visibility.internal_topics
124
+
125
+ all.reject { |topic| topic[:topic_name].start_with?("__") }
126
+ end
65
127
  end
66
128
  end
67
129
  end
@@ -69,13 +69,20 @@ module Karafka
69
69
  pending_jobs_count
70
70
  ].freeze
71
71
 
72
+ # The list filters processes by their id/topics/tags, while the per-process
73
+ # subscriptions view filters (at the view level) by topic/consumer/subscription group
74
+ self.filterable_attributes = {
75
+ default: %i[id subscribed_topics tags],
76
+ subscriptions: %i[topic consumer_group subscription_group]
77
+ }.freeze
78
+
72
79
  # Consumers list
73
80
  def index
74
81
  @current_state = Models::ConsumersState.current!
75
82
  @counters = Models::Counters.new(@current_state)
76
83
 
77
84
  @processes, last_page = Paginators::Arrays.call(
78
- refine(Models::Processes.active(@current_state)),
85
+ filter(sort(Models::Processes.active(@current_state))),
79
86
  @params.current_page
80
87
  )
81
88
 
@@ -110,7 +117,7 @@ module Karafka
110
117
  # the underlying hashes for sorting
111
118
  consumer_group.subscription_groups.flat_map(&:topics).flat_map(&:partitions)
112
119
 
113
- refine(consumer_group)
120
+ sort(consumer_group)
114
121
  end
115
122
 
116
123
  render
@@ -44,6 +44,12 @@ module Karafka
44
44
  lag_hybrid
45
45
  ].freeze
46
46
 
47
+ self.filterable_attributes = %i[
48
+ id
49
+ subscribed_topics
50
+ tags
51
+ ].freeze
52
+
47
53
  # Displays list of consumer processes + options to manage them
48
54
  def index
49
55
  features.commanding!
@@ -50,6 +50,12 @@ module Karafka
50
50
  updated_at
51
51
  ].freeze
52
52
 
53
+ self.filterable_attributes = %i[
54
+ topic
55
+ consumer
56
+ type
57
+ ].freeze
58
+
53
59
  # Shows all running jobs of a consumer
54
60
  # @param process_id [String]
55
61
  def running(process_id)
@@ -57,7 +63,7 @@ module Karafka
57
63
 
58
64
  @running_jobs = @process.jobs.running
59
65
 
60
- refine(@running_jobs)
66
+ filter(sort(@running_jobs))
61
67
 
62
68
  render
63
69
  end
@@ -69,7 +75,7 @@ module Karafka
69
75
 
70
76
  @pending_jobs = @process.jobs.pending
71
77
 
72
- refine(@pending_jobs)
78
+ filter(sort(@pending_jobs))
73
79
 
74
80
  render
75
81
  end
@@ -35,6 +35,10 @@ module Karafka
35
35
  module Controllers
36
36
  # DLQ topics overview
37
37
  class DlqController < BaseController
38
+ self.filterable_attributes = %w[
39
+ topic_name
40
+ ].freeze
41
+
38
42
  # Lists DLQ topics
39
43
  def index
40
44
  topics = Karafka::App.consumer_groups.flat_map(&:topics).flat_map(&:to_a)
@@ -52,6 +56,8 @@ module Karafka
52
56
  .select { |topic| dlq?(dlq_topic_patterns, topic[:topic_name]) }
53
57
  .sort_by { |topic| topic[:topic_name] }
54
58
 
59
+ @dlq_topics = filter(@dlq_topics)
60
+
55
61
  render
56
62
  end
57
63
 
@@ -36,6 +36,10 @@ module Karafka
36
36
  module Explorer
37
37
  # Data explorer controller
38
38
  class ExplorerController < BaseController
39
+ self.filterable_attributes = %w[
40
+ topic_name
41
+ ].freeze
42
+
39
43
  # Lists all the topics we can explore
40
44
  def index
41
45
  @topics = Models::ClusterInfo
@@ -46,6 +50,8 @@ module Karafka
46
50
  @topics.reject! { |topic| topic[:topic_name].start_with?("__") }
47
51
  end
48
52
 
53
+ @topics = filter(@topics)
54
+
49
55
  render
50
56
  end
51
57
 
@@ -47,6 +47,7 @@ module Karafka
47
47
  committed_offset_fd
48
48
  stored_offset
49
49
  stored_offset_fd
50
+ lo_offset
50
51
  hi_offset
51
52
  hi_offset_fd
52
53
  ls_offset
@@ -58,16 +59,29 @@ module Karafka
58
59
  poll_state_ch
59
60
  ].freeze
60
61
 
62
+ # The health stats are a tree keyed by consumer group and then topic name, so we filter
63
+ # on those keys rather than on record attributes. Topic keys live under `:topics` in the
64
+ # overview but directly under the consumer group in the cluster lags view; the key alias
65
+ # descends leniently, so one declaration covers both shapes without reshaping the data.
66
+ self.filterable_attributes = [
67
+ Lib::Filtering.key(:topic, under: :topics),
68
+ Lib::Filtering.key(:consumer_group)
69
+ ].freeze
70
+
61
71
  # Displays the current system state
62
72
  def overview
63
73
  current_state = Models::ConsumersState.current!
64
74
  @stats = Models::Health.current(current_state)
65
75
 
66
- # Refine only on a per topic basis not to resort higher levels
76
+ # Sort only on a per topic basis not to resort higher levels
67
77
  @stats.each_value do |cg_details|
68
- cg_details.each_value { |topic_details| refine(topic_details) }
78
+ cg_details.each_value { |topic_details| sort(topic_details) }
69
79
  end
70
80
 
81
+ # Narrow the whole tree down to the consumer groups/topics matching the current filter
82
+ # (if any). It is safe to prune in place here as the stats are built fresh per request.
83
+ filter(@stats)
84
+
71
85
  render
72
86
  end
73
87
 
@@ -86,9 +100,11 @@ module Karafka
86
100
  @stats = Models::Health.cluster_lags_with_offsets
87
101
 
88
102
  @stats.each_value do |cg_details|
89
- cg_details.each_value { |topic_details| refine(topic_details) }
103
+ cg_details.each_value { |topic_details| sort(topic_details) }
90
104
  end
91
105
 
106
+ filter(@stats)
107
+
92
108
  render
93
109
  end
94
110
 
@@ -35,6 +35,10 @@ module Karafka
35
35
  module Controllers
36
36
  # Displays list of active jobs
37
37
  class JobsController < Web::Ui::Controllers::JobsController
38
+ # This Pro controller inherits the OSS jobs controller (which has no filtering), so it
39
+ # pulls in the Pro-only filtering concern directly
40
+ include Lib::Filtering
41
+
38
42
  self.sortable_attributes = %w[
39
43
  id
40
44
  topic
@@ -46,6 +50,27 @@ module Karafka
46
50
  committed_offset
47
51
  updated_at
48
52
  ].freeze
53
+
54
+ self.filterable_attributes = %i[
55
+ id
56
+ topic
57
+ consumer
58
+ type
59
+ ].freeze
60
+
61
+ private
62
+
63
+ # Adds Pro-only filtering on top of the OSS sort/paginate seam
64
+ #
65
+ # @param jobs_total [Array] aggregated jobs
66
+ def paginate_jobs(jobs_total)
67
+ @jobs, last_page = Paginators::Arrays.call(
68
+ filter(sort(jobs_total)),
69
+ @params.current_page
70
+ )
71
+
72
+ paginate(@params.current_page, !last_page)
73
+ end
49
74
  end
50
75
  end
51
76
  end
@@ -35,6 +35,10 @@ module Karafka
35
35
  module Controllers
36
36
  # Controller for viewing and managing recurring tasks
37
37
  class RecurringTasksController < Web::Ui::Controllers::ClusterController
38
+ # This Pro controller inherits the OSS cluster controller (which has no filtering), so it
39
+ # pulls in the Pro-only filtering concern directly
40
+ include Lib::Filtering
41
+
38
42
  self.sortable_attributes = %w[
39
43
  id
40
44
  enabled
@@ -43,11 +47,16 @@ module Karafka
43
47
  next_time
44
48
  ].freeze
45
49
 
50
+ self.filterable_attributes = %i[
51
+ id
52
+ cron
53
+ ].freeze
54
+
46
55
  # Displays the current schedule
47
56
  def schedule
48
57
  @schedule = Models::RecurringTasks::Schedule.current
49
58
 
50
- @tasks = refine(@schedule.tasks) if @schedule
59
+ @tasks = filter(sort(@schedule.tasks)) if @schedule
51
60
 
52
61
  render
53
62
  end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Karafka Pro - Source Available Commercial Software
4
+ # Copyright (c) 2017-present Maciej Mensfeld. All rights reserved.
5
+ #
6
+ # This software is NOT open source. It is source-available commercial software
7
+ # requiring a paid license for use. It is NOT covered by LGPL.
8
+ #
9
+ # The author retains all right, title, and interest in this software,
10
+ # including all copyrights, patents, and other intellectual property rights.
11
+ # No patent rights are granted under this license.
12
+ #
13
+ # PROHIBITED:
14
+ # - Use without a valid commercial license
15
+ # - Redistribution, modification, or derivative works without authorization
16
+ # - Reverse engineering, decompilation, or disassembly of this software
17
+ # - Use as training data for AI/ML models or inclusion in datasets
18
+ # - Scraping, crawling, or automated collection for any purpose
19
+ #
20
+ # PERMITTED:
21
+ # - Reading, referencing, and linking for personal or commercial use
22
+ # - Runtime retrieval by AI assistants, coding agents, and RAG systems
23
+ # for the purpose of providing contextual help to Karafka users
24
+ #
25
+ # Receipt, viewing, or possession of this software does not convey or
26
+ # imply any license or right beyond those expressly stated above.
27
+ #
28
+ # License: https://karafka.io/docs/Pro-License-Comm/
29
+ # Contact: contact@karafka.io
30
+
31
+ module Karafka
32
+ module Web
33
+ module Pro
34
+ module Ui
35
+ module Controllers
36
+ # Namespace for the Pro request wrappers (params, etc.).
37
+ module Requests
38
+ # Extends the OSS request params with the Pro-only filtering (search) readers, so the
39
+ # filtering query parsing never lives in the shared OSS params.
40
+ class Params < Web::Ui::Controllers::Requests::Params
41
+ # @return [String] filtering keyword or empty string when no filtering is requested.
42
+ # Supports both the plain `filter=keyword` form (keyword filtering) and the
43
+ # field-selectable `filter[field]=...&filter[value]=...` form (its value part).
44
+ def current_filter
45
+ @current_filter ||= begin
46
+ filter = @request_params["filter"]
47
+
48
+ filter.is_a?(Hash) ? filter["value"].to_s.strip : filter.to_s.strip
49
+ end
50
+ end
51
+
52
+ # @return [String] the attribute selected for field-scoped filtering, or empty string
53
+ # when filtering across all allowed attributes (keyword filtering)
54
+ def current_filter_field
55
+ @current_filter_field ||= begin
56
+ filter = @request_params["filter"]
57
+
58
+ filter.is_a?(Hash) ? filter["field"].to_s.strip : ""
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end