karafka-web 0.3.0 → 0.4.0
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
- checksums.yaml.gz.sig +0 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +26 -0
- data/Gemfile.lock +8 -8
- data/lib/karafka/web/installer.rb +7 -2
- 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 +41 -30
- data/lib/karafka/web/tracking/consumers/sampler.rb +1 -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/message.rb +3 -3
- 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/_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/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: 741082cd49e03399e910d2119b2bbd5bffd397c0b548005db6dedc3d6e283557
|
4
|
+
data.tar.gz: b5b24528a2e489fa509c70deea9e79011d0945e6a6f1b8e44686acc0ec2ab96b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aba7fd6a0b9e3729219bd477319b6e06b0662e41f7734e0e43940df940f20451a63a7c4ab734475b29a1c615109329f04a4f6e9767c3cf36005b365ca2174b8f
|
7
|
+
data.tar.gz: '02160184e59088650bb6a00c831ef58a880b02a70dc2b2a4ccfceb4e140a0ee4e0b30e27a5e6df1b35e9516db47823464e6b125ed34ee29a742db4506460983c'
|
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,31 @@
|
|
1
1
|
# Karafka Web changelog
|
2
2
|
|
3
|
+
## 0.4.0 (2023-04-07)
|
4
|
+
- [Improvement] Include active jobs and active partitions subscriptions count in the per-process tab navigation.
|
5
|
+
- [Improvement] Include subscription groups names in the per-process subscriptions view.
|
6
|
+
- [Fix] Add missing support for using multiple subscription groups within a single consumer group.
|
7
|
+
- [Fix] Mask SASL credentials in topic routing view (#46)
|
8
|
+
|
9
|
+
### Upgrade notes
|
10
|
+
|
11
|
+
Because of the reporting schema change, it is recommended to:
|
12
|
+
|
13
|
+
- First, deploy **all** the Karafka consumer processes (`karafka server`)
|
14
|
+
- Deploy the Web update to your web server.
|
15
|
+
|
16
|
+
Please note that if you decide to use the updated Web UI with not updated consumers, you may hit a 500 error.
|
17
|
+
|
18
|
+
## 0.3.1 (2023-03-27)
|
19
|
+
- [Fix] Add missing retention policy for states topic.
|
20
|
+
- [Fix] Fix display of compacted messages placeholders for offsets lower than low watermark.
|
21
|
+
- [Fix] Fix invalid pagination per page count.
|
22
|
+
|
23
|
+
### Upgrade notes
|
24
|
+
|
25
|
+
If upgrading from `0.3.0`, nothing.
|
26
|
+
|
27
|
+
If upgrading from lower, please follow `0.3.0` upgrade procedure.
|
28
|
+
|
3
29
|
## 0.3.0 (2023-03-27)
|
4
30
|
- **[Feature]** Support paginating over compacted topics partitions.
|
5
31
|
- [Improvement] Display watermark offsets in the errors view.
|
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.0)
|
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
|
@@ -102,8 +102,13 @@ module Karafka
|
|
102
102
|
consumers_states_topic,
|
103
103
|
1,
|
104
104
|
replication_factor,
|
105
|
-
# We care only about the most recent state, previous are irrelevant
|
106
|
-
|
105
|
+
# We care only about the most recent state, previous are irrelevant. So we can easily
|
106
|
+
# compact after one minute. We do not use this beyond the most recent collective
|
107
|
+
# state, hence it all can easily go away.
|
108
|
+
{
|
109
|
+
'cleanup.policy': 'compact',
|
110
|
+
'retention.ms': 60 * 60 * 1_000
|
111
|
+
}
|
107
112
|
)
|
108
113
|
end
|
109
114
|
|
@@ -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,29 @@ 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
|
87
|
+
def partition_reportable?(pt_id, pt_stats)
|
88
|
+
return false if pt_id == -1
|
78
89
|
|
79
90
|
# Skip until lag info is available
|
80
|
-
return false if
|
91
|
+
return false if pt_stats['consumer_lag'] == -1
|
81
92
|
|
82
93
|
# Collect information only about what we are subscribed to and what we fetch or
|
83
94
|
# work in any way. Stopped means, we no longer work with it
|
84
|
-
return false if
|
95
|
+
return false if pt_stats['fetch_state'] == 'stopped'
|
85
96
|
|
86
97
|
true
|
87
98
|
end
|
88
99
|
|
89
100
|
# Extracts and formats partition relevant metrics
|
90
101
|
#
|
91
|
-
# @param
|
102
|
+
# @param pt_stats [Hash]
|
92
103
|
# @return [Hash] extracted partition metrics
|
93
|
-
def extract_partition_metrics(
|
94
|
-
metrics =
|
104
|
+
def extract_partition_metrics(pt_stats)
|
105
|
+
metrics = pt_stats.slice(
|
95
106
|
'consumer_lag_stored',
|
96
107
|
'consumer_lag_stored_d',
|
97
108
|
'committed_offset',
|
@@ -106,12 +117,12 @@ module Karafka
|
|
106
117
|
metrics
|
107
118
|
end
|
108
119
|
|
109
|
-
# @param
|
120
|
+
# @param cg_id [String]
|
110
121
|
# @param topic_name [String]
|
111
|
-
# @param
|
122
|
+
# @param pt_id [Integer]
|
112
123
|
# @return [String] poll state / is partition paused or not
|
113
|
-
def poll_state(
|
114
|
-
pause_id = [
|
124
|
+
def poll_state(cg_id, topic_name, pt_id)
|
125
|
+
pause_id = [cg_id, topic_name, pt_id].join('-')
|
115
126
|
|
116
127
|
sampler.pauses.include?(pause_id) ? 'paused' : 'active'
|
117
128
|
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
|
@@ -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
|
@@ -56,11 +56,11 @@ module Karafka
|
|
56
56
|
# not previous page leading offset
|
57
57
|
start_offset = high_offset - (per_page * page)
|
58
58
|
|
59
|
-
if start_offset
|
60
|
-
count = per_page
|
59
|
+
if start_offset <= low_offset
|
60
|
+
count = per_page - (low_offset - start_offset)
|
61
61
|
previous_page = page < 2 ? false : page - 1
|
62
62
|
next_page = false
|
63
|
-
start_offset =
|
63
|
+
start_offset = low_offset
|
64
64
|
else
|
65
65
|
previous_page = page < 2 ? false : page - 1
|
66
66
|
next_page = page + 1
|
@@ -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
|
+
%>
|
@@ -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.0
|
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-07 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
|