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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/Gemfile.lock +13 -13
- data/lib/karafka/web/management/actions/enable.rb +74 -10
- data/lib/karafka/web/pro/ui/app.rb +13 -0
- data/lib/karafka/web/pro/ui/controllers/base_controller.rb +4 -0
- data/lib/karafka/web/pro/ui/controllers/cluster_controller.rb +66 -4
- data/lib/karafka/web/pro/ui/controllers/consumers/consumers_controller.rb +9 -2
- data/lib/karafka/web/pro/ui/controllers/consumers/controls_controller.rb +6 -0
- data/lib/karafka/web/pro/ui/controllers/consumers/jobs_controller.rb +8 -2
- data/lib/karafka/web/pro/ui/controllers/dlq_controller.rb +6 -0
- data/lib/karafka/web/pro/ui/controllers/explorer/explorer_controller.rb +6 -0
- data/lib/karafka/web/pro/ui/controllers/health_controller.rb +19 -3
- data/lib/karafka/web/pro/ui/controllers/jobs_controller.rb +25 -0
- data/lib/karafka/web/pro/ui/controllers/recurring_tasks_controller.rb +10 -1
- data/lib/karafka/web/pro/ui/controllers/requests/params.rb +67 -0
- data/lib/karafka/web/pro/ui/controllers/routing_controller.rb +23 -1
- data/lib/karafka/web/pro/ui/controllers/scheduled_messages/schedules_controller.rb +6 -0
- data/lib/karafka/web/pro/ui/controllers/topics/configs_controller.rb +6 -1
- data/lib/karafka/web/pro/ui/controllers/topics/distributions_controller.rb +1 -1
- data/lib/karafka/web/pro/ui/controllers/topics/offsets_controller.rb +1 -1
- data/lib/karafka/web/pro/ui/controllers/topics/replications_controller.rb +1 -1
- data/lib/karafka/web/pro/ui/controllers/topics/topics_controller.rb +6 -0
- data/lib/karafka/web/pro/ui/helpers/filtering_helper.rb +173 -0
- data/lib/karafka/web/pro/ui/helpers/topics_filtering_helper.rb +80 -0
- data/lib/karafka/web/pro/ui/lib/filtering.rb +128 -0
- data/lib/karafka/web/pro/ui/lib/search/matchers/base.rb +2 -2
- data/lib/karafka/web/pro/ui/routes/cluster.rb +0 -2
- data/lib/karafka/web/pro/ui/views/cluster/_brokers_table.erb +41 -0
- data/lib/karafka/web/pro/ui/views/cluster/_configs_table.erb +41 -0
- data/lib/karafka/web/pro/ui/views/cluster/_partition.erb +2 -2
- data/lib/karafka/web/pro/ui/views/cluster/index.erb +12 -20
- data/lib/karafka/web/pro/ui/views/cluster/replication.erb +39 -0
- data/lib/karafka/web/pro/ui/views/cluster/show.erb +13 -21
- data/lib/karafka/web/pro/ui/views/consumers/consumers/_consumer_performances_table.erb +56 -0
- data/lib/karafka/web/pro/ui/views/consumers/consumers/_consumers_table.erb +43 -0
- data/lib/karafka/web/pro/ui/views/consumers/consumers/consumer/_subscription_group.erb +10 -2
- data/lib/karafka/web/pro/ui/views/consumers/consumers/index.erb +8 -24
- data/lib/karafka/web/pro/ui/views/consumers/consumers/performance.erb +8 -37
- data/lib/karafka/web/pro/ui/views/consumers/consumers/subscriptions.erb +20 -6
- data/lib/karafka/web/pro/ui/views/consumers/controls/_processes_table.erb +45 -0
- data/lib/karafka/web/pro/ui/views/consumers/controls/index.erb +14 -27
- data/lib/karafka/web/pro/ui/views/consumers/jobs/_jobs_table.erb +48 -0
- data/lib/karafka/web/pro/ui/views/consumers/jobs/pending.erb +8 -29
- data/lib/karafka/web/pro/ui/views/consumers/jobs/running.erb +8 -29
- data/lib/karafka/web/pro/ui/views/dlq/index.erb +9 -1
- data/lib/karafka/web/pro/ui/views/explorer/explorer/index.erb +9 -1
- data/lib/karafka/web/pro/ui/views/health/_changes_table.erb +59 -0
- data/lib/karafka/web/pro/ui/views/health/_cluster_lags_table.erb +43 -0
- data/lib/karafka/web/pro/ui/views/health/_lags_table.erb +60 -0
- data/lib/karafka/web/pro/ui/views/health/_no_data.erb +5 -1
- data/lib/karafka/web/pro/ui/views/health/_offsets_table.erb +63 -0
- data/lib/karafka/web/pro/ui/views/health/_overview_table.erb +83 -0
- data/lib/karafka/web/pro/ui/views/health/_tabs.erb +6 -0
- data/lib/karafka/web/pro/ui/views/health/changes.erb +13 -43
- data/lib/karafka/web/pro/ui/views/health/cluster_lags.erb +11 -25
- data/lib/karafka/web/pro/ui/views/health/lags.erb +13 -44
- data/lib/karafka/web/pro/ui/views/health/offsets.erb +13 -47
- data/lib/karafka/web/pro/ui/views/health/overview.erb +13 -67
- data/lib/karafka/web/pro/ui/views/jobs/_jobs_table.erb +48 -0
- data/lib/karafka/web/pro/ui/views/jobs/pending.erb +8 -30
- data/lib/karafka/web/pro/ui/views/jobs/running.erb +8 -30
- data/lib/karafka/web/pro/ui/views/recurring_tasks/_tasks_table.erb +43 -0
- data/lib/karafka/web/pro/ui/views/recurring_tasks/schedule.erb +8 -24
- data/lib/karafka/web/pro/ui/views/routing/_consumer_group.erb +36 -32
- data/lib/karafka/web/pro/ui/views/routing/_details_table.erb +43 -0
- data/lib/karafka/web/pro/ui/views/routing/index.erb +18 -6
- data/lib/karafka/web/pro/ui/views/routing/show.erb +12 -40
- data/lib/karafka/web/pro/ui/views/scheduled_messages/schedules/index.erb +7 -1
- data/lib/karafka/web/pro/ui/views/shared/_filter_box.erb +61 -0
- data/lib/karafka/web/pro/ui/views/topics/configs/_configs_table.erb +42 -0
- data/lib/karafka/web/pro/ui/views/topics/configs/index.erb +13 -22
- data/lib/karafka/web/pro/ui/views/topics/replications/_partition.erb +2 -2
- data/lib/karafka/web/pro/ui/views/topics/replications/show.erb +12 -2
- data/lib/karafka/web/pro/ui/views/topics/topics/index.erb +9 -1
- data/lib/karafka/web/tracking/consumers/listeners/errors.rb +80 -14
- data/lib/karafka/web/ui/base.rb +4 -0
- data/lib/karafka/web/ui/controllers/base_controller.rb +1 -1
- data/lib/karafka/web/ui/controllers/cluster_controller.rb +21 -22
- data/lib/karafka/web/ui/controllers/consumers_controller.rb +1 -1
- data/lib/karafka/web/ui/controllers/jobs_controller.rb +23 -27
- data/lib/karafka/web/ui/controllers/requests/hookable.rb +5 -0
- data/lib/karafka/web/ui/controllers/routing_controller.rb +15 -1
- data/lib/karafka/web/ui/helpers/application_helper.rb +3 -278
- data/lib/karafka/web/ui/helpers/badges_helper.rb +66 -0
- data/lib/karafka/web/ui/helpers/formatting_helper.rb +77 -0
- data/lib/karafka/web/ui/helpers/partitions_helper.rb +167 -0
- data/lib/karafka/web/ui/helpers/sorting_helper.rb +78 -0
- data/lib/karafka/web/ui/lib/filter.rb +377 -0
- data/lib/karafka/web/ui/lib/hash_flattener.rb +37 -0
- data/lib/karafka/web/ui/lib/sorter.rb +3 -1
- data/lib/karafka/web/ui/models/jobs.rb +3 -2
- data/lib/karafka/web/ui/models/process.rb +12 -0
- data/lib/karafka/web/ui/public/javascripts/application.min.js.gz +0 -0
- data/lib/karafka/web/ui/public/stylesheets/application.css +9 -0
- data/lib/karafka/web/ui/public/stylesheets/application.min.css +4230 -1258
- data/lib/karafka/web/ui/public/stylesheets/application.min.css.br +0 -0
- data/lib/karafka/web/ui/public/stylesheets/application.min.css.gz +0 -0
- data/lib/karafka/web/ui/public/stylesheets/libs/highlight_dark.min.css.gz +0 -0
- data/lib/karafka/web/ui/public/stylesheets/libs/highlight_light.min.css.gz +0 -0
- data/lib/karafka/web/ui/views/cluster/_brokers_table.erb +21 -0
- data/lib/karafka/web/ui/views/cluster/_partition.erb +2 -2
- data/lib/karafka/web/ui/views/cluster/_partitions_table.erb +31 -0
- data/lib/karafka/web/ui/views/cluster/brokers.erb +1 -21
- data/lib/karafka/web/ui/views/cluster/replication.erb +1 -23
- data/lib/karafka/web/ui/views/consumers/_consumers_table.erb +22 -0
- data/lib/karafka/web/ui/views/consumers/index.erb +1 -22
- data/lib/karafka/web/ui/views/errors/_table.erb +28 -0
- data/lib/karafka/web/ui/views/errors/index.erb +1 -28
- data/lib/karafka/web/ui/views/jobs/_jobs_table.erb +22 -0
- data/lib/karafka/web/ui/views/jobs/pending.erb +1 -22
- data/lib/karafka/web/ui/views/jobs/running.erb +1 -22
- data/lib/karafka/web/ui/views/routing/_consumer_group.erb +18 -12
- data/lib/karafka/web/ui/views/routing/_details_table.erb +23 -0
- data/lib/karafka/web/ui/views/routing/show.erb +1 -29
- data/lib/karafka/web/ui/views/shared/_no_filter_results.erb +6 -0
- data/lib/karafka/web/version.rb +1 -1
- data/package-lock.json +98 -61
- metadata +35 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 53d7f8aa3ebaa18166dbcc4ef988d88687a22c3862d77dd3d582850afe59e5d1
|
|
4
|
+
data.tar.gz: bb4066f370418c911e71108643f08c9c07f1b7ea073a4736c0fdc4805670741f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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.
|
|
16
|
+
concurrent-ruby (1.3.8)
|
|
17
17
|
drb (2.2.3)
|
|
18
18
|
erubi (1.13.1)
|
|
19
|
-
et-orbi (1.4.
|
|
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.
|
|
36
|
-
json (2.
|
|
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.
|
|
109
|
+
raabro (1.5.0)
|
|
110
110
|
racc (1.8.1)
|
|
111
|
-
rack (3.2.
|
|
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.
|
|
118
|
+
reline (0.7.0)
|
|
119
119
|
io-console (~> 0.5)
|
|
120
|
-
roda (3.
|
|
120
|
+
roda (3.107.0)
|
|
121
121
|
rack
|
|
122
122
|
ruby2_keywords (0.0.5)
|
|
123
|
-
simplecov (1.
|
|
124
|
-
tilt (2.
|
|
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.
|
|
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.
|
|
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
|
|
155
|
-
#
|
|
156
|
-
|
|
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 <
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
120
|
+
sort(consumer_group)
|
|
114
121
|
end
|
|
115
122
|
|
|
116
123
|
render
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
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|
|
|
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|
|
|
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 =
|
|
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
|