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
|
@@ -38,15 +38,23 @@ module Karafka
|
|
|
38
38
|
self.sortable_attributes = %w[
|
|
39
39
|
name
|
|
40
40
|
active?
|
|
41
|
+
value
|
|
41
42
|
].freeze
|
|
42
43
|
|
|
44
|
+
# The list is filtered by topic/consumer group (at the view level), while the per-topic
|
|
45
|
+
# details page filters its flat attribute/value rows
|
|
46
|
+
self.filterable_attributes = {
|
|
47
|
+
index: %i[topic consumer_group],
|
|
48
|
+
show: %i[name value]
|
|
49
|
+
}.freeze
|
|
50
|
+
|
|
43
51
|
# Routing list
|
|
44
52
|
def index
|
|
45
53
|
detect_patterns_routes
|
|
46
54
|
|
|
47
55
|
@routes = Karafka::App.routes
|
|
48
56
|
@routes.each do |consumer_group|
|
|
49
|
-
|
|
57
|
+
sort(consumer_group.topics)
|
|
50
58
|
end
|
|
51
59
|
|
|
52
60
|
current_state = Models::ConsumersState.current
|
|
@@ -80,11 +88,25 @@ module Karafka
|
|
|
80
88
|
|
|
81
89
|
@topic || not_found!(topic_id)
|
|
82
90
|
|
|
91
|
+
# Present the routing settings as a flat, sortable + filterable list of name/value rows
|
|
92
|
+
@details = sort(filter(topic_detail_rows(@topic)))
|
|
93
|
+
|
|
83
94
|
render
|
|
84
95
|
end
|
|
85
96
|
|
|
86
97
|
private
|
|
87
98
|
|
|
99
|
+
# @param topic [Karafka::Routing::Topic] topic we present the routing details for
|
|
100
|
+
# @return [Array<Hash>] `{ name:, value: }` rows for every routing setting (including
|
|
101
|
+
# the Pro multiplexing settings)
|
|
102
|
+
def topic_detail_rows(topic)
|
|
103
|
+
flattener = Web::Ui::Lib::HashFlattener
|
|
104
|
+
rows = flattener.call(topic.subscription_group.kafka, "kafka")
|
|
105
|
+
rows.merge!(flattener.call(topic.to_h.except(:kafka)))
|
|
106
|
+
rows.merge!(flattener.call(topic.subscription_group.multiplexing.to_h, "multiplexing"))
|
|
107
|
+
rows.map { |name, value| { name: name.to_s, value: value } }
|
|
108
|
+
end
|
|
109
|
+
|
|
88
110
|
# Detect routes defined as patterns
|
|
89
111
|
def detect_patterns_routes
|
|
90
112
|
Lib::PatternsDetector.new.call
|
|
@@ -36,6 +36,10 @@ module Karafka
|
|
|
36
36
|
module ScheduledMessages
|
|
37
37
|
# Controller to display list of schedules (groups) and details about each
|
|
38
38
|
class SchedulesController < BaseController
|
|
39
|
+
self.filterable_attributes = %w[
|
|
40
|
+
topic_name
|
|
41
|
+
].freeze
|
|
42
|
+
|
|
39
43
|
# Displays list of groups
|
|
40
44
|
def index
|
|
41
45
|
topics = Models::Topic.all
|
|
@@ -54,6 +58,8 @@ module Karafka
|
|
|
54
58
|
|
|
55
59
|
@topics = topics.select { |topic| candidates.include?(topic.topic_name) }
|
|
56
60
|
|
|
61
|
+
@topics = filter(@topics)
|
|
62
|
+
|
|
57
63
|
render
|
|
58
64
|
end
|
|
59
65
|
|
|
@@ -44,13 +44,18 @@ module Karafka
|
|
|
44
44
|
read_only?
|
|
45
45
|
].freeze
|
|
46
46
|
|
|
47
|
+
self.filterable_attributes = %i[
|
|
48
|
+
name
|
|
49
|
+
value
|
|
50
|
+
].freeze
|
|
51
|
+
|
|
47
52
|
# Displays requested topic config details
|
|
48
53
|
#
|
|
49
54
|
# @param topic_name [String] topic we're interested in
|
|
50
55
|
def index(topic_name)
|
|
51
56
|
@topic = Models::Topic.find(topic_name)
|
|
52
57
|
|
|
53
|
-
@configs =
|
|
58
|
+
@configs = filter(sort(@topic.configs))
|
|
54
59
|
|
|
55
60
|
render
|
|
56
61
|
end
|
|
@@ -62,7 +62,7 @@ module Karafka
|
|
|
62
62
|
|
|
63
63
|
@aggregated, distribution = @topic.distribution(@active_partitions)
|
|
64
64
|
|
|
65
|
-
@distribution =
|
|
65
|
+
@distribution = sort(distribution)
|
|
66
66
|
|
|
67
67
|
next_page = @active_partitions.last < @topic.partition_count - 1
|
|
68
68
|
paginate(@params.current_page, next_page)
|
|
@@ -49,7 +49,7 @@ module Karafka
|
|
|
49
49
|
def show(topic_name)
|
|
50
50
|
@topic = Models::Topic.find(topic_name)
|
|
51
51
|
|
|
52
|
-
@partitions =
|
|
52
|
+
@partitions = sort(@topic[:partitions])
|
|
53
53
|
|
|
54
54
|
# Extract replication factor from the first partition (same for all partitions)
|
|
55
55
|
# The partitions data is an array of hashes with :replica_count key
|
|
@@ -38,6 +38,10 @@ module Karafka
|
|
|
38
38
|
class TopicsController < BaseController
|
|
39
39
|
self.sortable_attributes = [].freeze
|
|
40
40
|
|
|
41
|
+
self.filterable_attributes = %w[
|
|
42
|
+
topic_name
|
|
43
|
+
].freeze
|
|
44
|
+
|
|
41
45
|
after(:create, :delete) { cache.clear }
|
|
42
46
|
|
|
43
47
|
# Displays list of topics we can work with
|
|
@@ -48,6 +52,8 @@ module Karafka
|
|
|
48
52
|
@topics.delete_if { |topic| topic[:topic_name].start_with?("__") }
|
|
49
53
|
end
|
|
50
54
|
|
|
55
|
+
@topics = filter(@topics)
|
|
56
|
+
|
|
51
57
|
render
|
|
52
58
|
end
|
|
53
59
|
|
|
@@ -0,0 +1,173 @@
|
|
|
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
|
+
# Namespace for the Pro-only Web UI view helpers.
|
|
36
|
+
module Helpers
|
|
37
|
+
# Helpers for rendering the keyword filtering (search) box on data tables. Companion
|
|
38
|
+
# to the [[SortingHelper]]: sorting reorders a listing, filtering narrows it down.
|
|
39
|
+
module FilteringHelper
|
|
40
|
+
# @return [Boolean] true if a filtering keyword is currently active. Filtering
|
|
41
|
+
# (search) is a Pro-only feature, so this is always false in OSS.
|
|
42
|
+
def filtering?
|
|
43
|
+
return false unless ::Karafka.pro?
|
|
44
|
+
|
|
45
|
+
!params.current_filter.empty?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Renders a filtering form for the current listing.
|
|
49
|
+
#
|
|
50
|
+
# The form submits via GET to the current path, preserving every other query parameter
|
|
51
|
+
# (most importantly the current sort) as hidden fields while resetting pagination, so
|
|
52
|
+
# filtering always starts from the first page.
|
|
53
|
+
#
|
|
54
|
+
# It renders as a full-width search field with a "Search" submit button and a "Reset"
|
|
55
|
+
# button (always present, but disabled when there is nothing to reset, so the layout
|
|
56
|
+
# does not shift). When the controller exposes a `{ field => label }` map of filterable
|
|
57
|
+
# fields (via `filter`), a field selector is fused into the left of the field so the
|
|
58
|
+
# user can pick which attribute to filter on.
|
|
59
|
+
#
|
|
60
|
+
# The placeholder is intentionally a single generic default across every listing: a
|
|
61
|
+
# field selector always accompanies the input and already tells the user which
|
|
62
|
+
# attribute is being filtered, so a per-view placeholder would be redundant (and would
|
|
63
|
+
# look stale until the form is submitted, since it is server-rendered and does not
|
|
64
|
+
# follow the select live).
|
|
65
|
+
#
|
|
66
|
+
# @param labels [Hash] per-view overrides for the field selector labels, e.g.
|
|
67
|
+
# `labels: { id: "Task ID" }`. Anything not overridden falls back to the shared
|
|
68
|
+
# {FILTER_NAMES} map (and then to a humanized attribute name).
|
|
69
|
+
# @return [String] html of the filtering form
|
|
70
|
+
def filter_box(labels: {})
|
|
71
|
+
# Filtering (search) is a Pro-only feature, so we render nothing in OSS.
|
|
72
|
+
return "" unless ::Karafka.pro?
|
|
73
|
+
|
|
74
|
+
# A field is either a plain attribute name or a key-alias descriptor; the selector only
|
|
75
|
+
# cares about its name. We render the field selector whenever the controller exposes
|
|
76
|
+
# any filterable field, so every search looks consistent (even single-field listings
|
|
77
|
+
# show a one-option select).
|
|
78
|
+
fields = Array(@filterable_fields).map { |field| filter_field_name(field) }
|
|
79
|
+
fielded = fields.any?
|
|
80
|
+
|
|
81
|
+
partial(
|
|
82
|
+
"shared/filter_box",
|
|
83
|
+
locals: {
|
|
84
|
+
fields: fields,
|
|
85
|
+
fielded: fielded,
|
|
86
|
+
selected: fielded ? selected_filter_field(fields) : nil,
|
|
87
|
+
labels: labels,
|
|
88
|
+
value_name: fielded ? "filter[value]" : "filter",
|
|
89
|
+
keyword: params.current_filter,
|
|
90
|
+
hidden_params: preserved_filter_params,
|
|
91
|
+
active: filtering?,
|
|
92
|
+
clear_path: filter_clear_path
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Human friendly labels for the attributes we allow filtering on. Attributes not listed
|
|
98
|
+
# here fall back to a humanized version of their name (see {#filter_field_label}), so
|
|
99
|
+
# controllers only need to declare bare attribute names in `filterable_attributes`.
|
|
100
|
+
FILTER_NAMES = {
|
|
101
|
+
id: "Process ID",
|
|
102
|
+
subscribed_topics: "Subscriptions",
|
|
103
|
+
tags: "Tags",
|
|
104
|
+
topic: "Topic",
|
|
105
|
+
topic_name: "Topic",
|
|
106
|
+
consumer_group: "Consumer group",
|
|
107
|
+
subscription_group: "Subscription group",
|
|
108
|
+
consumer: "Consumer",
|
|
109
|
+
type: "Type",
|
|
110
|
+
name: "Name",
|
|
111
|
+
value: "Value",
|
|
112
|
+
cron: "Cron",
|
|
113
|
+
broker_name: "Broker"
|
|
114
|
+
}.freeze
|
|
115
|
+
|
|
116
|
+
private_constant :FILTER_NAMES
|
|
117
|
+
|
|
118
|
+
private
|
|
119
|
+
|
|
120
|
+
# @param field [String, Symbol, #name] a filterable field: a plain attribute name or a
|
|
121
|
+
# key-alias descriptor
|
|
122
|
+
# @return [String] the field's name
|
|
123
|
+
def filter_field_name(field)
|
|
124
|
+
field.respond_to?(:path) ? field.name.to_s : field.to_s
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# @param attribute [String, Symbol] filterable attribute name
|
|
128
|
+
# @param overrides [Hash] per-view label overrides (keyed by attribute)
|
|
129
|
+
# @return [String] human friendly label for the attribute: a per-view override if given,
|
|
130
|
+
# otherwise the shared {FILTER_NAMES} entry, otherwise a humanized attribute name
|
|
131
|
+
def filter_field_label(attribute, overrides = {})
|
|
132
|
+
key = attribute.to_sym
|
|
133
|
+
|
|
134
|
+
overrides.transform_keys(&:to_sym).fetch(key) do
|
|
135
|
+
FILTER_NAMES.fetch(key) do
|
|
136
|
+
attribute.to_s.tr("_", " ").split.map(&:capitalize).join(" ")
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# @param fields [Array<String, Symbol>] filterable attributes
|
|
142
|
+
# @return [String] the currently selected filter field, defaulting to the first one when
|
|
143
|
+
# nothing (valid) is selected
|
|
144
|
+
def selected_filter_field(fields)
|
|
145
|
+
selected = params.current_filter_field
|
|
146
|
+
|
|
147
|
+
fields.map(&:to_s).include?(selected) ? selected : fields.first.to_s
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Builds the path used by the "clear" control: the current path with all filtering and
|
|
151
|
+
# pagination parameters removed.
|
|
152
|
+
#
|
|
153
|
+
# @return [String] path without any filter/page query parameters
|
|
154
|
+
def filter_clear_path
|
|
155
|
+
query = URI.encode_www_form(preserved_filter_params)
|
|
156
|
+
|
|
157
|
+
query.empty? ? request.path : "#{request.path}?#{query}"
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# @return [Hash] current request query parameters that we want to carry over when
|
|
161
|
+
# submitting the filtering form (everything except the filtering parameters themselves
|
|
162
|
+
# and the pagination, which we intentionally reset)
|
|
163
|
+
def preserved_filter_params
|
|
164
|
+
flatten_params("", request.params).reject do |key, _value|
|
|
165
|
+
key == "filter" || key.start_with?("filter[") || key == "page"
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
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 Helpers
|
|
36
|
+
# View-level filtering of topic collections for the routing and per-process subscriptions
|
|
37
|
+
# views. Unlike the generic {FilteringHelper} (which renders the search box) and the
|
|
38
|
+
# [[Filter]] engine (which prunes per-request structures in place), this narrows the live
|
|
39
|
+
# `Karafka::App.routes` / process subscriptions non-destructively, since those must never
|
|
40
|
+
# be mutated.
|
|
41
|
+
module TopicsFilteringHelper
|
|
42
|
+
# Non-destructively narrows a topics collection (routing or a consumer subscription)
|
|
43
|
+
# down to the ones matching the current filter.
|
|
44
|
+
#
|
|
45
|
+
# It is field aware: when the `topic` field is selected only topic names are matched;
|
|
46
|
+
# when `consumer_group`/`subscription_group` is selected the whole collection is kept or
|
|
47
|
+
# dropped based on that group's name. With no explicit field (plain keyword) a topic is
|
|
48
|
+
# kept when its own name matches or when any of the provided group labels match.
|
|
49
|
+
#
|
|
50
|
+
# It always returns a new array and leaves the source untouched.
|
|
51
|
+
#
|
|
52
|
+
# @param topics [Enumerable] topics of a subscription/consumer group
|
|
53
|
+
# @param consumer_group [String, nil] the consumer group name shown as the section title
|
|
54
|
+
# @param subscription_group [String, nil] the subscription group name (subscriptions)
|
|
55
|
+
# @return [Array] all topics when no filtering is active, otherwise only matching ones
|
|
56
|
+
def visible_topics(topics, consumer_group: nil, subscription_group: nil)
|
|
57
|
+
return topics.to_a unless filtering?
|
|
58
|
+
|
|
59
|
+
keyword = params.current_filter.downcase
|
|
60
|
+
by_name = -> { topics.select { |topic| topic.name.to_s.downcase.include?(keyword) } }
|
|
61
|
+
label_match = ->(label) { label.to_s.downcase.include?(keyword) }
|
|
62
|
+
|
|
63
|
+
case params.current_filter_field
|
|
64
|
+
when "consumer_group"
|
|
65
|
+
label_match.call(consumer_group) ? topics.to_a : []
|
|
66
|
+
when "subscription_group"
|
|
67
|
+
label_match.call(subscription_group) ? topics.to_a : []
|
|
68
|
+
when "topic"
|
|
69
|
+
by_name.call
|
|
70
|
+
else
|
|
71
|
+
# Plain keyword: match the topic name or any of the group labels
|
|
72
|
+
[consumer_group, subscription_group].any?(&label_match) ? topics.to_a : by_name.call
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
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 Lib
|
|
36
|
+
# Filtering (search) is a Pro-only feature, so all of its wiring lives in this concern and
|
|
37
|
+
# is mixed only into Pro controllers. OSS controllers never gain filtering.
|
|
38
|
+
#
|
|
39
|
+
# It is included by the Pro base controller (covering every standalone Pro controller)
|
|
40
|
+
# and, because a few Pro controllers inherit OSS controllers directly (and thus bypass
|
|
41
|
+
# the Pro base controller), by those explicitly.
|
|
42
|
+
module Filtering
|
|
43
|
+
# Descriptor for a filterable field that matches nested hash KEYS at a position in the
|
|
44
|
+
# structure, rather than an attribute on the records. Used for tree-shaped data whose
|
|
45
|
+
# meaningful labels are keys (e.g. the health stats, where consumer group and topic
|
|
46
|
+
# names are keys). See {Web::Ui::Lib::Filter} for how the engine consumes it.
|
|
47
|
+
#
|
|
48
|
+
# @!attribute name
|
|
49
|
+
# @return [String] the field name (shown in the selector, matched against the request)
|
|
50
|
+
# @!attribute path
|
|
51
|
+
# @return [Array] keys to descend from each container before matching that hash's keys
|
|
52
|
+
KeyField = Struct.new(:name, :path)
|
|
53
|
+
|
|
54
|
+
# Builds a {KeyField} descriptor for use in `filterable_attributes`.
|
|
55
|
+
#
|
|
56
|
+
# @param name [Symbol, String] field name
|
|
57
|
+
# @param under [Symbol, String, Array, nil] key(s) to descend before matching keys; nil
|
|
58
|
+
# matches the container's own keys
|
|
59
|
+
# @return [KeyField]
|
|
60
|
+
def self.key(name, under: nil)
|
|
61
|
+
KeyField.new(name.to_s, Array(under))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @param base [Class] the controller including this concern
|
|
65
|
+
def self.included(base)
|
|
66
|
+
# Attributes on which we can filter in a given controller. Since we can filter on
|
|
67
|
+
# method invocations, this needs to be limited and provided on a per controller basis
|
|
68
|
+
# (same contract as `sortable_attributes`).
|
|
69
|
+
#
|
|
70
|
+
# It is either a flat list applied to every action, or a `{ action => fields }` hash
|
|
71
|
+
# with an optional `:default` entry for controllers whose actions render different
|
|
72
|
+
# columns (e.g. cluster brokers vs configs). See {#filterable_fields} for the
|
|
73
|
+
# resolution.
|
|
74
|
+
base.singleton_class.attr_accessor :filterable_attributes
|
|
75
|
+
|
|
76
|
+
# Expose the per-action filterable fields to the view so the filtering box (and the
|
|
77
|
+
# view-level filtering helpers) can render the field selector. Resolved from the
|
|
78
|
+
# controller's `filterable_attributes` declaration on every request, so no action has
|
|
79
|
+
# to wire this up by hand.
|
|
80
|
+
base.before { @filterable_fields = filterable_fields }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
# Filters the provided resources in place based on the current filtering keyword and the
|
|
86
|
+
# fields allowed for filtering.
|
|
87
|
+
#
|
|
88
|
+
# The allowed fields default to `@filterable_fields`, which the before hook resolves
|
|
89
|
+
# from the controller's `filterable_attributes` declaration for the current action and
|
|
90
|
+
# exposes to the view. Pass `fields` only when the engine's match set must differ from
|
|
91
|
+
# the fields shown in the selector (e.g. the health views scope the selector by
|
|
92
|
+
# topic/consumer group but keyword-match against the leaf partition id); it overrides
|
|
93
|
+
# matching only and does not change the exposed selector.
|
|
94
|
+
#
|
|
95
|
+
# @param resources [Hash, Array, Web::Ui::Lib::HashProxy] object for filtering
|
|
96
|
+
# @param fields [Array<Symbol>, nil] explicit engine allow-list for matching, overriding
|
|
97
|
+
# `@filterable_fields`
|
|
98
|
+
# @return [Hash, Array, Web::Ui::Lib::HashProxy] filtered results
|
|
99
|
+
# @note It mutates the provided resources in place, so it must not be used on shared
|
|
100
|
+
# structures like the app routing.
|
|
101
|
+
def filter(resources, fields: nil)
|
|
102
|
+
Web::Ui::Lib::Filter.new(
|
|
103
|
+
@params.current_filter,
|
|
104
|
+
allowed_attributes: fields || @filterable_fields,
|
|
105
|
+
field: @params.current_filter_field
|
|
106
|
+
).call(resources)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Resolves the fields the current action can be filtered on from the controller's
|
|
110
|
+
# `filterable_attributes` declaration. The declaration is either a flat list (applied to
|
|
111
|
+
# every action) or a `{ action => fields }` hash with an optional `:default` entry used
|
|
112
|
+
# for the actions not listed explicitly.
|
|
113
|
+
#
|
|
114
|
+
# @return [Array<Symbol>, Array<String>] fields for the current action (empty when the
|
|
115
|
+
# action is not filterable)
|
|
116
|
+
def filterable_fields
|
|
117
|
+
attributes = self.class.filterable_attributes
|
|
118
|
+
|
|
119
|
+
return Array(attributes) unless attributes.is_a?(Hash)
|
|
120
|
+
|
|
121
|
+
Array(attributes[@current_action_name] || attributes[:default])
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -40,10 +40,10 @@ module Karafka
|
|
|
40
40
|
# Base class for all the search matchers
|
|
41
41
|
# Each matcher needs to have a class `#name` method and needs to respond to `#call`
|
|
42
42
|
class Base
|
|
43
|
-
# @param phrase [String] string phrase for search
|
|
44
43
|
# @param message [Karafka::Messages::Message] message in which we search
|
|
44
|
+
# @param phrase [String] string phrase for search
|
|
45
45
|
# @return [Boolean] true if found, otherwise false
|
|
46
|
-
def call(
|
|
46
|
+
def call(message, phrase)
|
|
47
47
|
raise NotImplementedError, "Implement in a subclass"
|
|
48
48
|
end
|
|
49
49
|
|
|
@@ -40,8 +40,6 @@ module Karafka
|
|
|
40
40
|
controller = build(Controllers::ClusterController)
|
|
41
41
|
|
|
42
42
|
r.get "replication" do
|
|
43
|
-
# We use the non-pro controller here because this action is the same
|
|
44
|
-
controller = build(Ui::Controllers::ClusterController)
|
|
45
43
|
controller.replication
|
|
46
44
|
end
|
|
47
45
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<%# Karafka Pro - Source Available Commercial Software %>
|
|
2
|
+
<%# Copyright (c) 2017-present Maciej Mensfeld. All rights reserved. %>
|
|
3
|
+
<%# This software is NOT open source. It is source-available commercial software %>
|
|
4
|
+
<%# requiring a paid license for use. It is NOT covered by LGPL. %>
|
|
5
|
+
<%# The author retains all right, title, and interest in this software, %>
|
|
6
|
+
<%# including all copyrights, patents, and other intellectual property rights. %>
|
|
7
|
+
<%# No patent rights are granted under this license. %>
|
|
8
|
+
<%# PROHIBITED: Use without a valid commercial license %>
|
|
9
|
+
<%# PROHIBITED: Redistribution, modification, or derivative works without authorization %>
|
|
10
|
+
<%# PROHIBITED: Reverse engineering, decompilation, or disassembly of this software %>
|
|
11
|
+
<%# PROHIBITED: Use as training data for AI/ML models or inclusion in datasets %>
|
|
12
|
+
<%# PROHIBITED: Scraping, crawling, or automated collection for any purpose %>
|
|
13
|
+
<%# PERMITTED: Reading, referencing, and linking for personal or commercial use %>
|
|
14
|
+
<%# PERMITTED: Runtime retrieval by AI assistants, coding agents, and RAG systems %>
|
|
15
|
+
<%# PERMITTED: for the purpose of providing contextual help to Karafka users %>
|
|
16
|
+
<%# Receipt, viewing, or possession of this software does not convey or %>
|
|
17
|
+
<%# imply any license or right beyond those expressly stated above. %>
|
|
18
|
+
<%# License: https://karafka.io/docs/Pro-License-Comm/ %>
|
|
19
|
+
<%# Contact: contact@karafka.io %>
|
|
20
|
+
|
|
21
|
+
<div class="col-span-12 mb-3">
|
|
22
|
+
<div class="data-table-wrapper">
|
|
23
|
+
<table class="data-table">
|
|
24
|
+
<thead>
|
|
25
|
+
<tr>
|
|
26
|
+
<th><%== sort_link('Node ID', :id) %></th>
|
|
27
|
+
<th><%== sort_link(:name) %></th>
|
|
28
|
+
<th><%== sort_link(:port) %></th>
|
|
29
|
+
</tr>
|
|
30
|
+
</thead>
|
|
31
|
+
<tbody>
|
|
32
|
+
<%==
|
|
33
|
+
each_partial(
|
|
34
|
+
@brokers,
|
|
35
|
+
'cluster/broker'
|
|
36
|
+
)
|
|
37
|
+
%>
|
|
38
|
+
</tbody>
|
|
39
|
+
</table>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<%# Karafka Pro - Source Available Commercial Software %>
|
|
2
|
+
<%# Copyright (c) 2017-present Maciej Mensfeld. All rights reserved. %>
|
|
3
|
+
<%# This software is NOT open source. It is source-available commercial software %>
|
|
4
|
+
<%# requiring a paid license for use. It is NOT covered by LGPL. %>
|
|
5
|
+
<%# The author retains all right, title, and interest in this software, %>
|
|
6
|
+
<%# including all copyrights, patents, and other intellectual property rights. %>
|
|
7
|
+
<%# No patent rights are granted under this license. %>
|
|
8
|
+
<%# PROHIBITED: Use without a valid commercial license %>
|
|
9
|
+
<%# PROHIBITED: Redistribution, modification, or derivative works without authorization %>
|
|
10
|
+
<%# PROHIBITED: Reverse engineering, decompilation, or disassembly of this software %>
|
|
11
|
+
<%# PROHIBITED: Use as training data for AI/ML models or inclusion in datasets %>
|
|
12
|
+
<%# PROHIBITED: Scraping, crawling, or automated collection for any purpose %>
|
|
13
|
+
<%# PERMITTED: Reading, referencing, and linking for personal or commercial use %>
|
|
14
|
+
<%# PERMITTED: Runtime retrieval by AI assistants, coding agents, and RAG systems %>
|
|
15
|
+
<%# PERMITTED: for the purpose of providing contextual help to Karafka users %>
|
|
16
|
+
<%# Receipt, viewing, or possession of this software does not convey or %>
|
|
17
|
+
<%# imply any license or right beyond those expressly stated above. %>
|
|
18
|
+
<%# License: https://karafka.io/docs/Pro-License-Comm/ %>
|
|
19
|
+
<%# Contact: contact@karafka.io %>
|
|
20
|
+
|
|
21
|
+
<div class="data-table-wrapper">
|
|
22
|
+
<table class="data-table">
|
|
23
|
+
<thead>
|
|
24
|
+
<tr>
|
|
25
|
+
<th><%== sort_link(:name) %></th>
|
|
26
|
+
<th><%== sort_link(:value) %></th>
|
|
27
|
+
<th><%== sort_link(:default?) %></th>
|
|
28
|
+
<th><%== sort_link(:sensitive?) %></th>
|
|
29
|
+
<th><%== sort_link(:read_only?) %></th>
|
|
30
|
+
</tr>
|
|
31
|
+
</thead>
|
|
32
|
+
<tbody>
|
|
33
|
+
<%==
|
|
34
|
+
each_partial(
|
|
35
|
+
@configs,
|
|
36
|
+
'cluster/config'
|
|
37
|
+
)
|
|
38
|
+
%>
|
|
39
|
+
</tbody>
|
|
40
|
+
</table>
|
|
41
|
+
</div>
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
<%= partition[:leader] %>
|
|
36
36
|
</td>
|
|
37
37
|
<td>
|
|
38
|
-
|
|
38
|
+
<%== partition_replica_brokers(partition, link: true) %>
|
|
39
39
|
</td>
|
|
40
40
|
<td>
|
|
41
|
-
|
|
41
|
+
<%== partition_in_sync_brokers(partition, link: true) %>
|
|
42
42
|
</td>
|
|
43
43
|
</tr>
|