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.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.ruby-version +1 -1
  4. data/CHANGELOG.md +21 -0
  5. data/Gemfile.lock +8 -8
  6. data/lib/karafka/web/tracking/consumers/contracts/consumer_group.rb +5 -5
  7. data/lib/karafka/web/tracking/consumers/contracts/subscription_group.rb +33 -0
  8. data/lib/karafka/web/tracking/consumers/listeners/statistics.rb +45 -33
  9. data/lib/karafka/web/tracking/consumers/sampler.rb +1 -1
  10. data/lib/karafka/web/ui/helpers/application_helper.rb +9 -1
  11. data/lib/karafka/web/ui/models/consumer_group.rb +6 -5
  12. data/lib/karafka/web/ui/models/health.rb +25 -15
  13. data/lib/karafka/web/ui/models/process.rb +10 -0
  14. data/lib/karafka/web/ui/models/subscription_group.rb +20 -0
  15. data/lib/karafka/web/ui/pro/views/consumers/_consumer.erb +7 -5
  16. data/lib/karafka/web/ui/pro/views/consumers/consumer/_consumer_group.erb +8 -109
  17. data/lib/karafka/web/ui/pro/views/consumers/consumer/_partition.erb +7 -1
  18. data/lib/karafka/web/ui/pro/views/consumers/consumer/_subscription_group.erb +110 -0
  19. data/lib/karafka/web/ui/pro/views/consumers/consumer/_tabs.erb +2 -0
  20. data/lib/karafka/web/ui/pro/views/errors/_detail.erb +1 -1
  21. data/lib/karafka/web/ui/pro/views/explorer/_detail.erb +9 -0
  22. data/lib/karafka/web/ui/pro/views/explorer/_message.erb +1 -1
  23. data/lib/karafka/web/ui/pro/views/health/index.erb +39 -39
  24. data/lib/karafka/web/ui/views/consumers/_consumer.erb +7 -5
  25. data/lib/karafka/web/ui/views/errors/_detail.erb +1 -1
  26. data/lib/karafka/web/ui/views/routing/_detail.erb +1 -1
  27. data/lib/karafka/web/version.rb +1 -1
  28. data/renovate.json +6 -0
  29. data.tar.gz.sig +0 -0
  30. metadata +7 -3
  31. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b6f97a725c2cd9e8458d8b4a32a347e42898e1f5598b3998a460e063fc0b149
4
- data.tar.gz: 4a8762c41fa42f5e9c75380ce098423acd72db1720dbf412fd33cb412efc3855
3
+ metadata.gz: 130c84348624e2b4a25faebece676cf3a4ee23a41cbe8527a97020ede7f95701
4
+ data.tar.gz: 627002c97f8c0c3c684371cf4c4a83fb24a9cf1de9d481358c4d3b9bf3b59675
5
5
  SHA512:
6
- metadata.gz: b1899bee43d1597e59755ce9f148b817e2c69c3b1b76ee7c6058aef2fc4a985f057b50c26bd000cc121c3cf73c694f906a92a85885e2d5337630ed728ba3e1a1
7
- data.tar.gz: aaca8f979aa93075cceb315f25be6ca98dcde650a6302cca3b4d134df7159210996c71010be28043cb673d0ec54a8a54f5c058ec3f83d30a3d343c230de1a4cb
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
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.3.1)
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.2)
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.17.0)
43
- rack (3.0.4.1)
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.65.0)
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.3)
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.4.11)
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.7
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 topics details
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(:topics) { |val| val.is_a?(Hash) }
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
- topic_contract = Topic.new
20
+ subscription_group_contract = SubscriptionGroup.new
21
21
 
22
- data.fetch(:topics).each do |_topic_name, details|
23
- topic_contract.validate!(details)
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
- consumer_group_id = event[:consumer_group_id]
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 |partition_name, partition_statistics|
27
- partition_id = partition_name.to_i
28
+ partitions.each do |pt_name, pt_stats|
29
+ pt_id = pt_name.to_i
28
30
 
29
- next unless partition_reportable?(partition_id, partition_statistics)
31
+ next unless partition_reportable?(pt_id, pt_stats)
30
32
 
31
- metrics = extract_partition_metrics(partition_statistics)
33
+ metrics = extract_partition_metrics(pt_stats)
32
34
 
33
35
  next if metrics.empty?
34
36
 
35
- topics_details = cg_details[:topics]
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][partition_id] = metrics.merge(
43
- id: partition_id,
44
- poll_state: poll_state(consumer_group_id, topic_name, partition_id)
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 consumer_group_id [String]
55
- # @param consumer_group_statistics [Hash]
65
+ # @param sg_id [String]
66
+ # @param sg_stats [Hash]
56
67
  # @return [Hash] consumer group relevant details
57
- def extract_consumer_group_details(consumer_group_id, consumer_group_statistics)
68
+ def extract_sg_details(sg_id, sg_stats)
58
69
  {
59
- id: consumer_group_id,
60
- state: consumer_group_statistics.slice(
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 partition_id [Integer]
73
- # @param partition_statistics [Hash]
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?(partition_id, partition_statistics)
77
- return false if partition_id == -1
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 no longer work with it
84
- return false if partition_statistics['fetch_state'] == 'stopped'
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 partition_statistics [Hash]
103
+ # @param pt_stats [Hash]
92
104
  # @return [Hash] extracted partition metrics
93
- def extract_partition_metrics(partition_statistics)
94
- metrics = partition_statistics.slice(
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 consumer_group_id [String]
121
+ # @param cg_id [String]
110
122
  # @param topic_name [String]
111
- # @param partition_id [Integer]
123
+ # @param pt_id [Integer]
112
124
  # @return [String] poll state / is partition paused or not
113
- def poll_state(consumer_group_id, topic_name, partition_id)
114
- pause_id = [consumer_group_id, topic_name, partition_id].join('-')
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.2'
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<Topic>] Data of topics belonging to this consumer group
11
- def topics
12
- super.values.map do |topic_hash|
13
- Topic.new(topic_hash)
14
- end
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
- processes = Processes.active(state)
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
- processes.each do |process|
18
- process.consumer_groups.each do |details|
19
- name = details.id
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
- stats[name] ||= {}
29
+ private
22
30
 
23
- details.topics.each do |details2|
24
- t_name = details2.name
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
- stats[name][t_name] ||= {}
27
- details2.partitions.each do |partition|
28
- partition_id = partition.id
29
- stats[name][t_name] ||= {}
30
- stats[name][t_name][partition_id] = partition.to_h
31
- stats[name][t_name][partition_id][:process] = process
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.topics.each do |topic| %>
14
- <span class="badge bg-secondary badge-topic" title="Consumer group: <%= consumer_group.id %>">
15
- <%= topic.name %>:
16
- <%= topic.partitions.map(&:id).join(',') %>
17
- </span>
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
- <div class="row mb-4">
2
- <div class="col-sm-12">
3
- <h5 class="mb-4">
4
- <%= consumer_group.id %>
5
- </h5>
6
-
7
- <div class="card-group text-center">
8
- <div class="card">
9
- <div class="card-body d-flex flex-column align-items-center justify-content-center p-2">
10
- State:&nbsp;
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:&nbsp;
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:&nbsp;
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:&nbsp;
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:&nbsp;
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
- <%= partition.committed_offset %>
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:&nbsp;
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:&nbsp;
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:&nbsp;
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:&nbsp;
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:&nbsp;
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>
@@ -5,7 +5,7 @@
5
5
  <%= "#{k}.#{k2}" %>
6
6
  </td>
7
7
  <td>
8
- <% if k2.to_s.include?('ssl') %>
8
+ <% if %w[sasl ssl].any? { |scope| k2.to_s.include?(scope) } %>
9
9
  ***
10
10
  <% elsif k2.to_s == 'tags' %>
11
11
  <%== tags(v2) %>
@@ -9,6 +9,15 @@
9
9
  </td>
10
10
  </tr>
11
11
  <% end %>
12
+ <% elsif v.is_a?(Time) %>
13
+ <tr>
14
+ <td>
15
+ <%= k %>
16
+ </td>
17
+ <td>
18
+ <%== labeled_time(v) %>
19
+ </td>
20
+ </tr>
12
21
  <% else %>
13
22
  <tr>
14
23
  <td>
@@ -13,7 +13,7 @@
13
13
  <%= message.offset %>
14
14
  </td>
15
15
  <td>
16
- <%== relative_time message.timestamp %>
16
+ <%== labeled_time(message.timestamp) %>
17
17
  </td>
18
18
  <td>
19
19
  <%= message.key %>
@@ -13,48 +13,48 @@
13
13
  <% end %>
14
14
 
15
15
  <% @stats.each do |cg_name, details| %>
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/>
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
- <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
- %>
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
- <% end %>
55
- </tbody>
56
- </table>
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.topics.each do |topic| %>
14
- <span class="badge bg-secondary badge-topic" title="Consumer group: <%= consumer_group.id %>">
15
- <%= topic.name %>:
16
- <%= topic.partitions.map(&:id).join(',') %>
17
- </span>
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>
@@ -5,7 +5,7 @@
5
5
  <%= "#{k}.#{k2}" %>
6
6
  </td>
7
7
  <td>
8
- <% if k2.to_s.include?('ssl') %>
8
+ <% if %w[sasl ssl].any? { |scope| k2.to_s.include?(scope) } %>
9
9
  ***
10
10
  <% elsif k2.to_s == 'tags' %>
11
11
  <%== tags(v2) %>
@@ -5,7 +5,7 @@
5
5
  <%= "#{k}.#{k2}" %>
6
6
  </td>
7
7
  <td>
8
- <% if k2.to_s.include?('ssl') %>
8
+ <% if %w[sasl ssl].any? { |scope| k2.to_s.include?(scope) } %>
9
9
  ***
10
10
  <% else %>
11
11
  <%= v2 %>
@@ -3,6 +3,6 @@
3
3
  module Karafka
4
4
  module Web
5
5
  # Current gem version
6
- VERSION = '0.3.1'
6
+ VERSION = '0.4.1'
7
7
  end
8
8
  end
data/renovate.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
+ "extends": [
4
+ "config:base"
5
+ ]
6
+ }
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.3.1
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-03-27 00:00:00.000000000 Z
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.6
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