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
|
@@ -5,31 +5,10 @@ module Karafka
|
|
|
5
5
|
module Ui
|
|
6
6
|
# Namespace for helpers used by the Web UI
|
|
7
7
|
module Helpers
|
|
8
|
-
#
|
|
8
|
+
# Application-wide helpers: navigation, layout, icons, empty states and generic hash
|
|
9
|
+
# utilities. More specific concerns live in dedicated helpers ([[SortingHelper]],
|
|
10
|
+
# [[FormattingHelper]], [[BadgesHelper]], [[PartitionsHelper]]).
|
|
9
11
|
module ApplicationHelper
|
|
10
|
-
# Default attribute names mapped from the attributes themselves
|
|
11
|
-
# It makes it easier as we do not have to declare those all the time
|
|
12
|
-
SORT_NAMES = {
|
|
13
|
-
id: "ID",
|
|
14
|
-
partition_id: "Partition",
|
|
15
|
-
memory_usage: "RSS",
|
|
16
|
-
started_at: "Started",
|
|
17
|
-
committed_offset: "Committed",
|
|
18
|
-
last_offset: "Last",
|
|
19
|
-
first_offset: "First",
|
|
20
|
-
lo_offset: "Low",
|
|
21
|
-
hi_offset: "High",
|
|
22
|
-
ls_offset: "LSO",
|
|
23
|
-
lag_hybrid: "Lag",
|
|
24
|
-
lag_stored: "Stored",
|
|
25
|
-
stored_offset: "Stored",
|
|
26
|
-
fetch_state: "Fetch",
|
|
27
|
-
poll_state: "Poll",
|
|
28
|
-
lso_risk_state: "LSO"
|
|
29
|
-
}.freeze
|
|
30
|
-
|
|
31
|
-
private_constant :SORT_NAMES
|
|
32
|
-
|
|
33
12
|
# Adds active class to the current location in the nav if needed
|
|
34
13
|
# @param location [Hash]
|
|
35
14
|
def nav_class(location)
|
|
@@ -39,167 +18,6 @@ module Karafka
|
|
|
39
18
|
local_location.public_send(:"#{comparator}?", value) ? "active" : ""
|
|
40
19
|
end
|
|
41
20
|
|
|
42
|
-
# Converts object into a string and for objects that would anyhow return their
|
|
43
|
-
# stringified instance value, it replaces it with the class name instead.
|
|
44
|
-
# Useful for deserializers, etc presentation.
|
|
45
|
-
#
|
|
46
|
-
# @param object [Object]
|
|
47
|
-
# @return [String]
|
|
48
|
-
def object_value_to_s(object)
|
|
49
|
-
object.to_s.include?("#<") ? object.class.to_s : object.to_s
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# Takes a status and recommends background style color
|
|
53
|
-
#
|
|
54
|
-
# @param status [String] status
|
|
55
|
-
# @return [String] background style
|
|
56
|
-
def status_badge(status)
|
|
57
|
-
case status
|
|
58
|
-
when "initialized" then "badge-success"
|
|
59
|
-
when "supervising" then "badge-success"
|
|
60
|
-
when "running" then "badge-success"
|
|
61
|
-
when "quieting" then "badge-warning"
|
|
62
|
-
when "quiet" then "badge-warning"
|
|
63
|
-
when "stopping" then "badge-warning"
|
|
64
|
-
when "stopped" then "badge-error"
|
|
65
|
-
when "terminated" then "badge-error"
|
|
66
|
-
else
|
|
67
|
-
raise ::Karafka::Errors::UnsupportedCaseError, status
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
# Takes the lag trend and gives it appropriate background style color for badge
|
|
72
|
-
#
|
|
73
|
-
# @param trend [Numeric] lag trend
|
|
74
|
-
# @return [String] bg classes
|
|
75
|
-
def lag_trend_badge(trend)
|
|
76
|
-
bg = "badge-success" if trend.negative?
|
|
77
|
-
bg ||= "badge-warning" if trend.positive?
|
|
78
|
-
bg ||= "badge-secondary"
|
|
79
|
-
bg
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
# Renders tags one after another
|
|
83
|
-
#
|
|
84
|
-
# @param tags_array [Array<String>]
|
|
85
|
-
# @return [String] tags badges
|
|
86
|
-
def tags(tags_array)
|
|
87
|
-
tags_array
|
|
88
|
-
.map { |tag| %(<span class="badge badge-info">#{tag}</span>) }
|
|
89
|
-
.join(" ")
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
# Takes a kafka report state and recommends background style color
|
|
93
|
-
# @param state [String] state
|
|
94
|
-
# @return [String] background style
|
|
95
|
-
def kafka_state_badge(state)
|
|
96
|
-
case state
|
|
97
|
-
when "up" then "badge-success"
|
|
98
|
-
when "active" then "badge-success"
|
|
99
|
-
when "steady" then "badge-success"
|
|
100
|
-
else
|
|
101
|
-
"badge-warning"
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
# @param mem_kb [Integer] memory used in KB
|
|
106
|
-
# @return [String] formatted memory usage
|
|
107
|
-
def format_memory(mem_kb)
|
|
108
|
-
return "0" if !mem_kb || mem_kb.zero?
|
|
109
|
-
|
|
110
|
-
if mem_kb < 10_240
|
|
111
|
-
"#{number_with_delimiter(mem_kb.round(4))} KB"
|
|
112
|
-
elsif mem_kb < 1_000_000
|
|
113
|
-
"#{number_with_delimiter((mem_kb / 1024.0).to_i)} MB"
|
|
114
|
-
else
|
|
115
|
-
"#{number_with_delimiter((mem_kb / (1024.0 * 1024.0)).round(1))} GB"
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Converts number to a more friendly delimiter based version
|
|
120
|
-
# @param number [Numeric]
|
|
121
|
-
# @param delimiter [String] delimiter (comma by default)
|
|
122
|
-
# @return [String] number with delimiter
|
|
123
|
-
def number_with_delimiter(number, delimiter = ",")
|
|
124
|
-
return "" unless number
|
|
125
|
-
|
|
126
|
-
parts = number.to_s.to_str.split(".")
|
|
127
|
-
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}")
|
|
128
|
-
parts.join(".")
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
# @param lag [Integer] lag
|
|
132
|
-
# @return [String] lag if correct or `N/A` with labeled explanation
|
|
133
|
-
# @see #offset_with_label
|
|
134
|
-
def lag_with_label(lag)
|
|
135
|
-
if lag.negative?
|
|
136
|
-
title = "Not available until first offset commit"
|
|
137
|
-
%(<span class="badge badge-secondary" title="#{title}">N/A</span>)
|
|
138
|
-
else
|
|
139
|
-
lag.to_s
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
# @param topic_name [String] name of the topic for explorer path
|
|
144
|
-
# @param partition_id [Integer] partition for the explorer path
|
|
145
|
-
# @param offset [Integer] offset
|
|
146
|
-
# @param explore [Boolean] should we generate (when allowed) a link to message explorer
|
|
147
|
-
# @return [String] offset if correct or `N/A` with labeled explanation for offsets
|
|
148
|
-
# that are less than 0. Offset with less than 0 indicates, that the offset was not
|
|
149
|
-
# yet committed and there is no value we know of
|
|
150
|
-
def offset_with_label(topic_name, partition_id, offset, explore: false)
|
|
151
|
-
if offset.negative?
|
|
152
|
-
title = "Not available until first offset commit"
|
|
153
|
-
%(<span class="badge badge-secondary" title="#{title}">N/A</span>)
|
|
154
|
-
elsif explore
|
|
155
|
-
path = explorer_topics_path(topic_name, partition_id, offset)
|
|
156
|
-
%(<a href="#{path}">#{offset}</a>)
|
|
157
|
-
else
|
|
158
|
-
offset.to_s
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
# Normalizes the metric value for display. Negative values coming from statistics usually
|
|
163
|
-
# mean, that the value is not (yet) available.
|
|
164
|
-
#
|
|
165
|
-
# @param value [Integer]
|
|
166
|
-
# @return [String] input value if not negative or N/A
|
|
167
|
-
def normalized_metric(value)
|
|
168
|
-
value.negative? ? "N/A" : value.to_s
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
# @param details [::Karafka::Web::Ui::Models::Partition] partition information with
|
|
172
|
-
# lso risk state info
|
|
173
|
-
# @return [String] background classes for row marking
|
|
174
|
-
def lso_risk_state_bg(details)
|
|
175
|
-
case details.lso_risk_state
|
|
176
|
-
when :active
|
|
177
|
-
""
|
|
178
|
-
when :at_risk
|
|
179
|
-
"bg-warning bg-opacity-25"
|
|
180
|
-
when :stopped
|
|
181
|
-
"bg-error bg-opacity-25"
|
|
182
|
-
else
|
|
183
|
-
raise ::Karafka::Errors::UnsupportedCaseError
|
|
184
|
-
end
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
# @param details [::Karafka::Web::Ui::Models::Partition] partition information with
|
|
188
|
-
# lso risk state info
|
|
189
|
-
# @return [String] background classes for row marking
|
|
190
|
-
def lso_risk_state_badge(details)
|
|
191
|
-
case details.lso_risk_state
|
|
192
|
-
when :active
|
|
193
|
-
""
|
|
194
|
-
when :at_risk
|
|
195
|
-
"badge-warning"
|
|
196
|
-
when :stopped
|
|
197
|
-
"badge-error"
|
|
198
|
-
else
|
|
199
|
-
raise ::Karafka::Errors::UnsupportedCaseError
|
|
200
|
-
end
|
|
201
|
-
end
|
|
202
|
-
|
|
203
21
|
# Sets the particular page title
|
|
204
22
|
#
|
|
205
23
|
# @param title [String] page title
|
|
@@ -208,99 +26,6 @@ module Karafka
|
|
|
208
26
|
content_for(:title) { title }
|
|
209
27
|
end
|
|
210
28
|
|
|
211
|
-
# @param hash [Hash] we want to flatten
|
|
212
|
-
# @param parent_key [String] key for recursion
|
|
213
|
-
# @param result [Hash] result for recursion
|
|
214
|
-
# @return [Hash]
|
|
215
|
-
def flat_hash(hash, parent_key = nil, result = {})
|
|
216
|
-
hash.each do |key, value|
|
|
217
|
-
current_key = parent_key ? "#{parent_key}.#{key}" : key.to_s
|
|
218
|
-
if value.is_a?(Hash)
|
|
219
|
-
flat_hash(value, current_key, result)
|
|
220
|
-
elsif value.is_a?(Array)
|
|
221
|
-
value.each_with_index do |item, index|
|
|
222
|
-
flat_hash({ index => item }, current_key, result)
|
|
223
|
-
end
|
|
224
|
-
else
|
|
225
|
-
result[current_key] = value
|
|
226
|
-
end
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
result
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
# @param name [String] link value
|
|
233
|
-
# @param attribute [Symbol, nil] sorting attribute or nil if we provide only symbol name
|
|
234
|
-
# @param rev [Boolean] when set to true, arrows will be in the reverse position. This is
|
|
235
|
-
# used when the description in the link is reverse to data we sort. For example we have
|
|
236
|
-
# order on when processes were started and we display "x hours" ago but we sort on
|
|
237
|
-
# their age, meaning that it looks like it is the other way around. This flag allows
|
|
238
|
-
# us to reverse just he arrow making it look consistent with the presented data order
|
|
239
|
-
# @return [String] html link for sorting with arrow when attribute sort enabled
|
|
240
|
-
def sort_link(name, attribute = nil, rev: false)
|
|
241
|
-
unless attribute
|
|
242
|
-
attribute = name
|
|
243
|
-
|
|
244
|
-
if SORT_NAMES[attribute]
|
|
245
|
-
name = SORT_NAMES[attribute]
|
|
246
|
-
else
|
|
247
|
-
name = attribute.to_s.tr("_", " ").tr("?", "")
|
|
248
|
-
# Always capitalize the name
|
|
249
|
-
name = name.split.map(&:capitalize).join(" ")
|
|
250
|
-
end
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
arrow_both = "⇕"
|
|
254
|
-
arrow_down = "▾"
|
|
255
|
-
arrow_up = "▴"
|
|
256
|
-
|
|
257
|
-
desc = "#{attribute} desc"
|
|
258
|
-
asc = "#{attribute} asc"
|
|
259
|
-
path = current_path(sort: desc)
|
|
260
|
-
full_name = "#{name} #{arrow_both}"
|
|
261
|
-
|
|
262
|
-
if params.current_sort == desc
|
|
263
|
-
path = current_path(sort: asc)
|
|
264
|
-
full_name = "#{name} #{rev ? arrow_up : arrow_down}"
|
|
265
|
-
end
|
|
266
|
-
|
|
267
|
-
if params.current_sort == asc
|
|
268
|
-
path = current_path(sort: desc)
|
|
269
|
-
full_name = "#{name} #{rev ? arrow_down : arrow_up}"
|
|
270
|
-
end
|
|
271
|
-
|
|
272
|
-
"<a class=\"sort\" href=\"#{path}\">#{full_name}</a>"
|
|
273
|
-
end
|
|
274
|
-
|
|
275
|
-
# Truncates given text if it is too long and wraps it with a title with full text.
|
|
276
|
-
# Can use a middle-based strategy that keeps beginning and ending of a string instead of
|
|
277
|
-
# keeping just the beginning.
|
|
278
|
-
#
|
|
279
|
-
# The `:middle` strategy is useful when we have strings such as really long process names
|
|
280
|
-
# that have important beginning and end but middle can be removed without risk of not
|
|
281
|
-
# allowing user to recognize the content.
|
|
282
|
-
#
|
|
283
|
-
# @param string [String] string we want to truncate
|
|
284
|
-
# @param length [Integer] max length of the final string that we accept before truncating
|
|
285
|
-
# @param omission [String] truncation omission
|
|
286
|
-
# @param strategy [Symbol] `:default` or `:middle` how should we truncate
|
|
287
|
-
# @return [String] HTML span tag with truncated content and full content title
|
|
288
|
-
def truncate(string, length: 50, omission: "...", strategy: :default)
|
|
289
|
-
return string if string.length <= length
|
|
290
|
-
|
|
291
|
-
case strategy
|
|
292
|
-
when :default
|
|
293
|
-
truncated = string[0...(length - omission.length)] + omission
|
|
294
|
-
when :middle
|
|
295
|
-
part_length = (length - omission.length) / 2
|
|
296
|
-
truncated = string[0...part_length] + omission + string[-part_length..]
|
|
297
|
-
else
|
|
298
|
-
raise Karafka::Errors::UnsupportedCaseError, "Unknown strategy: #{strategy}"
|
|
299
|
-
end
|
|
300
|
-
|
|
301
|
-
%(<span title="#{string}">#{truncated}</span>)
|
|
302
|
-
end
|
|
303
|
-
|
|
304
29
|
# Renders the svg icon out of our icon set
|
|
305
30
|
# @param name [String, Symbol] name of the icon
|
|
306
31
|
# @param size [Integer, nil] optional size override (defaults to the icon's own size)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Web
|
|
5
|
+
module Ui
|
|
6
|
+
module Helpers
|
|
7
|
+
# Helpers mapping domain states (process status, kafka state, lag trend) to badge styles
|
|
8
|
+
# and rendering simple badge collections
|
|
9
|
+
module BadgesHelper
|
|
10
|
+
# Takes a status and recommends background style color
|
|
11
|
+
#
|
|
12
|
+
# @param status [String] status
|
|
13
|
+
# @return [String] background style
|
|
14
|
+
def status_badge(status)
|
|
15
|
+
case status
|
|
16
|
+
when "initialized" then "badge-success"
|
|
17
|
+
when "supervising" then "badge-success"
|
|
18
|
+
when "running" then "badge-success"
|
|
19
|
+
when "quieting" then "badge-warning"
|
|
20
|
+
when "quiet" then "badge-warning"
|
|
21
|
+
when "stopping" then "badge-warning"
|
|
22
|
+
when "stopped" then "badge-error"
|
|
23
|
+
when "terminated" then "badge-error"
|
|
24
|
+
else
|
|
25
|
+
raise ::Karafka::Errors::UnsupportedCaseError, status
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Takes the lag trend and gives it appropriate background style color for badge
|
|
30
|
+
#
|
|
31
|
+
# @param trend [Numeric] lag trend
|
|
32
|
+
# @return [String] bg classes
|
|
33
|
+
def lag_trend_badge(trend)
|
|
34
|
+
bg = "badge-success" if trend.negative?
|
|
35
|
+
bg ||= "badge-warning" if trend.positive?
|
|
36
|
+
bg ||= "badge-secondary"
|
|
37
|
+
bg
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Renders tags one after another
|
|
41
|
+
#
|
|
42
|
+
# @param tags_array [Array<String>]
|
|
43
|
+
# @return [String] tags badges
|
|
44
|
+
def tags(tags_array)
|
|
45
|
+
tags_array
|
|
46
|
+
.map { |tag| %(<span class="badge badge-info">#{tag}</span>) }
|
|
47
|
+
.join(" ")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Takes a kafka report state and recommends background style color
|
|
51
|
+
# @param state [String] state
|
|
52
|
+
# @return [String] background style
|
|
53
|
+
def kafka_state_badge(state)
|
|
54
|
+
case state
|
|
55
|
+
when "up" then "badge-success"
|
|
56
|
+
when "active" then "badge-success"
|
|
57
|
+
when "steady" then "badge-success"
|
|
58
|
+
else
|
|
59
|
+
"badge-warning"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Web
|
|
5
|
+
module Ui
|
|
6
|
+
module Helpers
|
|
7
|
+
# Generic value, number and text formatting helpers used across the Web UI
|
|
8
|
+
module FormattingHelper
|
|
9
|
+
# Converts object into a string and for objects that would anyhow return their
|
|
10
|
+
# stringified instance value, it replaces it with the class name instead.
|
|
11
|
+
# Useful for deserializers, etc presentation.
|
|
12
|
+
#
|
|
13
|
+
# @param object [Object]
|
|
14
|
+
# @return [String]
|
|
15
|
+
def object_value_to_s(object)
|
|
16
|
+
object.to_s.include?("#<") ? object.class.to_s : object.to_s
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @param mem_kb [Integer] memory used in KB
|
|
20
|
+
# @return [String] formatted memory usage
|
|
21
|
+
def format_memory(mem_kb)
|
|
22
|
+
return "0" if !mem_kb || mem_kb.zero?
|
|
23
|
+
|
|
24
|
+
if mem_kb < 10_240
|
|
25
|
+
"#{number_with_delimiter(mem_kb.round(4))} KB"
|
|
26
|
+
elsif mem_kb < 1_000_000
|
|
27
|
+
"#{number_with_delimiter((mem_kb / 1024.0).to_i)} MB"
|
|
28
|
+
else
|
|
29
|
+
"#{number_with_delimiter((mem_kb / (1024.0 * 1024.0)).round(1))} GB"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Converts number to a more friendly delimiter based version
|
|
34
|
+
# @param number [Numeric]
|
|
35
|
+
# @param delimiter [String] delimiter (comma by default)
|
|
36
|
+
# @return [String] number with delimiter
|
|
37
|
+
def number_with_delimiter(number, delimiter = ",")
|
|
38
|
+
return "" unless number
|
|
39
|
+
|
|
40
|
+
parts = number.to_s.to_str.split(".")
|
|
41
|
+
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}")
|
|
42
|
+
parts.join(".")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Truncates given text if it is too long and wraps it with a title with full text.
|
|
46
|
+
# Can use a middle-based strategy that keeps beginning and ending of a string instead of
|
|
47
|
+
# keeping just the beginning.
|
|
48
|
+
#
|
|
49
|
+
# The `:middle` strategy is useful when we have strings such as really long process names
|
|
50
|
+
# that have important beginning and end but middle can be removed without risk of not
|
|
51
|
+
# allowing user to recognize the content.
|
|
52
|
+
#
|
|
53
|
+
# @param string [String] string we want to truncate
|
|
54
|
+
# @param length [Integer] max length of the final string that we accept before truncating
|
|
55
|
+
# @param omission [String] truncation omission
|
|
56
|
+
# @param strategy [Symbol] `:default` or `:middle` how should we truncate
|
|
57
|
+
# @return [String] HTML span tag with truncated content and full content title
|
|
58
|
+
def truncate(string, length: 50, omission: "...", strategy: :default)
|
|
59
|
+
return string if string.length <= length
|
|
60
|
+
|
|
61
|
+
case strategy
|
|
62
|
+
when :default
|
|
63
|
+
truncated = string[0...(length - omission.length)] + omission
|
|
64
|
+
when :middle
|
|
65
|
+
part_length = (length - omission.length) / 2
|
|
66
|
+
truncated = string[0...part_length] + omission + string[-part_length..]
|
|
67
|
+
else
|
|
68
|
+
raise Karafka::Errors::UnsupportedCaseError, "Unknown strategy: #{strategy}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
%(<span title="#{string}">#{truncated}</span>)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Web
|
|
5
|
+
module Ui
|
|
6
|
+
module Helpers
|
|
7
|
+
# Kafka partition presentation helpers: replica/ISR broker ids, LSO risk state and
|
|
8
|
+
# offset/lag labels
|
|
9
|
+
module PartitionsHelper
|
|
10
|
+
# Renders the assigned replica broker ids for a partition as badges. The leader is
|
|
11
|
+
# emphasized and any replica that is not part of the in-sync set (ISR) is highlighted
|
|
12
|
+
# as a warning, turning the replication view into an at-a-glance health signal.
|
|
13
|
+
#
|
|
14
|
+
# @param partition [Hash, Ui::Models::Partition] partition metadata
|
|
15
|
+
# @param link [Boolean] when true, each broker badge links to its broker details page
|
|
16
|
+
# (Pro-only; OSS has no per-broker view so it renders plain badges)
|
|
17
|
+
# @return [String] replica broker ids as badges with a count subtext
|
|
18
|
+
def partition_replica_brokers(partition, link: false)
|
|
19
|
+
replicas = partition[:replicas]
|
|
20
|
+
|
|
21
|
+
return %(<span class="text-muted">—</span>) if replicas.empty?
|
|
22
|
+
|
|
23
|
+
leader = partition[:leader]
|
|
24
|
+
isrs = partition[:isrs]
|
|
25
|
+
|
|
26
|
+
badges = replicas.map do |broker_id|
|
|
27
|
+
css = if broker_id == leader
|
|
28
|
+
"badge-primary"
|
|
29
|
+
elsif isrs.include?(broker_id)
|
|
30
|
+
"badge-info"
|
|
31
|
+
else
|
|
32
|
+
"badge-warning"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
broker_id_badge(broker_id, css, link: link)
|
|
36
|
+
end.join(" ")
|
|
37
|
+
|
|
38
|
+
"#{badges}#{broker_count_note(replicas.size, "replicas")}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Renders the in-sync (ISR) broker ids for a partition as badges, with the leader
|
|
42
|
+
# emphasized. An empty ISR set (fully under-replicated partition) renders a dash.
|
|
43
|
+
#
|
|
44
|
+
# @param partition [Hash, Ui::Models::Partition] partition metadata
|
|
45
|
+
# @param link [Boolean] when true, each broker badge links to its broker details page
|
|
46
|
+
# (Pro-only; OSS has no per-broker view so it renders plain badges)
|
|
47
|
+
# @return [String] in-sync broker ids as badges with a count subtext
|
|
48
|
+
def partition_in_sync_brokers(partition, link: false)
|
|
49
|
+
isrs = partition[:isrs]
|
|
50
|
+
|
|
51
|
+
return %(<span class="text-muted">—</span>) if isrs.empty?
|
|
52
|
+
|
|
53
|
+
leader = partition[:leader]
|
|
54
|
+
|
|
55
|
+
badges = isrs.map do |broker_id|
|
|
56
|
+
css = (broker_id == leader) ? "badge-primary" : "badge-success"
|
|
57
|
+
|
|
58
|
+
broker_id_badge(broker_id, css, link: link)
|
|
59
|
+
end.join(" ")
|
|
60
|
+
|
|
61
|
+
"#{badges}#{broker_count_note(isrs.size, "in-sync")}"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @param details [::Karafka::Web::Ui::Models::Partition] partition information with
|
|
65
|
+
# lso risk state info
|
|
66
|
+
# @return [String] background classes for row marking
|
|
67
|
+
def lso_risk_state_bg(details)
|
|
68
|
+
case details.lso_risk_state
|
|
69
|
+
when :active
|
|
70
|
+
""
|
|
71
|
+
when :at_risk
|
|
72
|
+
"bg-warning bg-opacity-25"
|
|
73
|
+
when :stopped
|
|
74
|
+
"bg-error bg-opacity-25"
|
|
75
|
+
else
|
|
76
|
+
raise ::Karafka::Errors::UnsupportedCaseError
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @param details [::Karafka::Web::Ui::Models::Partition] partition information with
|
|
81
|
+
# lso risk state info
|
|
82
|
+
# @return [String] background classes for row marking
|
|
83
|
+
def lso_risk_state_badge(details)
|
|
84
|
+
case details.lso_risk_state
|
|
85
|
+
when :active
|
|
86
|
+
""
|
|
87
|
+
when :at_risk
|
|
88
|
+
"badge-warning"
|
|
89
|
+
when :stopped
|
|
90
|
+
"badge-error"
|
|
91
|
+
else
|
|
92
|
+
raise ::Karafka::Errors::UnsupportedCaseError
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# @param lag [Integer] lag
|
|
97
|
+
# @return [String] lag if correct or `N/A` with labeled explanation
|
|
98
|
+
# @see #offset_with_label
|
|
99
|
+
def lag_with_label(lag)
|
|
100
|
+
if lag.negative?
|
|
101
|
+
title = "Not available until first offset commit"
|
|
102
|
+
%(<span class="badge badge-secondary" title="#{title}">N/A</span>)
|
|
103
|
+
else
|
|
104
|
+
lag.to_s
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# @param topic_name [String] name of the topic for explorer path
|
|
109
|
+
# @param partition_id [Integer] partition for the explorer path
|
|
110
|
+
# @param offset [Integer] offset
|
|
111
|
+
# @param explore [Boolean] should we generate (when allowed) a link to message explorer
|
|
112
|
+
# @return [String] offset if correct or `N/A` with labeled explanation for offsets
|
|
113
|
+
# that are less than 0. Offset with less than 0 indicates, that the offset was not
|
|
114
|
+
# yet committed and there is no value we know of
|
|
115
|
+
def offset_with_label(topic_name, partition_id, offset, explore: false)
|
|
116
|
+
if offset.negative?
|
|
117
|
+
title = "Not available until first offset commit"
|
|
118
|
+
%(<span class="badge badge-secondary" title="#{title}">N/A</span>)
|
|
119
|
+
elsif explore
|
|
120
|
+
path = explorer_topics_path(topic_name, partition_id, offset)
|
|
121
|
+
%(<a href="#{path}">#{offset}</a>)
|
|
122
|
+
else
|
|
123
|
+
offset.to_s
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Normalizes the metric value for display. Negative values coming from statistics usually
|
|
128
|
+
# mean, that the value is not (yet) available.
|
|
129
|
+
#
|
|
130
|
+
# @param value [Integer]
|
|
131
|
+
# @return [String] input value if not negative or N/A
|
|
132
|
+
def normalized_metric(value)
|
|
133
|
+
value.negative? ? "N/A" : value.to_s
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
private
|
|
137
|
+
|
|
138
|
+
# Renders the small muted "N replicas" / "N in-sync" summary shown under the broker
|
|
139
|
+
# badges. Kept next to the badge rendering so the count is never duplicated with the
|
|
140
|
+
# numeric fallback used for older karafka-rdkafka.
|
|
141
|
+
#
|
|
142
|
+
# @param count [Integer] number of brokers
|
|
143
|
+
# @param label [String] summary label (e.g. "replicas", "in-sync")
|
|
144
|
+
# @return [String] count subtext html
|
|
145
|
+
def broker_count_note(count, label)
|
|
146
|
+
%(<div><small class="text-muted">#{count} #{label}</small></div>)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Renders a single broker id badge, optionally wrapped in a link to the broker details
|
|
150
|
+
# page. Linking is Pro-only (OSS has no per-broker view), so it is opt-in via `link`.
|
|
151
|
+
#
|
|
152
|
+
# @param broker_id [Integer] broker id
|
|
153
|
+
# @param css [String] badge style class
|
|
154
|
+
# @param link [Boolean] whether to link the badge to the broker details page
|
|
155
|
+
# @return [String] badge html (optionally linked)
|
|
156
|
+
def broker_id_badge(broker_id, css, link:)
|
|
157
|
+
badge = %(<span class="badge #{css}">#{broker_id}</span>)
|
|
158
|
+
|
|
159
|
+
return badge unless link
|
|
160
|
+
|
|
161
|
+
%(<a href="#{root_path("cluster", broker_id)}" title="Broker #{broker_id} details">#{badge}</a>)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Web
|
|
5
|
+
module Ui
|
|
6
|
+
module Helpers
|
|
7
|
+
# Helpers for rendering sortable column links in data tables
|
|
8
|
+
module SortingHelper
|
|
9
|
+
# Default attribute names mapped from the attributes themselves
|
|
10
|
+
# It makes it easier as we do not have to declare those all the time
|
|
11
|
+
SORT_NAMES = {
|
|
12
|
+
id: "ID",
|
|
13
|
+
partition_id: "Partition",
|
|
14
|
+
memory_usage: "RSS",
|
|
15
|
+
started_at: "Started",
|
|
16
|
+
committed_offset: "Committed",
|
|
17
|
+
last_offset: "Last",
|
|
18
|
+
first_offset: "First",
|
|
19
|
+
lo_offset: "Low",
|
|
20
|
+
hi_offset: "High",
|
|
21
|
+
ls_offset: "LSO",
|
|
22
|
+
lag_hybrid: "Lag",
|
|
23
|
+
lag_stored: "Stored",
|
|
24
|
+
stored_offset: "Stored",
|
|
25
|
+
fetch_state: "Fetch",
|
|
26
|
+
poll_state: "Poll",
|
|
27
|
+
lso_risk_state: "LSO"
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
30
|
+
private_constant :SORT_NAMES
|
|
31
|
+
|
|
32
|
+
# @param name [String] link value
|
|
33
|
+
# @param attribute [Symbol, nil] sorting attribute or nil if we provide only symbol name
|
|
34
|
+
# @param rev [Boolean] when set to true, arrows will be in the reverse position. This is
|
|
35
|
+
# used when the description in the link is reverse to data we sort. For example we have
|
|
36
|
+
# order on when processes were started and we display "x hours" ago but we sort on
|
|
37
|
+
# their age, meaning that it looks like it is the other way around. This flag allows
|
|
38
|
+
# us to reverse just he arrow making it look consistent with the presented data order
|
|
39
|
+
# @return [String] html link for sorting with arrow when attribute sort enabled
|
|
40
|
+
def sort_link(name, attribute = nil, rev: false)
|
|
41
|
+
unless attribute
|
|
42
|
+
attribute = name
|
|
43
|
+
|
|
44
|
+
if SORT_NAMES[attribute]
|
|
45
|
+
name = SORT_NAMES[attribute]
|
|
46
|
+
else
|
|
47
|
+
name = attribute.to_s.tr("_", " ").tr("?", "")
|
|
48
|
+
# Always capitalize the name
|
|
49
|
+
name = name.split.map(&:capitalize).join(" ")
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
arrow_both = "⇕"
|
|
54
|
+
arrow_down = "▾"
|
|
55
|
+
arrow_up = "▴"
|
|
56
|
+
|
|
57
|
+
desc = "#{attribute} desc"
|
|
58
|
+
asc = "#{attribute} asc"
|
|
59
|
+
path = current_path(sort: desc)
|
|
60
|
+
full_name = "#{name} #{arrow_both}"
|
|
61
|
+
|
|
62
|
+
if params.current_sort == desc
|
|
63
|
+
path = current_path(sort: asc)
|
|
64
|
+
full_name = "#{name} #{rev ? arrow_up : arrow_down}"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
if params.current_sort == asc
|
|
68
|
+
path = current_path(sort: desc)
|
|
69
|
+
full_name = "#{name} #{rev ? arrow_down : arrow_up}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
"<a class=\"sort\" href=\"#{path}\">#{full_name}</a>"
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|