karafka-web 0.7.10 → 0.8.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (163) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/workflows/ci.yml +18 -5
  4. data/.ruby-version +1 -1
  5. data/CHANGELOG.md +63 -0
  6. data/Gemfile.lock +22 -22
  7. data/docker-compose.yml +3 -1
  8. data/karafka-web.gemspec +2 -2
  9. data/lib/karafka/web/config.rb +16 -3
  10. data/lib/karafka/web/contracts/config.rb +7 -2
  11. data/lib/karafka/web/errors.rb +12 -0
  12. data/lib/karafka/web/inflector.rb +33 -0
  13. data/lib/karafka/web/installer.rb +20 -11
  14. data/lib/karafka/web/management/actions/base.rb +36 -0
  15. data/lib/karafka/web/management/actions/clean_boot_file.rb +33 -0
  16. data/lib/karafka/web/management/actions/create_initial_states.rb +77 -0
  17. data/lib/karafka/web/management/actions/create_topics.rb +139 -0
  18. data/lib/karafka/web/management/actions/delete_topics.rb +30 -0
  19. data/lib/karafka/web/management/actions/enable.rb +117 -0
  20. data/lib/karafka/web/management/actions/extend_boot_file.rb +39 -0
  21. data/lib/karafka/web/management/actions/migrate_states_data.rb +18 -0
  22. data/lib/karafka/web/management/migrations/0_base.rb +58 -0
  23. data/lib/karafka/web/management/migrations/0_set_initial_consumers_metrics.rb +36 -0
  24. data/lib/karafka/web/management/migrations/0_set_initial_consumers_state.rb +43 -0
  25. data/lib/karafka/web/management/migrations/1699543515_fill_missing_received_and_sent_bytes_in_consumers_metrics.rb +26 -0
  26. data/lib/karafka/web/management/migrations/1699543515_fill_missing_received_and_sent_bytes_in_consumers_state.rb +23 -0
  27. data/lib/karafka/web/management/migrations/1700234522_introduce_waiting_in_consumers_metrics.rb +24 -0
  28. data/lib/karafka/web/management/migrations/1700234522_introduce_waiting_in_consumers_state.rb +20 -0
  29. data/lib/karafka/web/management/migrations/1700234522_remove_processing_from_consumers_metrics.rb +24 -0
  30. data/lib/karafka/web/management/migrations/1700234522_remove_processing_from_consumers_state.rb +20 -0
  31. data/lib/karafka/web/management/migrations/1704722380_split_listeners_into_active_and_paused_in_metrics.rb +36 -0
  32. data/lib/karafka/web/management/migrations/1704722380_split_listeners_into_active_and_paused_in_states.rb +32 -0
  33. data/lib/karafka/web/management/migrator.rb +117 -0
  34. data/lib/karafka/web/processing/consumer.rb +39 -38
  35. data/lib/karafka/web/processing/consumers/aggregators/metrics.rb +2 -3
  36. data/lib/karafka/web/processing/consumers/aggregators/state.rb +8 -3
  37. data/lib/karafka/web/processing/consumers/contracts/aggregated_stats.rb +5 -1
  38. data/lib/karafka/web/processing/publisher.rb +59 -0
  39. data/lib/karafka/web/tracking/consumers/contracts/job.rb +3 -2
  40. data/lib/karafka/web/tracking/consumers/contracts/partition.rb +1 -0
  41. data/lib/karafka/web/tracking/consumers/contracts/report.rb +6 -1
  42. data/lib/karafka/web/tracking/consumers/contracts/subscription_group.rb +10 -1
  43. data/lib/karafka/web/tracking/consumers/listeners/connections.rb +49 -0
  44. data/lib/karafka/web/tracking/consumers/listeners/pausing.rb +7 -4
  45. data/lib/karafka/web/tracking/consumers/listeners/processing.rb +78 -70
  46. data/lib/karafka/web/tracking/consumers/listeners/statistics.rb +40 -13
  47. data/lib/karafka/web/tracking/consumers/sampler.rb +82 -25
  48. data/lib/karafka/web/tracking/helpers/ttls/array.rb +72 -0
  49. data/lib/karafka/web/tracking/helpers/ttls/hash.rb +34 -0
  50. data/lib/karafka/web/tracking/helpers/ttls/stats.rb +49 -0
  51. data/lib/karafka/web/tracking/helpers/ttls/windows.rb +32 -0
  52. data/lib/karafka/web/tracking/reporter.rb +1 -0
  53. data/lib/karafka/web/ui/app.rb +22 -4
  54. data/lib/karafka/web/ui/base.rb +18 -2
  55. data/lib/karafka/web/ui/controllers/base.rb +34 -4
  56. data/lib/karafka/web/ui/controllers/become_pro.rb +1 -1
  57. data/lib/karafka/web/ui/controllers/cluster.rb +33 -9
  58. data/lib/karafka/web/ui/controllers/consumers.rb +8 -2
  59. data/lib/karafka/web/ui/controllers/dashboard.rb +2 -2
  60. data/lib/karafka/web/ui/controllers/errors.rb +2 -2
  61. data/lib/karafka/web/ui/controllers/jobs.rb +55 -5
  62. data/lib/karafka/web/ui/controllers/requests/params.rb +5 -0
  63. data/lib/karafka/web/ui/controllers/responses/deny.rb +15 -0
  64. data/lib/karafka/web/ui/controllers/responses/file.rb +23 -0
  65. data/lib/karafka/web/ui/controllers/responses/{data.rb → render.rb} +3 -3
  66. data/lib/karafka/web/ui/controllers/routing.rb +11 -2
  67. data/lib/karafka/web/ui/controllers/status.rb +1 -1
  68. data/lib/karafka/web/ui/helpers/application_helper.rb +70 -0
  69. data/lib/karafka/web/ui/lib/hash_proxy.rb +29 -14
  70. data/lib/karafka/web/ui/lib/sorter.rb +170 -0
  71. data/lib/karafka/web/ui/models/counters.rb +6 -0
  72. data/lib/karafka/web/ui/models/health.rb +23 -2
  73. data/lib/karafka/web/ui/models/jobs.rb +48 -0
  74. data/lib/karafka/web/ui/models/metrics/charts/aggregated.rb +33 -0
  75. data/lib/karafka/web/ui/models/metrics/charts/topics.rb +1 -1
  76. data/lib/karafka/web/ui/models/process.rb +2 -1
  77. data/lib/karafka/web/ui/models/status.rb +23 -7
  78. data/lib/karafka/web/ui/models/topic.rb +3 -1
  79. data/lib/karafka/web/ui/models/visibility_filter.rb +16 -0
  80. data/lib/karafka/web/ui/pro/app.rb +44 -6
  81. data/lib/karafka/web/ui/pro/controllers/cluster.rb +1 -0
  82. data/lib/karafka/web/ui/pro/controllers/consumers.rb +52 -6
  83. data/lib/karafka/web/ui/pro/controllers/dashboard.rb +1 -1
  84. data/lib/karafka/web/ui/pro/controllers/dlq.rb +1 -1
  85. data/lib/karafka/web/ui/pro/controllers/errors.rb +3 -3
  86. data/lib/karafka/web/ui/pro/controllers/explorer.rb +8 -8
  87. data/lib/karafka/web/ui/pro/controllers/health.rb +34 -2
  88. data/lib/karafka/web/ui/pro/controllers/jobs.rb +11 -0
  89. data/lib/karafka/web/ui/pro/controllers/messages.rb +42 -0
  90. data/lib/karafka/web/ui/pro/controllers/routing.rb +11 -2
  91. data/lib/karafka/web/ui/pro/views/consumers/_breadcrumbs.erb +8 -2
  92. data/lib/karafka/web/ui/pro/views/consumers/_consumer.erb +14 -8
  93. data/lib/karafka/web/ui/pro/views/consumers/_counters.erb +8 -6
  94. data/lib/karafka/web/ui/pro/views/consumers/consumer/_job.erb +4 -1
  95. data/lib/karafka/web/ui/pro/views/consumers/consumer/_no_jobs.erb +1 -1
  96. data/lib/karafka/web/ui/pro/views/consumers/consumer/_partition.erb +1 -3
  97. data/lib/karafka/web/ui/pro/views/consumers/consumer/_subscription_group.erb +28 -11
  98. data/lib/karafka/web/ui/pro/views/consumers/consumer/_tabs.erb +10 -3
  99. data/lib/karafka/web/ui/pro/views/consumers/index.erb +3 -3
  100. data/lib/karafka/web/ui/pro/views/consumers/pending_jobs.erb +43 -0
  101. data/lib/karafka/web/ui/pro/views/consumers/{jobs.erb → running_jobs.erb} +11 -10
  102. data/lib/karafka/web/ui/pro/views/dashboard/index.erb +7 -1
  103. data/lib/karafka/web/ui/pro/views/explorer/message/_message_actions.erb +18 -0
  104. data/lib/karafka/web/ui/pro/views/explorer/message/_metadata.erb +43 -0
  105. data/lib/karafka/web/ui/pro/views/explorer/message/_payload.erb +21 -0
  106. data/lib/karafka/web/ui/pro/views/explorer/message/_payload_actions.erb +19 -0
  107. data/lib/karafka/web/ui/pro/views/explorer/show.erb +9 -84
  108. data/lib/karafka/web/ui/pro/views/health/_breadcrumbs.erb +8 -0
  109. data/lib/karafka/web/ui/pro/views/health/_partition.erb +1 -3
  110. data/lib/karafka/web/ui/pro/views/health/_partition_offset.erb +4 -4
  111. data/lib/karafka/web/ui/pro/views/health/_partition_times.erb +32 -0
  112. data/lib/karafka/web/ui/pro/views/health/_tabs.erb +9 -0
  113. data/lib/karafka/web/ui/pro/views/health/changes.erb +66 -0
  114. data/lib/karafka/web/ui/pro/views/health/offsets.erb +14 -14
  115. data/lib/karafka/web/ui/pro/views/health/overview.erb +11 -11
  116. data/lib/karafka/web/ui/pro/views/jobs/_job.erb +1 -1
  117. data/lib/karafka/web/ui/pro/views/jobs/_no_jobs.erb +1 -1
  118. data/lib/karafka/web/ui/pro/views/jobs/pending.erb +39 -0
  119. data/lib/karafka/web/ui/pro/views/jobs/running.erb +39 -0
  120. data/lib/karafka/web/ui/pro/views/routing/_consumer_group.erb +2 -2
  121. data/lib/karafka/web/ui/pro/views/routing/_topic.erb +9 -0
  122. data/lib/karafka/web/ui/pro/views/routing/show.erb +12 -0
  123. data/lib/karafka/web/ui/pro/views/shared/_navigation.erb +1 -1
  124. data/lib/karafka/web/ui/public/javascripts/application.js +10 -0
  125. data/lib/karafka/web/ui/public/stylesheets/application.css +4 -0
  126. data/lib/karafka/web/ui/views/cluster/_breadcrumbs.erb +16 -0
  127. data/lib/karafka/web/ui/views/cluster/_tabs.erb +27 -0
  128. data/lib/karafka/web/ui/views/cluster/brokers.erb +27 -0
  129. data/lib/karafka/web/ui/views/cluster/topics.erb +35 -0
  130. data/lib/karafka/web/ui/views/consumers/_counters.erb +8 -6
  131. data/lib/karafka/web/ui/views/consumers/_summary.erb +2 -2
  132. data/lib/karafka/web/ui/views/consumers/index.erb +3 -3
  133. data/lib/karafka/web/ui/views/dashboard/_ranges_selector.erb +23 -7
  134. data/lib/karafka/web/ui/views/dashboard/index.erb +19 -8
  135. data/lib/karafka/web/ui/views/errors/show.erb +2 -23
  136. data/lib/karafka/web/ui/views/jobs/_breadcrumbs.erb +17 -1
  137. data/lib/karafka/web/ui/views/jobs/_job.erb +1 -1
  138. data/lib/karafka/web/ui/views/jobs/_no_jobs.erb +1 -1
  139. data/lib/karafka/web/ui/views/jobs/_tabs.erb +27 -0
  140. data/lib/karafka/web/ui/views/jobs/{index.erb → pending.erb} +9 -7
  141. data/lib/karafka/web/ui/{pro/views/jobs/index.erb → views/jobs/running.erb} +9 -11
  142. data/lib/karafka/web/ui/views/routing/_consumer_group.erb +14 -12
  143. data/lib/karafka/web/ui/views/shared/_navigation.erb +1 -1
  144. data/lib/karafka/web/ui/views/shared/_pagination.erb +1 -1
  145. data/lib/karafka/web/ui/views/shared/exceptions/not_allowed.erb +37 -0
  146. data/lib/karafka/web/ui/views/status/show.erb +17 -2
  147. data/lib/karafka/web/ui/views/status/warnings/_routing_topics_presence.erb +15 -0
  148. data/lib/karafka/web/version.rb +1 -1
  149. data/lib/karafka/web.rb +6 -2
  150. data.tar.gz.sig +0 -0
  151. metadata +61 -26
  152. metadata.gz.sig +0 -0
  153. data/lib/karafka/web/management/base.rb +0 -34
  154. data/lib/karafka/web/management/clean_boot_file.rb +0 -31
  155. data/lib/karafka/web/management/create_initial_states.rb +0 -101
  156. data/lib/karafka/web/management/create_topics.rb +0 -133
  157. data/lib/karafka/web/management/delete_topics.rb +0 -28
  158. data/lib/karafka/web/management/enable.rb +0 -102
  159. data/lib/karafka/web/management/extend_boot_file.rb +0 -37
  160. data/lib/karafka/web/tracking/ttl_array.rb +0 -59
  161. data/lib/karafka/web/tracking/ttl_hash.rb +0 -16
  162. data/lib/karafka/web/ui/pro/views/dashboard/_ranges_selector.erb +0 -39
  163. data/lib/karafka/web/ui/views/cluster/index.erb +0 -74
@@ -26,11 +26,11 @@ module Karafka
26
26
  .topics
27
27
  .sort_by { |topic| topic[:topic_name] }
28
28
 
29
- unless ::Karafka::Web.config.ui.show_internal_topics
29
+ unless ::Karafka::Web.config.ui.visibility.internal_topics
30
30
  @topics.reject! { |topic| topic[:topic_name].start_with?('__') }
31
31
  end
32
32
 
33
- respond
33
+ render
34
34
  end
35
35
 
36
36
  # Displays aggregated messages from (potentially) all partitions of a topic
@@ -45,7 +45,7 @@ module Karafka
45
45
  # @note We cannot use offset references here because each of the partitions may have
46
46
  # completely different values
47
47
  def topic(topic_id)
48
- @visibility_filter = ::Karafka::Web.config.ui.visibility_filter
48
+ @visibility_filter = ::Karafka::Web.config.ui.visibility.filter
49
49
 
50
50
  @topic_id = topic_id
51
51
  @partitions_count = Models::ClusterInfo.partitions_count(topic_id)
@@ -60,7 +60,7 @@ module Karafka
60
60
 
61
61
  paginate(@params.current_page, next_page)
62
62
 
63
- respond
63
+ render
64
64
  end
65
65
 
66
66
  # Shows messages available in a given partition
@@ -68,7 +68,7 @@ module Karafka
68
68
  # @param topic_id [String]
69
69
  # @param partition_id [Integer]
70
70
  def partition(topic_id, partition_id)
71
- @visibility_filter = ::Karafka::Web.config.ui.visibility_filter
71
+ @visibility_filter = ::Karafka::Web.config.ui.visibility.filter
72
72
  @topic_id = topic_id
73
73
  @partition_id = partition_id
74
74
  @watermark_offsets = Ui::Models::WatermarkOffsets.find(topic_id, partition_id)
@@ -84,7 +84,7 @@ module Karafka
84
84
  @messages.map { |message| message.is_a?(Array) ? message.last : message.offset }
85
85
  )
86
86
 
87
- respond
87
+ render
88
88
  end
89
89
 
90
90
  # Displays given message
@@ -94,7 +94,7 @@ module Karafka
94
94
  # @param offset [Integer] offset of the message we want to display
95
95
  # @param paginate [Boolean] do we want to have pagination
96
96
  def show(topic_id, partition_id, offset, paginate: true)
97
- @visibility_filter = ::Karafka::Web.config.ui.visibility_filter
97
+ @visibility_filter = ::Karafka::Web.config.ui.visibility.filter
98
98
  @topic_id = topic_id
99
99
  @partition_id = partition_id
100
100
  @offset = offset
@@ -116,7 +116,7 @@ module Karafka
116
116
  paginate(offset, watermark_offsets.low, watermark_offsets.high)
117
117
  end
118
118
 
119
- respond
119
+ render
120
120
  end
121
121
 
122
122
  # Displays the most recent message on a topic/partition
@@ -18,12 +18,36 @@ module Karafka
18
18
  module Controllers
19
19
  # Health state controller
20
20
  class Health < Ui::Controllers::Base
21
+ self.sortable_attributes = %w[
22
+ id
23
+ lag_stored
24
+ lag_stored_d
25
+ committed_offset
26
+ committed_offset_fd
27
+ stored_offset
28
+ stored_offset_fd
29
+ hi_offset
30
+ hi_offset_fd
31
+ ls_offset
32
+ ls_offset_fd
33
+ fetch_state
34
+ poll_state
35
+ lso_risk_state
36
+ name
37
+ poll_state_ch
38
+ ].freeze
39
+
21
40
  # Displays the current system state
22
41
  def overview
23
42
  current_state = Models::ConsumersState.current!
24
43
  @stats = Models::Health.current(current_state)
25
44
 
26
- respond
45
+ # Refine only on a per topic basis not to resort higher levels
46
+ @stats.each_value do |cg_details|
47
+ cg_details.each_value { |topic_details| refine(topic_details) }
48
+ end
49
+
50
+ render
27
51
  end
28
52
 
29
53
  # Displays details about offsets and their progression/statuses
@@ -31,7 +55,15 @@ module Karafka
31
55
  # Same data as overview but presented differently
32
56
  overview
33
57
 
34
- respond
58
+ render
59
+ end
60
+
61
+ # Displays information related to time of changes of particular attributes
62
+ def changes
63
+ # Same data as overview but presented differently
64
+ overview
65
+
66
+ render
35
67
  end
36
68
  end
37
69
  end
@@ -18,6 +18,17 @@ module Karafka
18
18
  module Controllers
19
19
  # Displays list of active jobs
20
20
  class Jobs < Ui::Controllers::Jobs
21
+ self.sortable_attributes = %w[
22
+ name
23
+ topic
24
+ consumer
25
+ type
26
+ messages
27
+ first_offset
28
+ last_offset
29
+ committed_offset
30
+ updated_at
31
+ ].freeze
21
32
  end
22
33
  end
23
34
  end
@@ -42,6 +42,43 @@ module Karafka
42
42
  )
43
43
  end
44
44
 
45
+ # Dispatches the message raw payload to the browser as a file
46
+ #
47
+ # @param topic_id [String]
48
+ # @param partition_id [Integer]
49
+ # @param offset [Integer] offset of the message we want to download
50
+ def download(topic_id, partition_id, offset)
51
+ message = Ui::Models::Message.find(topic_id, partition_id, offset)
52
+
53
+ # Check if downloads are allowed
54
+ return deny unless visibility_filter.download?(message)
55
+
56
+ file(
57
+ message.raw_payload,
58
+ "#{topic_id}_#{partition_id}_#{offset}_payload.msg"
59
+ )
60
+ end
61
+
62
+ # Dispatches the message payload first deserialized and then serialized to JSON
63
+ # It differs from the raw payload in cases where raw payload is compressed or binary
64
+ # or contains data that the Web UI user should not see that was altered on the Web UI
65
+ # with the visibility filter.
66
+ #
67
+ # @param topic_id [String]
68
+ # @param partition_id [Integer]
69
+ # @param offset [Integer] offset of the message we want to export
70
+ def export(topic_id, partition_id, offset)
71
+ message = Ui::Models::Message.find(topic_id, partition_id, offset)
72
+
73
+ # Check if exports are allowed
74
+ return deny unless visibility_filter.export?(message)
75
+
76
+ file(
77
+ message.payload.to_json,
78
+ "#{topic_id}_#{partition_id}_#{offset}_payload.json"
79
+ )
80
+ end
81
+
45
82
  private
46
83
 
47
84
  # @param message [Karafka::Messages::Message]
@@ -54,6 +91,11 @@ module Karafka
54
91
  and received offset #{delivery.offset}.
55
92
  MSG
56
93
  end
94
+
95
+ # @return [Object] visibility filter. Either default or user-based
96
+ def visibility_filter
97
+ ::Karafka::Web.config.ui.visibility.filter
98
+ end
57
99
  end
58
100
  end
59
101
  end
@@ -18,13 +18,22 @@ module Karafka
18
18
  module Controllers
19
19
  # Routing details - same as in OSS
20
20
  class Routing < Ui::Controllers::Routing
21
+ self.sortable_attributes = %w[
22
+ name
23
+ active?
24
+ ].freeze
25
+
21
26
  # Routing list
22
27
  def index
23
28
  detect_patterns_routes
24
29
 
25
30
  @routes = Karafka::App.routes
26
31
 
27
- respond
32
+ @routes.each do |consumer_group|
33
+ refine(consumer_group.topics)
34
+ end
35
+
36
+ render
28
37
  end
29
38
 
30
39
  # Given route details
@@ -37,7 +46,7 @@ module Karafka
37
46
 
38
47
  @topic || raise(::Karafka::Web::Errors::Ui::NotFoundError, topic_id)
39
48
 
40
- respond
49
+ render
41
50
  end
42
51
 
43
52
  private
@@ -14,13 +14,19 @@
14
14
  <% if current_path.include?('/jobs') %>
15
15
  <li class="breadcrumb-item">
16
16
  <a href="<%= root_path('consumers', @process.id, 'jobs') %>">
17
- Running jobs
17
+ Jobs
18
+ </a>
19
+ </li>
20
+
21
+ <li class="breadcrumb-item">
22
+ <a href="<%= root_path('consumers', @process.id, 'jobs') %>">
23
+ Running
18
24
  </a>
19
25
  </li>
20
26
  <% elsif current_path.include?('/subscriptions') %>
21
27
  <li class="breadcrumb-item">
22
28
  <a href="<%= root_path('consumers', @process.id, 'subscriptions') %>">
23
- Active subscriptions
29
+ Subscriptions
24
30
  </a>
25
31
  </li>
26
32
  <% else %>
@@ -10,18 +10,24 @@
10
10
 
11
11
  <p class="mt-0 mb-1">
12
12
  <% process.consumer_groups.each do |consumer_group| %>
13
+ <% sg_topics = Hash.new { |h, k| h[k] = [] } %>
14
+
13
15
  <% consumer_group.subscription_groups.each do |subscription_group| %>
14
16
  <% subscription_group.topics.each do |topic| %>
15
- <span class="badge bg-secondary badge-topic" title="Consumer group: <%= consumer_group.id %>">
16
- <%= topic.name %>:
17
- <% if topic.partitions.size > 10 %>
18
- <%= "#{topic.partitions.first(10).map(&:id).join(',')}..." %>
19
- <% else %>
20
- <%= topic.partitions.map(&:id).join(',') %>
21
- <% end %>
22
- </span>
17
+ <% sg_topics[topic.name] << topic.partitions.map(&:id) %>
23
18
  <% end %>
24
19
  <% end %>
20
+
21
+ <% sg_topics.each do |topic_name, partitions| %>
22
+ <span class="badge bg-secondary badge-topic" title="Consumer group: <%= consumer_group.id %>">
23
+ <%= topic_name %>:
24
+ <% if partitions.size > 10 %>
25
+ <%= "#{partitions.sort.first(10).join(',')}..." %>
26
+ <% else %>
27
+ <%= partitions.sort.join(',') %>
28
+ <% end %>
29
+ </span>
30
+ <% end %>
25
31
  <% end %>
26
32
  </p>
27
33
 
@@ -24,18 +24,20 @@
24
24
  <div class="desc">Lag stored</div>
25
25
  </li>
26
26
  <li class="col-sm">
27
- <a href="<%= root_path('jobs') %>">
27
+ <a href="<%= root_path('jobs/running') %>">
28
28
  <div class="count mb-1">
29
29
  <%= number_with_delimiter @counters.busy, ' ' %>
30
30
  </div>
31
- <div class="desc">Busy</div>
31
+ <div class="desc">Running</div>
32
32
  </a>
33
33
  </li>
34
34
  <li class="col-sm">
35
- <div class="count mb-1">
36
- <%= number_with_delimiter @counters.enqueued, ' ' %>
37
- </div>
38
- <div class="desc">Enqueued</div>
35
+ <a href="<%= root_path('jobs/pending') %>">
36
+ <div class="count mb-1">
37
+ <%= number_with_delimiter @counters.pending, ' ' %>
38
+ </div>
39
+ <div class="desc">Pending</div>
40
+ </a>
39
41
  </li>
40
42
  <li class="col-sm">
41
43
  <a href="<%= root_path('errors') %>">
@@ -16,6 +16,9 @@
16
16
  <td>
17
17
  <code>#<%= job.type %></code>
18
18
  </td>
19
+ <td>
20
+ <%= job.messages %>
21
+ </td>
19
22
  <td>
20
23
  <%== offset_with_label job.topic, job.partition, job.first_offset, explore: true %>
21
24
  </td>
@@ -26,6 +29,6 @@
26
29
  <%== offset_with_label job.topic, job.partition, job.committed_offset, explore: true %>
27
30
  </td>
28
31
  <td>
29
- <%== relative_time job.started_at %>
32
+ <%== relative_time job.updated_at %>
30
33
  </td>
31
34
  </tr>
@@ -2,7 +2,7 @@
2
2
  <div class="row">
3
3
  <div class="col-lg-12">
4
4
  <div class="alert alert-info" role="alert">
5
- This process is not running any jobs at the moment.
5
+ This process has no <%= type %> jobs at the moment.
6
6
  </div>
7
7
  </div>
8
8
  </div>
@@ -26,9 +26,7 @@
26
26
  </span>
27
27
  </td>
28
28
  <td>
29
- <span class="badge <%= kafka_state_bg(partition.poll_state) %> mt-1 mb-1">
30
- <%= partition.poll_state %>
31
- </span>
29
+ <%== poll_state_with_change_time_label(partition.poll_state, partition.poll_state_ch) %>
32
30
  </td>
33
31
  <td>
34
32
  <span class="badge bg-success <%= lso_risk_state_bg(partition) %> bg-opacity-100">
@@ -14,6 +14,7 @@
14
14
  </span>
15
15
  </div>
16
16
  </div>
17
+
17
18
  <div class="card">
18
19
  <div class="card-body d-flex flex-column align-items-center justify-content-center p-2">
19
20
  Join state:&nbsp;
@@ -22,30 +23,46 @@
22
23
  </span>
23
24
  </div>
24
25
  </div>
26
+
25
27
  <div class="card">
26
28
  <div class="card-body d-flex flex-column align-items-center justify-content-center p-2">
27
29
  State change:&nbsp;
28
30
  <span class="badge bg-secondary mt-1 mb-1">
29
31
  <%==
30
32
  relative_time(
31
- Time.at(@process.dispatched_at) - (subscription_group.stateage / 1_000)
33
+ Time.at(@process.dispatched_at) - (subscription_group.stateage / 1_000.0)
32
34
  )
33
35
  %>
34
36
  </span>
35
37
  </div>
36
38
  </div>
39
+
40
+ <div class="card">
41
+ <div class="card-body d-flex flex-column align-items-center justify-content-center p-2">
42
+ Last Poll:&nbsp;
43
+ <span class="badge bg-secondary mt-1 mb-1">
44
+ <%==
45
+ relative_time(
46
+ Time.at(@process.dispatched_at) - (subscription_group.poll_age / 1_000.0)
47
+ )
48
+ %>
49
+ </span>
50
+ </div>
51
+ </div>
52
+
37
53
  <div class="card">
38
54
  <div class="card-body d-flex flex-column align-items-center justify-content-center p-2">
39
55
  Last rebalance:&nbsp;
40
56
  <span class="badge bg-secondary mt-1 mb-1">
41
57
  <%==
42
58
  relative_time(
43
- Time.at(@process.dispatched_at) - (subscription_group.rebalance_age / 1_000)
59
+ Time.at(@process.dispatched_at) - (subscription_group.rebalance_age / 1_000.0)
44
60
  )
45
61
  %>
46
62
  </span>
47
63
  </div>
48
64
  </div>
65
+
49
66
  <div class="card">
50
67
  <div class="card-body d-flex flex-column align-items-center justify-content-center p-2">
51
68
  Rebalance count:&nbsp;
@@ -68,7 +85,7 @@
68
85
  <div class="row mb-4">
69
86
  <div class="col-lg-12">
70
87
  <div class="alert alert-info" role="alert">
71
- This process does not consume any messages from any topics of this consumer group.
88
+ This process does not consume any messages from any topics of this subscription group.
72
89
  </div>
73
90
  </div>
74
91
  </div>
@@ -86,14 +103,14 @@
86
103
  </th>
87
104
  <tr class="align-middle">
88
105
  </tr>
89
- <th>Partition</th>
90
- <th>Lag stored</th>
91
- <th>Lag stored trend</th>
92
- <th>Committed offset</th>
93
- <th>Stored offset</th>
94
- <th>Fetch state</th>
95
- <th>Poll state</th>
96
- <th>LSO state</th>
106
+ <th><%== sort_link('Partition', :id) %></th>
107
+ <th><%== sort_link(:lag_stored) %></th>
108
+ <th><%== sort_link('Lag stored trend', :lag_stored_d) %></th>
109
+ <th><%== sort_link(:committed_offset) %></th>
110
+ <th><%== sort_link(:stored_offset) %></th>
111
+ <th><%== sort_link(:fetch_state) %></th>
112
+ <th><%== sort_link(:poll_state) %></th>
113
+ <th><%== sort_link('LSO state', :lso_risk_state) %></th>
97
114
  </tr>
98
115
  </thead>
99
116
  <tbody>
@@ -5,15 +5,22 @@
5
5
  <ul class="nav nav-tabs">
6
6
  <li class="nav-item">
7
7
  <a class="nav-link <%= nav_class(include: 'subscriptions') %>" href="<%= root_path('consumers', @process.id, 'subscriptions') %>">
8
- Active subscriptions
8
+ Subscriptions
9
9
  (<%= @process.subscribed_partitions_count %>)
10
10
  </a>
11
11
  </li>
12
12
 
13
13
  <li class="nav-item">
14
- <a class="nav-link <%= nav_class(include: 'jobs') %>" href="<%= root_path('consumers', @process.id, 'jobs') %>">
14
+ <a class="nav-link <%= nav_class(include: 'jobs/running') %>" href="<%= root_path('consumers', @process.id, 'jobs', 'running') %>">
15
15
  Running jobs
16
- (<%= @process.jobs.count %>)
16
+ (<%= @process.jobs.running.count %>)
17
+ </a>
18
+ </li>
19
+
20
+ <li class="nav-item">
21
+ <a class="nav-link <%= nav_class(include: 'jobs/pending') %>" href="<%= root_path('consumers', @process.id, 'jobs', 'pending') %>">
22
+ Pending jobs
23
+ (<%= @process.jobs.pending.count %>)
17
24
  </a>
18
25
  </li>
19
26
 
@@ -12,12 +12,12 @@
12
12
  <table class="processes bg-white table table-hover table-bordered table-striped mb-0 align-middle">
13
13
  <thead>
14
14
  <tr class="align-middle">
15
- <th>Name</th>
16
- <th class="col-sm-2">Started</th>
15
+ <th><%== sort_link(:name) %></th>
16
+ <th class="col-sm-2"><%== sort_link('Started', :started_at, rev: true) %></th>
17
17
  <th class="col-sm-1">Memory</th>
18
18
  <th class="col-sm-1">Performance</th>
19
19
  <th class="col-sm-1">Load</th>
20
- <th class="col-sm-1">Lag stored</th>
20
+ <th class="col-sm-1"><%== sort_link(:lag_stored) %></th>
21
21
  </tr>
22
22
  </thead>
23
23
  <tbody>
@@ -0,0 +1,43 @@
1
+ <%== view_title(@process.name) %>
2
+
3
+ <% if @process.status == 'stopped' %>
4
+ <%== partial 'consumers/consumer/stopped' %>
5
+ <% end %>
6
+
7
+ <%== partial 'consumers/consumer/metrics' %>
8
+
9
+ <%== partial 'consumers/consumer/tabs' %>
10
+
11
+ <% if @pending_jobs.empty? %>
12
+ <%== partial 'consumers/consumer/no_jobs', locals: { type: 'pending' } %>
13
+ <% else %>
14
+ <div class="container">
15
+ <div class="row mb-5">
16
+ <div class="col-sm-12">
17
+ <table class="processes bg-white table table-hover table-bordered table-striped mb-0 align-middle">
18
+ <thead>
19
+ <tr class="align-middle">
20
+ <th><%== sort_link(:topic) %></th>
21
+ <th><%== sort_link(:consumer) %></th>
22
+ <th><%== sort_link(:type) %></th>
23
+ <th><%== sort_link(:messages) %></th>
24
+ <th><%== sort_link(:first_offset) %></th>
25
+ <th><%== sort_link(:last_offset) %></th>
26
+ <th><%== sort_link(:committed_offset) %></th>
27
+ <th><%== sort_link('Scheduled at', :updated_at, rev: true) %></th>
28
+ </tr>
29
+ </thead>
30
+ <tbody>
31
+ <%==
32
+ render_each(
33
+ @pending_jobs,
34
+ 'consumers/consumer/_job',
35
+ local: :job
36
+ )
37
+ %>
38
+ </tbody>
39
+ </table>
40
+ </div>
41
+ </div>
42
+ </div>
43
+ <% end %>
@@ -8,8 +8,8 @@
8
8
 
9
9
  <%== partial 'consumers/consumer/tabs' %>
10
10
 
11
- <% if @process.jobs.empty? %>
12
- <%== partial 'consumers/consumer/no_jobs' %>
11
+ <% if @running_jobs.empty? %>
12
+ <%== partial 'consumers/consumer/no_jobs', locals: { type: 'running' } %>
13
13
  <% else %>
14
14
  <div class="container">
15
15
  <div class="row mb-5">
@@ -17,19 +17,20 @@
17
17
  <table class="processes bg-white table table-hover table-bordered table-striped mb-0 align-middle">
18
18
  <thead>
19
19
  <tr class="align-middle">
20
- <th>Topic</th>
21
- <th>Consumer</th>
22
- <th>Type</th>
23
- <th>First offset</th>
24
- <th>Last offset</th>
25
- <th>Committed offset</th>
26
- <th>Started at</th>
20
+ <th><%== sort_link(:topic) %></th>
21
+ <th><%== sort_link(:consumer) %></th>
22
+ <th><%== sort_link(:type) %></th>
23
+ <th><%== sort_link(:messages) %></th>
24
+ <th><%== sort_link(:first_offset) %></th>
25
+ <th><%== sort_link(:last_offset) %></th>
26
+ <th><%== sort_link(:committed_offset) %></th>
27
+ <th><%== sort_link('Started at', :updated_at, rev: true) %></th>
27
28
  </tr>
28
29
  </thead>
29
30
  <tbody>
30
31
  <%==
31
32
  render_each(
32
- @process.jobs,
33
+ @running_jobs,
33
34
  'consumers/consumer/_job',
34
35
  local: :job
35
36
  )
@@ -56,6 +56,7 @@
56
56
  <%== partial 'shared/tab_nav', locals: { title: 'Utilization', id: 'utilization', active: true } %>
57
57
  <%== partial 'shared/tab_nav', locals: { title: 'RSS', id: 'rss' } %>
58
58
  <%== partial 'shared/tab_nav', locals: { title: 'Concurrency', id: 'concurrency' } %>
59
+ <%== partial 'shared/tab_nav', locals: { title: 'Data transfers', id: 'data-transfers' } %>
59
60
  </ul>
60
61
 
61
62
  <div class="tab-content">
@@ -70,9 +71,14 @@
70
71
  </div>
71
72
 
72
73
  <div class="tab-pane show" id="concurrency" role="tabpanel">
73
- <% data = @aggregated_charts.with(:processes, :workers, :listeners) %>
74
+ <% data = @aggregated_charts.with(:processes, :workers, :active_listeners, :standby_listeners) %>
74
75
  <%== partial 'shared/chart', locals: { data: data, id: 'concurrency' } %>
75
76
  </div>
77
+
78
+ <div class="tab-pane show" id="data-transfers" role="tabpanel">
79
+ <% data = @aggregated_charts.data_transfers %>
80
+ <%== partial 'shared/chart', locals: { data: data, id: 'data-transfers', label_type_y: 'memory' } %>
81
+ </div>
76
82
  </div>
77
83
  </div>
78
84
  </div>
@@ -0,0 +1,18 @@
1
+ <%
2
+ republish_path = root_path('messages', @message.topic, @message.partition, @message.offset, 'republish')
3
+ surrounding_path = explorer_path(@message.topic, @message.partition, @message.offset, 'surrounding')
4
+ %>
5
+
6
+ <div class="row mb-0">
7
+ <div class="col-sm-12 text-end">
8
+ <a href="<%= surrounding_path %>" class="btn btn-secondary btn-sm float-end ms-1">
9
+ &#8651;
10
+ Surrounding
11
+ </a>
12
+
13
+ <form action="<%= republish_path %>" method="post" class="confirm-action float-end">
14
+ <%== csrf_tag(republish_path) %>
15
+ <input type="submit" value="&#10227; Republish" class="btn btn-primary btn-sm"/>
16
+ </form>
17
+ </div>
18
+ </div>
@@ -0,0 +1,43 @@
1
+ <div class="row mb-5">
2
+ <div class="col-sm-12">
3
+ <table class="processes bg-white table table-hover table-bordered table-striped mb-0 align-middle">
4
+ <tbody>
5
+ <% @message.metadata.to_h.except(:received_at, :key, :headers).each do |k, v| %>
6
+ <%==
7
+ partial(
8
+ 'explorer/messages/detail',
9
+ locals: {
10
+ k: k,
11
+ v: v
12
+ }
13
+ )
14
+ %>
15
+ <% end %>
16
+
17
+ <%==
18
+ partial(
19
+ 'explorer/messages/detail',
20
+ locals: {
21
+ k: 'bytesize',
22
+ v: format_memory(((@message.raw_payload&.bytesize || 0) / 1024.to_f).round(4))
23
+ }
24
+ )
25
+ %>
26
+
27
+ <%==
28
+ partial(
29
+ 'explorer/messages/key',
30
+ locals: { message: @message }
31
+ )
32
+ %>
33
+
34
+ <%==
35
+ partial(
36
+ 'explorer/messages/headers',
37
+ locals: { message: @message }
38
+ )
39
+ %>
40
+ </tbody>
41
+ </table>
42
+ </div>
43
+ </div>