karafka-web 0.3.1 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +21 -0
- data/Gemfile.lock +8 -8
- data/lib/karafka/web/tracking/consumers/contracts/consumer_group.rb +5 -5
- data/lib/karafka/web/tracking/consumers/contracts/subscription_group.rb +33 -0
- data/lib/karafka/web/tracking/consumers/listeners/statistics.rb +45 -33
- data/lib/karafka/web/tracking/consumers/sampler.rb +1 -1
- data/lib/karafka/web/ui/helpers/application_helper.rb +9 -1
- data/lib/karafka/web/ui/models/consumer_group.rb +6 -5
- data/lib/karafka/web/ui/models/health.rb +25 -15
- data/lib/karafka/web/ui/models/process.rb +10 -0
- data/lib/karafka/web/ui/models/subscription_group.rb +20 -0
- data/lib/karafka/web/ui/pro/views/consumers/_consumer.erb +7 -5
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_consumer_group.erb +8 -109
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_partition.erb +7 -1
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_subscription_group.erb +110 -0
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_tabs.erb +2 -0
- data/lib/karafka/web/ui/pro/views/errors/_detail.erb +1 -1
- data/lib/karafka/web/ui/pro/views/explorer/_detail.erb +9 -0
- data/lib/karafka/web/ui/pro/views/explorer/_message.erb +1 -1
- data/lib/karafka/web/ui/pro/views/health/index.erb +39 -39
- data/lib/karafka/web/ui/views/consumers/_consumer.erb +7 -5
- data/lib/karafka/web/ui/views/errors/_detail.erb +1 -1
- data/lib/karafka/web/ui/views/routing/_detail.erb +1 -1
- data/lib/karafka/web/version.rb +1 -1
- data/renovate.json +6 -0
- data.tar.gz.sig +0 -0
- metadata +7 -3
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 130c84348624e2b4a25faebece676cf3a4ee23a41cbe8527a97020ede7f95701
|
4
|
+
data.tar.gz: 627002c97f8c0c3c684371cf4c4a83fb24a9cf1de9d481358c4d3b9bf3b59675
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4e9195f492ce88ad8e4f03394a80851d61cc23512cbc3fe8ded3af5b5245ef4cb52bb536954be5587011027735c13e5906e7c1f8b8fd1d9ce0d4d687d6712311
|
7
|
+
data.tar.gz: f8354d6bc12b847cfb6b1a633e164ee6cd80b3dc9f9a91103f18b007be0a569a22417d2236b5dafca4938c81d8e65eeafcb7a55a48553c2dacd9df9c7e6ae21f
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.2.
|
1
|
+
3.2.2
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,26 @@
|
|
1
1
|
# Karafka Web changelog
|
2
2
|
|
3
|
+
## 0.4.1 (2023-04-12)
|
4
|
+
- [Improvement] Replace the "x time ago" in the code explorer with an exact date (`2023-04-12 10:16:48.596 +0200 `).
|
5
|
+
- [Improvement] When hovering over a message timestamp, a label with raw numeric timestamp will be presented.
|
6
|
+
- [Improvement] Do not skip reporting on partitions subscribed that never received any messages.
|
7
|
+
- [Fix] Skip reporting data on subscriptions that were revoked and not only stopped by us.
|
8
|
+
|
9
|
+
## 0.4.0 (2023-04-07)
|
10
|
+
- [Improvement] Include active jobs and active partitions subscriptions count in the per-process tab navigation.
|
11
|
+
- [Improvement] Include subscription groups names in the per-process subscriptions view.
|
12
|
+
- [Fix] Add missing support for using multiple subscription groups within a single consumer group.
|
13
|
+
- [Fix] Mask SASL credentials in topic routing view (#46)
|
14
|
+
|
15
|
+
### Upgrade notes
|
16
|
+
|
17
|
+
Because of the reporting schema change, it is recommended to:
|
18
|
+
|
19
|
+
- First, deploy **all** the Karafka consumer processes (`karafka server`)
|
20
|
+
- Deploy the Web update to your web server.
|
21
|
+
|
22
|
+
Please note that if you decide to use the updated Web UI with not updated consumers, you may hit a 500 error.
|
23
|
+
|
3
24
|
## 0.3.1 (2023-03-27)
|
4
25
|
- [Fix] Add missing retention policy for states topic.
|
5
26
|
- [Fix] Fix display of compacted messages placeholders for offsets lower than low watermark.
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
karafka-web (0.
|
4
|
+
karafka-web (0.4.1)
|
5
5
|
erubi (~> 1.4)
|
6
6
|
karafka (>= 2.0.38, < 3.0.0)
|
7
7
|
karafka-core (>= 2.0.12, < 3.0.0)
|
@@ -11,7 +11,7 @@ PATH
|
|
11
11
|
GEM
|
12
12
|
remote: https://rubygems.org/
|
13
13
|
specs:
|
14
|
-
activesupport (7.0.4.
|
14
|
+
activesupport (7.0.4.3)
|
15
15
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
16
16
|
i18n (>= 1.6, < 2)
|
17
17
|
minitest (>= 5.1)
|
@@ -39,13 +39,13 @@ GEM
|
|
39
39
|
mini_portile2 (~> 2.6)
|
40
40
|
rake (> 12)
|
41
41
|
mini_portile2 (2.8.1)
|
42
|
-
minitest (5.
|
43
|
-
rack (3.0.
|
42
|
+
minitest (5.18.0)
|
43
|
+
rack (3.0.7)
|
44
44
|
rackup (0.2.3)
|
45
45
|
rack (>= 3.0.0.beta1)
|
46
46
|
webrick
|
47
47
|
rake (13.0.6)
|
48
|
-
roda (3.
|
48
|
+
roda (3.66.0)
|
49
49
|
rack
|
50
50
|
rspec (3.12.0)
|
51
51
|
rspec-core (~> 3.12.0)
|
@@ -56,7 +56,7 @@ GEM
|
|
56
56
|
rspec-expectations (3.12.2)
|
57
57
|
diff-lcs (>= 1.2.0, < 2.0)
|
58
58
|
rspec-support (~> 3.12.0)
|
59
|
-
rspec-mocks (3.12.
|
59
|
+
rspec-mocks (3.12.5)
|
60
60
|
diff-lcs (>= 1.2.0, < 2.0)
|
61
61
|
rspec-support (~> 3.12.0)
|
62
62
|
rspec-support (3.12.0)
|
@@ -70,7 +70,7 @@ GEM
|
|
70
70
|
tilt (2.1.0)
|
71
71
|
tzinfo (2.0.6)
|
72
72
|
concurrent-ruby (~> 1.0)
|
73
|
-
waterdrop (2.
|
73
|
+
waterdrop (2.5.1)
|
74
74
|
karafka-core (>= 2.0.12, < 3.0.0)
|
75
75
|
zeitwerk (~> 2.3)
|
76
76
|
webrick (1.8.1)
|
@@ -89,4 +89,4 @@ DEPENDENCIES
|
|
89
89
|
simplecov
|
90
90
|
|
91
91
|
BUNDLED WITH
|
92
|
-
2.4.
|
92
|
+
2.4.10
|
@@ -7,20 +7,20 @@ module Karafka
|
|
7
7
|
# Consumer tracking related contracts
|
8
8
|
module Contracts
|
9
9
|
# Expected data for each consumer group
|
10
|
-
# It's mostly about
|
10
|
+
# It's mostly about subscription groups details
|
11
11
|
class ConsumerGroup < BaseContract
|
12
12
|
configure
|
13
13
|
|
14
14
|
required(:id) { |val| val.is_a?(String) && !val.empty? }
|
15
|
-
required(:
|
15
|
+
required(:subscription_groups) { |val| val.is_a?(Hash) }
|
16
16
|
|
17
17
|
virtual do |data, errors|
|
18
18
|
next unless errors.empty?
|
19
19
|
|
20
|
-
|
20
|
+
subscription_group_contract = SubscriptionGroup.new
|
21
21
|
|
22
|
-
data.fetch(:
|
23
|
-
|
22
|
+
data.fetch(:subscription_groups).each do |_subscription_group_name, details|
|
23
|
+
subscription_group_contract.validate!(details)
|
24
24
|
end
|
25
25
|
|
26
26
|
nil
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Web
|
5
|
+
module Tracking
|
6
|
+
module Consumers
|
7
|
+
module Contracts
|
8
|
+
# Expected data for each subscription group
|
9
|
+
# It's mostly about topics details
|
10
|
+
class SubscriptionGroup < BaseContract
|
11
|
+
configure
|
12
|
+
|
13
|
+
required(:id) { |val| val.is_a?(String) && !val.empty? }
|
14
|
+
required(:topics) { |val| val.is_a?(Hash) }
|
15
|
+
required(:state) { |val| val.is_a?(Hash) }
|
16
|
+
|
17
|
+
virtual do |data, errors|
|
18
|
+
next unless errors.empty?
|
19
|
+
|
20
|
+
topic_contract = Topic.new
|
21
|
+
|
22
|
+
data.fetch(:topics).each do |_topic_name, details|
|
23
|
+
topic_contract.validate!(details)
|
24
|
+
end
|
25
|
+
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -14,50 +14,61 @@ module Karafka
|
|
14
14
|
statistics = event[:statistics]
|
15
15
|
topics = statistics.fetch('topics')
|
16
16
|
cgrp = statistics.fetch('cgrp')
|
17
|
-
|
17
|
+
cg_id = event[:consumer_group_id]
|
18
|
+
sg_id = event[:subscription_group_id]
|
19
|
+
sg_details = extract_sg_details(sg_id, cgrp)
|
18
20
|
|
21
|
+
# More than one subscription group from the same consumer group may be reporting
|
22
|
+
# almost the same time. To prevent corruption of partial data, we put everything here
|
23
|
+
# in track as we merge data from multiple subscription groups
|
19
24
|
track do |sampler|
|
20
|
-
cg_details = extract_consumer_group_details(consumer_group_id, cgrp)
|
21
|
-
sampler.consumer_groups[consumer_group_id] = cg_details
|
22
|
-
|
23
25
|
topics.each do |topic_name, topic_values|
|
24
26
|
partitions = topic_values.fetch('partitions')
|
25
27
|
|
26
|
-
partitions.each do |
|
27
|
-
|
28
|
+
partitions.each do |pt_name, pt_stats|
|
29
|
+
pt_id = pt_name.to_i
|
28
30
|
|
29
|
-
next unless partition_reportable?(
|
31
|
+
next unless partition_reportable?(pt_id, pt_stats)
|
30
32
|
|
31
|
-
metrics = extract_partition_metrics(
|
33
|
+
metrics = extract_partition_metrics(pt_stats)
|
32
34
|
|
33
35
|
next if metrics.empty?
|
34
36
|
|
35
|
-
topics_details =
|
37
|
+
topics_details = sg_details[:topics]
|
36
38
|
|
37
39
|
topic_details = topics_details[topic_name] ||= {
|
38
40
|
name: topic_name,
|
39
41
|
partitions: {}
|
40
42
|
}
|
41
43
|
|
42
|
-
topic_details[:partitions][
|
43
|
-
id:
|
44
|
-
|
44
|
+
topic_details[:partitions][pt_id] = metrics.merge(
|
45
|
+
id: pt_id,
|
46
|
+
# Pauses are stored on a consumer group since we do not process same topic
|
47
|
+
# twice in the multiple subscription groups
|
48
|
+
poll_state: poll_state(cg_id, topic_name, pt_id)
|
45
49
|
)
|
46
50
|
end
|
47
51
|
end
|
52
|
+
|
53
|
+
sampler.consumer_groups[cg_id] ||= {
|
54
|
+
id: cg_id,
|
55
|
+
subscription_groups: {}
|
56
|
+
}
|
57
|
+
|
58
|
+
sampler.consumer_groups[cg_id][:subscription_groups][sg_id] = sg_details
|
48
59
|
end
|
49
60
|
end
|
50
61
|
|
51
62
|
private
|
52
63
|
|
53
64
|
# Extracts basic consumer group related details
|
54
|
-
# @param
|
55
|
-
# @param
|
65
|
+
# @param sg_id [String]
|
66
|
+
# @param sg_stats [Hash]
|
56
67
|
# @return [Hash] consumer group relevant details
|
57
|
-
def
|
68
|
+
def extract_sg_details(sg_id, sg_stats)
|
58
69
|
{
|
59
|
-
id:
|
60
|
-
state:
|
70
|
+
id: sg_id,
|
71
|
+
state: sg_stats.slice(
|
61
72
|
'state',
|
62
73
|
'join_state',
|
63
74
|
'stateage',
|
@@ -69,29 +80,30 @@ module Karafka
|
|
69
80
|
}
|
70
81
|
end
|
71
82
|
|
72
|
-
# @param
|
73
|
-
# @param
|
83
|
+
# @param pt_id [Integer]
|
84
|
+
# @param pt_stats [Hash]
|
74
85
|
# @return [Boolean] is this partition relevant to the current process, hence should we
|
75
86
|
# report about it in the context of the process.
|
76
|
-
def partition_reportable?(
|
77
|
-
return false if
|
78
|
-
|
79
|
-
# Skip until lag info is available
|
80
|
-
return false if partition_statistics['consumer_lag'] == -1
|
87
|
+
def partition_reportable?(pt_id, pt_stats)
|
88
|
+
return false if pt_id == -1
|
81
89
|
|
82
90
|
# Collect information only about what we are subscribed to and what we fetch or
|
83
|
-
# work in any way. Stopped means, we
|
84
|
-
return false if
|
91
|
+
# work in any way. Stopped means, we stopped working with it
|
92
|
+
return false if pt_stats['fetch_state'] == 'stopped'
|
93
|
+
|
94
|
+
# Return if we no longer fetch this partition in a particular process. None means
|
95
|
+
# that we no longer have this subscription assigned and we do not fetch
|
96
|
+
return false if pt_stats['fetch_state'] == 'none'
|
85
97
|
|
86
98
|
true
|
87
99
|
end
|
88
100
|
|
89
101
|
# Extracts and formats partition relevant metrics
|
90
102
|
#
|
91
|
-
# @param
|
103
|
+
# @param pt_stats [Hash]
|
92
104
|
# @return [Hash] extracted partition metrics
|
93
|
-
def extract_partition_metrics(
|
94
|
-
metrics =
|
105
|
+
def extract_partition_metrics(pt_stats)
|
106
|
+
metrics = pt_stats.slice(
|
95
107
|
'consumer_lag_stored',
|
96
108
|
'consumer_lag_stored_d',
|
97
109
|
'committed_offset',
|
@@ -106,12 +118,12 @@ module Karafka
|
|
106
118
|
metrics
|
107
119
|
end
|
108
120
|
|
109
|
-
# @param
|
121
|
+
# @param cg_id [String]
|
110
122
|
# @param topic_name [String]
|
111
|
-
# @param
|
123
|
+
# @param pt_id [Integer]
|
112
124
|
# @return [String] poll state / is partition paused or not
|
113
|
-
def poll_state(
|
114
|
-
pause_id = [
|
125
|
+
def poll_state(cg_id, topic_name, pt_id)
|
126
|
+
pause_id = [cg_id, topic_name, pt_id].join('-')
|
115
127
|
|
116
128
|
sampler.pauses.include?(pause_id) ? 'paused' : 'active'
|
117
129
|
end
|
@@ -14,7 +14,7 @@ module Karafka
|
|
14
14
|
# Current schema version
|
15
15
|
# This can be used in the future for detecting incompatible changes and writing
|
16
16
|
# migrations
|
17
|
-
SCHEMA_VERSION = '1.0
|
17
|
+
SCHEMA_VERSION = '1.1.0'
|
18
18
|
|
19
19
|
# 60 seconds window for time tracked window-based metrics
|
20
20
|
TIMES_TTL = 60
|
@@ -125,10 +125,18 @@ module Karafka
|
|
125
125
|
# @param time [Float] UTC time float
|
126
126
|
# @return [String] relative time tag for timeago.js
|
127
127
|
def relative_time(time)
|
128
|
-
stamp = Time.at(time).getutc.iso8601
|
128
|
+
stamp = Time.at(time).getutc.iso8601(3)
|
129
129
|
%(<time class="ltr" dir="ltr" title="#{stamp}" datetime="#{stamp}">#{time}</time>)
|
130
130
|
end
|
131
131
|
|
132
|
+
# @param time [Time] time object we want to present with detailed ms label
|
133
|
+
# @return [String] span tag with raw timestamp as a title and time as a value
|
134
|
+
def labeled_time(time)
|
135
|
+
stamp = (time.to_f * 1000).to_i
|
136
|
+
|
137
|
+
%(<span title="#{stamp}">#{time}</span>)
|
138
|
+
end
|
139
|
+
|
132
140
|
# Returns the view title html code
|
133
141
|
#
|
134
142
|
# @param title [String] page title
|
@@ -7,11 +7,12 @@ module Karafka
|
|
7
7
|
module Models
|
8
8
|
# Representation of data of a Karafka consumer group
|
9
9
|
class ConsumerGroup < Lib::HashProxy
|
10
|
-
# @return [Array<
|
11
|
-
def
|
12
|
-
super
|
13
|
-
|
14
|
-
|
10
|
+
# @return [Array<SubscriptionGroup>] Data of topics belonging to this consumer group
|
11
|
+
def subscription_groups
|
12
|
+
super
|
13
|
+
.values
|
14
|
+
.map { |sg_hash| SubscriptionGroup.new(sg_hash) }
|
15
|
+
.sort_by(&:id)
|
15
16
|
end
|
16
17
|
end
|
17
18
|
end
|
@@ -12,29 +12,39 @@ module Karafka
|
|
12
12
|
def current(state)
|
13
13
|
stats = {}
|
14
14
|
|
15
|
-
|
15
|
+
iterate_partitions(state) do |process, consumer_group, topic, partition|
|
16
|
+
cg_name = consumer_group.id
|
17
|
+
t_name = topic.name
|
18
|
+
pt_id = partition.id
|
16
19
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
+
stats[cg_name] ||= {}
|
21
|
+
stats[cg_name][t_name] ||= {}
|
22
|
+
stats[cg_name][t_name][pt_id] = partition.to_h
|
23
|
+
stats[cg_name][t_name][pt_id][:process] = process
|
24
|
+
end
|
25
|
+
|
26
|
+
stats
|
27
|
+
end
|
20
28
|
|
21
|
-
|
29
|
+
private
|
22
30
|
|
23
|
-
|
24
|
-
|
31
|
+
# Iterates over all partitions, yielding with extra expanded details
|
32
|
+
#
|
33
|
+
# @param state [State]
|
34
|
+
def iterate_partitions(state)
|
35
|
+
processes = Processes.active(state)
|
25
36
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
37
|
+
processes.each do |process|
|
38
|
+
process.consumer_groups.each do |consumer_group|
|
39
|
+
consumer_group.subscription_groups.each do |subscription_group|
|
40
|
+
subscription_group.topics.each do |topic|
|
41
|
+
topic.partitions.each do |partition|
|
42
|
+
yield(process, consumer_group, topic, partition)
|
43
|
+
end
|
32
44
|
end
|
33
45
|
end
|
34
46
|
end
|
35
47
|
end
|
36
|
-
|
37
|
-
stats
|
38
48
|
end
|
39
49
|
end
|
40
50
|
end
|
@@ -43,12 +43,22 @@ module Karafka
|
|
43
43
|
# @return [Integer] collective lag on this process
|
44
44
|
def lag_stored
|
45
45
|
consumer_groups
|
46
|
+
.flat_map(&:subscription_groups)
|
46
47
|
.flat_map(&:topics)
|
47
48
|
.flat_map(&:partitions)
|
48
49
|
.map(&:lag_stored)
|
49
50
|
.delete_if(&:negative?)
|
50
51
|
.sum
|
51
52
|
end
|
53
|
+
|
54
|
+
# @return [Integer] number of partitions to which we are currently subscribed
|
55
|
+
def subscribed_partitions_count
|
56
|
+
consumer_groups
|
57
|
+
.flat_map(&:subscription_groups)
|
58
|
+
.flat_map(&:topics)
|
59
|
+
.flat_map(&:partitions)
|
60
|
+
.count
|
61
|
+
end
|
52
62
|
end
|
53
63
|
end
|
54
64
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Web
|
5
|
+
module Ui
|
6
|
+
# Namespace for models representing pieces of data about Karafka setup
|
7
|
+
module Models
|
8
|
+
# Representation of data of a Karafka subscription group
|
9
|
+
class SubscriptionGroup < Lib::HashProxy
|
10
|
+
# @return [Array<Topic>] Data of topics belonging to this subscription group
|
11
|
+
def topics
|
12
|
+
super.values.map do |topic_hash|
|
13
|
+
Topic.new(topic_hash)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -10,11 +10,13 @@
|
|
10
10
|
|
11
11
|
<p class="mt-0 mb-1">
|
12
12
|
<% process.consumer_groups.each do |consumer_group| %>
|
13
|
-
<% consumer_group.
|
14
|
-
|
15
|
-
<%=
|
16
|
-
|
17
|
-
|
13
|
+
<% consumer_group.subscription_groups.each do |subscription_group| %>
|
14
|
+
<% subscription_group.topics.each do |topic| %>
|
15
|
+
<span class="badge bg-secondary badge-topic" title="Consumer group: <%= consumer_group.id %>">
|
16
|
+
<%= topic.name %>:
|
17
|
+
<%= topic.partitions.map(&:id).join(',') %>
|
18
|
+
</span>
|
19
|
+
<% end %>
|
18
20
|
<% end %>
|
19
21
|
<% end %>
|
20
22
|
</p>
|
@@ -1,109 +1,8 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
<div class="card-body d-flex flex-column align-items-center justify-content-center p-2">
|
10
|
-
State:
|
11
|
-
<span class="badge <%= kafka_state_bg(consumer_group[:state][:state]) %> mt-1 mb-1">
|
12
|
-
<%= consumer_group[:state][:state] %>
|
13
|
-
</span>
|
14
|
-
</div>
|
15
|
-
</div>
|
16
|
-
<div class="card">
|
17
|
-
<div class="card-body d-flex flex-column align-items-center justify-content-center p-2">
|
18
|
-
Join state:
|
19
|
-
<span class="badge <%= kafka_state_bg(consumer_group.join_state) %> mt-1 mb-1">
|
20
|
-
<%= consumer_group.join_state %>
|
21
|
-
</span>
|
22
|
-
</div>
|
23
|
-
</div>
|
24
|
-
<div class="card">
|
25
|
-
<div class="card-body d-flex flex-column align-items-center justify-content-center p-2">
|
26
|
-
State change:
|
27
|
-
<span class="badge bg-secondary mt-1 mb-1">
|
28
|
-
<%==
|
29
|
-
relative_time(
|
30
|
-
Time.at(@process.dispatched_at) - (consumer_group.stateage / 1_000)
|
31
|
-
)
|
32
|
-
%>
|
33
|
-
</span>
|
34
|
-
</div>
|
35
|
-
</div>
|
36
|
-
<div class="card">
|
37
|
-
<div class="card-body d-flex flex-column align-items-center justify-content-center p-2">
|
38
|
-
Last rebalance:
|
39
|
-
<span class="badge bg-secondary mt-1 mb-1">
|
40
|
-
<%==
|
41
|
-
relative_time(
|
42
|
-
Time.at(@process.dispatched_at) - (consumer_group.rebalance_age / 1_000)
|
43
|
-
)
|
44
|
-
%>
|
45
|
-
</span>
|
46
|
-
</div>
|
47
|
-
</div>
|
48
|
-
<div class="card">
|
49
|
-
<div class="card-body d-flex flex-column align-items-center justify-content-center p-2">
|
50
|
-
Rebalance count:
|
51
|
-
<span class="badge bg-secondary mt-1 mb-1">
|
52
|
-
<%= consumer_group.rebalance_cnt %>
|
53
|
-
</span>
|
54
|
-
</div>
|
55
|
-
</div>
|
56
|
-
</div>
|
57
|
-
</div>
|
58
|
-
|
59
|
-
<span class="text-end mt-3">
|
60
|
-
<small>
|
61
|
-
Last rebalance reason: <%= consumer_group.rebalance_reason %>
|
62
|
-
</small>
|
63
|
-
</span>
|
64
|
-
</div>
|
65
|
-
|
66
|
-
<% if consumer_group.topics.empty? %>
|
67
|
-
<div class="row">
|
68
|
-
<div class="col-lg-12">
|
69
|
-
<div class="alert alert-info" role="alert">
|
70
|
-
This process does not consume any messages from any topics of this consumer group.
|
71
|
-
</div>
|
72
|
-
</div>
|
73
|
-
</div>
|
74
|
-
<% else %>
|
75
|
-
<div class="row mb-5">
|
76
|
-
<div class="col-sm-12">
|
77
|
-
<table class="processes bg-white table table-hover table-bordered table-striped mb-0 align-middle">
|
78
|
-
<thead>
|
79
|
-
<tr class="align-middle">
|
80
|
-
<th>Topic</th>
|
81
|
-
<th>Partition</th>
|
82
|
-
<th>Lag stored</th>
|
83
|
-
<th>Lag trend</th>
|
84
|
-
<th>Committed offset</th>
|
85
|
-
<th>Stored offset</th>
|
86
|
-
<th>Fetch state</th>
|
87
|
-
<th>Poll state</th>
|
88
|
-
</tr>
|
89
|
-
</thead>
|
90
|
-
<tbody>
|
91
|
-
<% consumer_group.topics.each do |topic| %>
|
92
|
-
<% topic.partitions.each do |partition| %>
|
93
|
-
<%==
|
94
|
-
partial(
|
95
|
-
'consumers/consumer/partition',
|
96
|
-
locals: {
|
97
|
-
topic: topic,
|
98
|
-
partition: partition,
|
99
|
-
consumer_group: consumer_group
|
100
|
-
}
|
101
|
-
)
|
102
|
-
%>
|
103
|
-
<% end %>
|
104
|
-
<% end %>
|
105
|
-
</tbody>
|
106
|
-
</table>
|
107
|
-
</div>
|
108
|
-
</div>
|
109
|
-
<% end %>
|
1
|
+
<%==
|
2
|
+
render_each(
|
3
|
+
consumer_group.subscription_groups,
|
4
|
+
'consumers/consumer/_subscription_group',
|
5
|
+
local: :subscription_group,
|
6
|
+
locals: { consumer_group: consumer_group }
|
7
|
+
)
|
8
|
+
%>
|
@@ -20,7 +20,13 @@
|
|
20
20
|
</span>
|
21
21
|
</td>
|
22
22
|
<td>
|
23
|
-
|
23
|
+
<% if partition.committed_offset.to_i < 0 %>
|
24
|
+
<span class="badge bg-secondary" title="Not available until first offset commit">
|
25
|
+
N/A
|
26
|
+
</span>
|
27
|
+
<% else %>
|
28
|
+
<%= partition.committed_offset %>
|
29
|
+
<% end %>
|
24
30
|
</td>
|
25
31
|
<td>
|
26
32
|
<% if partition.stored_offset.to_i < 0 %>
|
@@ -0,0 +1,110 @@
|
|
1
|
+
<div class="row mb-4">
|
2
|
+
<div class="col-sm-12">
|
3
|
+
<h6 class="mb-4">
|
4
|
+
<%= consumer_group.id %> /
|
5
|
+
<%= subscription_group.id %>
|
6
|
+
</h6>
|
7
|
+
|
8
|
+
<div class="card-group text-center">
|
9
|
+
<div class="card">
|
10
|
+
<div class="card-body d-flex flex-column align-items-center justify-content-center p-2">
|
11
|
+
State:
|
12
|
+
<span class="badge <%= kafka_state_bg(subscription_group[:state][:state]) %> mt-1 mb-1">
|
13
|
+
<%= subscription_group[:state][:state] %>
|
14
|
+
</span>
|
15
|
+
</div>
|
16
|
+
</div>
|
17
|
+
<div class="card">
|
18
|
+
<div class="card-body d-flex flex-column align-items-center justify-content-center p-2">
|
19
|
+
Join state:
|
20
|
+
<span class="badge <%= kafka_state_bg(subscription_group.join_state) %> mt-1 mb-1">
|
21
|
+
<%= subscription_group.join_state %>
|
22
|
+
</span>
|
23
|
+
</div>
|
24
|
+
</div>
|
25
|
+
<div class="card">
|
26
|
+
<div class="card-body d-flex flex-column align-items-center justify-content-center p-2">
|
27
|
+
State change:
|
28
|
+
<span class="badge bg-secondary mt-1 mb-1">
|
29
|
+
<%==
|
30
|
+
relative_time(
|
31
|
+
Time.at(@process.dispatched_at) - (subscription_group.stateage / 1_000)
|
32
|
+
)
|
33
|
+
%>
|
34
|
+
</span>
|
35
|
+
</div>
|
36
|
+
</div>
|
37
|
+
<div class="card">
|
38
|
+
<div class="card-body d-flex flex-column align-items-center justify-content-center p-2">
|
39
|
+
Last rebalance:
|
40
|
+
<span class="badge bg-secondary mt-1 mb-1">
|
41
|
+
<%==
|
42
|
+
relative_time(
|
43
|
+
Time.at(@process.dispatched_at) - (subscription_group.rebalance_age / 1_000)
|
44
|
+
)
|
45
|
+
%>
|
46
|
+
</span>
|
47
|
+
</div>
|
48
|
+
</div>
|
49
|
+
<div class="card">
|
50
|
+
<div class="card-body d-flex flex-column align-items-center justify-content-center p-2">
|
51
|
+
Rebalance count:
|
52
|
+
<span class="badge bg-secondary mt-1 mb-1">
|
53
|
+
<%= subscription_group.rebalance_cnt %>
|
54
|
+
</span>
|
55
|
+
</div>
|
56
|
+
</div>
|
57
|
+
</div>
|
58
|
+
</div>
|
59
|
+
|
60
|
+
<span class="text-end mt-3">
|
61
|
+
<small>
|
62
|
+
Last rebalance reason: <%= subscription_group.rebalance_reason %>
|
63
|
+
</small>
|
64
|
+
</span>
|
65
|
+
</div>
|
66
|
+
|
67
|
+
<% if subscription_group.topics.empty? %>
|
68
|
+
<div class="row">
|
69
|
+
<div class="col-lg-12">
|
70
|
+
<div class="alert alert-info" role="alert">
|
71
|
+
This process does not consume any messages from any topics of this consumer group.
|
72
|
+
</div>
|
73
|
+
</div>
|
74
|
+
</div>
|
75
|
+
<% else %>
|
76
|
+
<div class="row mb-5">
|
77
|
+
<div class="col-sm-12">
|
78
|
+
<table class="processes bg-white table table-hover table-bordered table-striped mb-0 align-middle">
|
79
|
+
<thead>
|
80
|
+
<tr class="align-middle">
|
81
|
+
<th>Topic</th>
|
82
|
+
<th>Partition</th>
|
83
|
+
<th>Lag stored</th>
|
84
|
+
<th>Lag trend</th>
|
85
|
+
<th>Committed offset</th>
|
86
|
+
<th>Stored offset</th>
|
87
|
+
<th>Fetch state</th>
|
88
|
+
<th>Poll state</th>
|
89
|
+
</tr>
|
90
|
+
</thead>
|
91
|
+
<tbody>
|
92
|
+
<% subscription_group.topics.each do |topic| %>
|
93
|
+
<% topic.partitions.each do |partition| %>
|
94
|
+
<%==
|
95
|
+
partial(
|
96
|
+
'consumers/consumer/partition',
|
97
|
+
locals: {
|
98
|
+
topic: topic,
|
99
|
+
partition: partition,
|
100
|
+
subscription_group: subscription_group
|
101
|
+
}
|
102
|
+
)
|
103
|
+
%>
|
104
|
+
<% end %>
|
105
|
+
<% end %>
|
106
|
+
</tbody>
|
107
|
+
</table>
|
108
|
+
</div>
|
109
|
+
</div>
|
110
|
+
<% end %>
|
@@ -6,11 +6,13 @@
|
|
6
6
|
<li class="nav-item">
|
7
7
|
<a class="nav-link <%= nav_class(include: 'subscriptions') %>" href="<%= root_path('consumers', @process.id, 'subscriptions') %>">
|
8
8
|
Active subscriptions
|
9
|
+
(<%= @process.subscribed_partitions_count %>)
|
9
10
|
</a>
|
10
11
|
</li>
|
11
12
|
<li class="nav-item">
|
12
13
|
<a class="nav-link <%= nav_class(include: 'jobs') %>" href="<%= root_path('consumers', @process.id, 'jobs') %>">
|
13
14
|
Running jobs
|
15
|
+
(<%= @process.jobs.count %>)
|
14
16
|
</a>
|
15
17
|
</li>
|
16
18
|
</ul>
|
@@ -13,48 +13,48 @@
|
|
13
13
|
<% end %>
|
14
14
|
|
15
15
|
<% @stats.each do |cg_name, details| %>
|
16
|
-
<div class="container mb-5">
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
<div class="container mb-5">
|
17
|
+
<div class="row mb-3">
|
18
|
+
<div class="col-sm-12">
|
19
|
+
<h4 class="mb-4"><%= cg_name %></h4>
|
20
|
+
<hr/>
|
21
|
+
</div>
|
21
22
|
</div>
|
22
|
-
</div>
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
24
|
+
<div class="row mb-5">
|
25
|
+
<div class="col-sm-12">
|
26
|
+
<table class="processes bg-white table table-hover table-bordered table-striped mb-0 align-middle">
|
27
|
+
<thead>
|
28
|
+
<tr class="align-middle">
|
29
|
+
<th class="align-middle">Topic</th>
|
30
|
+
<th>Partition</th>
|
31
|
+
<th>Lag stored</th>
|
32
|
+
<th>Lag trend</th>
|
33
|
+
<th>Committed offset</th>
|
34
|
+
<th>Stored offset</th>
|
35
|
+
<th>Fetch state</th>
|
36
|
+
<th>Poll state</th>
|
37
|
+
<th>Process name</th>
|
38
|
+
</tr>
|
39
|
+
</thead>
|
40
|
+
<tbody>
|
41
|
+
<% details.sort_by(&:first).each do |topic_name, partitions| %>
|
42
|
+
<% partitions.sort_by(&:first).each do |partition_id, details| %>
|
43
|
+
<%==
|
44
|
+
partial(
|
45
|
+
'health/partition',
|
46
|
+
locals: {
|
47
|
+
topic_name: topic_name,
|
48
|
+
partition_id: partition_id,
|
49
|
+
details: details
|
50
|
+
}
|
51
|
+
)
|
52
|
+
%>
|
53
|
+
<% end %>
|
53
54
|
<% end %>
|
54
|
-
|
55
|
-
</
|
56
|
-
</
|
55
|
+
</tbody>
|
56
|
+
</table>
|
57
|
+
</div>
|
57
58
|
</div>
|
58
59
|
</div>
|
59
|
-
</div>
|
60
60
|
<% end %>
|
@@ -10,11 +10,13 @@
|
|
10
10
|
|
11
11
|
<p class="mt-0 mb-1">
|
12
12
|
<% process.consumer_groups.each do |consumer_group| %>
|
13
|
-
<% consumer_group.
|
14
|
-
|
15
|
-
<%=
|
16
|
-
|
17
|
-
|
13
|
+
<% consumer_group.subscription_groups.each do |subscription_group| %>
|
14
|
+
<% subscription_group.topics.each do |topic| %>
|
15
|
+
<span class="badge bg-secondary badge-topic" title="Consumer group: <%= consumer_group.id %>">
|
16
|
+
<%= topic.name %>:
|
17
|
+
<%= topic.partitions.map(&:id).join(',') %>
|
18
|
+
</span>
|
19
|
+
<% end %>
|
18
20
|
<% end %>
|
19
21
|
<% end %>
|
20
22
|
</p>
|
data/lib/karafka/web/version.rb
CHANGED
data/renovate.json
ADDED
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: karafka-web
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Maciej Mensfeld
|
@@ -35,7 +35,7 @@ cert_chain:
|
|
35
35
|
Qf04B9ceLUaC4fPVEz10FyobjaFoY4i32xRto3XnrzeAgfEe4swLq8bQsR3w/EF3
|
36
36
|
MGU0FeSV2Yj7Xc2x/7BzLK8xQn5l7Yy75iPF+KP3vVmDHnNl
|
37
37
|
-----END CERTIFICATE-----
|
38
|
-
date: 2023-
|
38
|
+
date: 2023-04-12 00:00:00.000000000 Z
|
39
39
|
dependencies:
|
40
40
|
- !ruby/object:Gem::Dependency
|
41
41
|
name: erubi
|
@@ -176,6 +176,7 @@ files:
|
|
176
176
|
- lib/karafka/web/tracking/consumers/contracts/job.rb
|
177
177
|
- lib/karafka/web/tracking/consumers/contracts/partition.rb
|
178
178
|
- lib/karafka/web/tracking/consumers/contracts/report.rb
|
179
|
+
- lib/karafka/web/tracking/consumers/contracts/subscription_group.rb
|
179
180
|
- lib/karafka/web/tracking/consumers/contracts/topic.rb
|
180
181
|
- lib/karafka/web/tracking/consumers/listeners/base.rb
|
181
182
|
- lib/karafka/web/tracking/consumers/listeners/errors.rb
|
@@ -213,6 +214,7 @@ files:
|
|
213
214
|
- lib/karafka/web/ui/models/processes.rb
|
214
215
|
- lib/karafka/web/ui/models/state.rb
|
215
216
|
- lib/karafka/web/ui/models/status.rb
|
217
|
+
- lib/karafka/web/ui/models/subscription_group.rb
|
216
218
|
- lib/karafka/web/ui/models/topic.rb
|
217
219
|
- lib/karafka/web/ui/models/watermark_offsets.rb
|
218
220
|
- lib/karafka/web/ui/pro/app.rb
|
@@ -236,6 +238,7 @@ files:
|
|
236
238
|
- lib/karafka/web/ui/pro/views/consumers/consumer/_no_subscriptions.erb
|
237
239
|
- lib/karafka/web/ui/pro/views/consumers/consumer/_partition.erb
|
238
240
|
- lib/karafka/web/ui/pro/views/consumers/consumer/_stopped.erb
|
241
|
+
- lib/karafka/web/ui/pro/views/consumers/consumer/_subscription_group.erb
|
239
242
|
- lib/karafka/web/ui/pro/views/consumers/consumer/_tabs.erb
|
240
243
|
- lib/karafka/web/ui/pro/views/consumers/index.erb
|
241
244
|
- lib/karafka/web/ui/pro/views/consumers/jobs.erb
|
@@ -337,6 +340,7 @@ files:
|
|
337
340
|
- lib/karafka/web/ui/views/status/show.erb
|
338
341
|
- lib/karafka/web/ui/views/status/warnings/_pro_subscription.erb
|
339
342
|
- lib/karafka/web/version.rb
|
343
|
+
- renovate.json
|
340
344
|
homepage: https://karafka.io
|
341
345
|
licenses:
|
342
346
|
- LGPL-3.0
|
@@ -364,7 +368,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
364
368
|
- !ruby/object:Gem::Version
|
365
369
|
version: '0'
|
366
370
|
requirements: []
|
367
|
-
rubygems_version: 3.4.
|
371
|
+
rubygems_version: 3.4.10
|
368
372
|
signing_key:
|
369
373
|
specification_version: 4
|
370
374
|
summary: Karafka ecosystem Web UI interface
|
metadata.gz.sig
CHANGED
Binary file
|