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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5c3bf9c5a445bd6a5d291412eea7fba31d1de171f0262543cf149c49946af788
4
- data.tar.gz: d4febb4b2b6bf5405213abaa114892b06f6207623c100cbfc4a3037809a0e94e
3
+ metadata.gz: 741082cd49e03399e910d2119b2bbd5bffd397c0b548005db6dedc3d6e283557
4
+ data.tar.gz: b5b24528a2e489fa509c70deea9e79011d0945e6a6f1b8e44686acc0ec2ab96b
5
5
  SHA512:
6
- metadata.gz: 4ba13c91f46ef01927464720f9b0919f7a6bb5d7cc081a30e926a14b40331d375fdbab53b4de6fe3849101b15c3b9eeb70538a5821486ed15e4677a5e6995a3d
7
- data.tar.gz: 6402543fa36aac339bd2d0795a45802d4c3f3f085d4075fed994ea51d889a872cc2f8fe17c04206bf8811c2395a7381d01698e29adba497fec4e5b35c97892c4
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
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.3.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.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
@@ -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
- { 'cleanup.policy': 'compact' }
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 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,29 @@ 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
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 partition_statistics['consumer_lag'] == -1
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 partition_statistics['fetch_state'] == 'stopped'
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 partition_statistics [Hash]
102
+ # @param pt_stats [Hash]
92
103
  # @return [Hash] extracted partition metrics
93
- def extract_partition_metrics(partition_statistics)
94
- metrics = partition_statistics.slice(
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 consumer_group_id [String]
120
+ # @param cg_id [String]
110
121
  # @param topic_name [String]
111
- # @param partition_id [Integer]
122
+ # @param pt_id [Integer]
112
123
  # @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('-')
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.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
@@ -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
@@ -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 < low_offset
60
- count = per_page + start_offset
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 = 0
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.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
+ %>
@@ -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) %>
@@ -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.0'
6
+ VERSION = '0.4.0'
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.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-03-27 00:00:00.000000000 Z
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.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