karafka-web 0.6.3 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (214) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/workflows/ci.yml +13 -4
  4. data/CHANGELOG.md +119 -5
  5. data/Gemfile +1 -0
  6. data/Gemfile.lock +27 -24
  7. data/README.md +2 -0
  8. data/bin/rspecs +6 -0
  9. data/certs/cert_chain.pem +21 -21
  10. data/docker-compose.yml +22 -0
  11. data/karafka-web.gemspec +3 -3
  12. data/lib/karafka/web/app.rb +6 -2
  13. data/lib/karafka/web/cli.rb +51 -47
  14. data/lib/karafka/web/config.rb +33 -9
  15. data/lib/karafka/web/contracts/base.rb +32 -0
  16. data/lib/karafka/web/contracts/config.rb +63 -0
  17. data/lib/karafka/web/deserializer.rb +10 -1
  18. data/lib/karafka/web/errors.rb +29 -7
  19. data/lib/karafka/web/installer.rb +58 -148
  20. data/lib/karafka/web/management/base.rb +34 -0
  21. data/lib/karafka/web/management/clean_boot_file.rb +31 -0
  22. data/lib/karafka/web/management/create_initial_states.rb +101 -0
  23. data/lib/karafka/web/management/create_topics.rb +127 -0
  24. data/lib/karafka/web/management/delete_topics.rb +28 -0
  25. data/lib/karafka/web/management/enable.rb +82 -0
  26. data/lib/karafka/web/management/extend_boot_file.rb +37 -0
  27. data/lib/karafka/web/processing/consumer.rb +73 -17
  28. data/lib/karafka/web/processing/consumers/aggregators/base.rb +56 -0
  29. data/lib/karafka/web/processing/consumers/aggregators/metrics.rb +154 -0
  30. data/lib/karafka/web/processing/consumers/aggregators/state.rb +180 -0
  31. data/lib/karafka/web/processing/consumers/contracts/aggregated_stats.rb +32 -0
  32. data/lib/karafka/web/processing/consumers/contracts/metrics.rb +53 -0
  33. data/lib/karafka/web/processing/consumers/contracts/process.rb +19 -0
  34. data/lib/karafka/web/processing/consumers/contracts/state.rb +49 -0
  35. data/lib/karafka/web/processing/consumers/contracts/topic_stats.rb +21 -0
  36. data/lib/karafka/web/processing/consumers/metrics.rb +29 -0
  37. data/lib/karafka/web/processing/consumers/schema_manager.rb +56 -0
  38. data/lib/karafka/web/processing/consumers/state.rb +6 -9
  39. data/lib/karafka/web/processing/time_series_tracker.rb +130 -0
  40. data/lib/karafka/web/tracking/consumers/contracts/consumer_group.rb +2 -2
  41. data/lib/karafka/web/tracking/consumers/contracts/job.rb +2 -1
  42. data/lib/karafka/web/tracking/consumers/contracts/partition.rb +14 -1
  43. data/lib/karafka/web/tracking/consumers/contracts/report.rb +10 -8
  44. data/lib/karafka/web/tracking/consumers/contracts/subscription_group.rb +2 -2
  45. data/lib/karafka/web/tracking/consumers/contracts/topic.rb +2 -2
  46. data/lib/karafka/web/tracking/consumers/listeners/processing.rb +6 -2
  47. data/lib/karafka/web/tracking/consumers/listeners/statistics.rb +15 -1
  48. data/lib/karafka/web/tracking/consumers/reporter.rb +14 -6
  49. data/lib/karafka/web/tracking/consumers/sampler.rb +80 -39
  50. data/lib/karafka/web/tracking/contracts/error.rb +2 -1
  51. data/lib/karafka/web/ui/app.rb +20 -10
  52. data/lib/karafka/web/ui/base.rb +56 -6
  53. data/lib/karafka/web/ui/controllers/base.rb +28 -0
  54. data/lib/karafka/web/ui/controllers/become_pro.rb +1 -1
  55. data/lib/karafka/web/ui/controllers/cluster.rb +12 -6
  56. data/lib/karafka/web/ui/controllers/consumers.rb +4 -2
  57. data/lib/karafka/web/ui/controllers/dashboard.rb +32 -0
  58. data/lib/karafka/web/ui/controllers/errors.rb +19 -6
  59. data/lib/karafka/web/ui/controllers/jobs.rb +4 -2
  60. data/lib/karafka/web/ui/controllers/requests/params.rb +28 -0
  61. data/lib/karafka/web/ui/controllers/responses/redirect.rb +29 -0
  62. data/lib/karafka/web/ui/helpers/application_helper.rb +57 -14
  63. data/lib/karafka/web/ui/helpers/paths_helper.rb +48 -0
  64. data/lib/karafka/web/ui/lib/hash_proxy.rb +18 -6
  65. data/lib/karafka/web/ui/lib/paginations/base.rb +61 -0
  66. data/lib/karafka/web/ui/lib/paginations/offset_based.rb +96 -0
  67. data/lib/karafka/web/ui/lib/paginations/page_based.rb +70 -0
  68. data/lib/karafka/web/ui/lib/paginations/paginators/arrays.rb +33 -0
  69. data/lib/karafka/web/ui/lib/paginations/paginators/base.rb +23 -0
  70. data/lib/karafka/web/ui/lib/paginations/paginators/partitions.rb +52 -0
  71. data/lib/karafka/web/ui/lib/paginations/paginators/sets.rb +85 -0
  72. data/lib/karafka/web/ui/lib/paginations/watermark_offsets_based.rb +75 -0
  73. data/lib/karafka/web/ui/lib/ttl_cache.rb +82 -0
  74. data/lib/karafka/web/ui/models/cluster_info.rb +59 -0
  75. data/lib/karafka/web/ui/models/consumers_metrics.rb +46 -0
  76. data/lib/karafka/web/ui/models/{state.rb → consumers_state.rb} +6 -2
  77. data/lib/karafka/web/ui/models/health.rb +37 -7
  78. data/lib/karafka/web/ui/models/message.rb +123 -39
  79. data/lib/karafka/web/ui/models/metrics/aggregated.rb +196 -0
  80. data/lib/karafka/web/ui/models/metrics/charts/aggregated.rb +50 -0
  81. data/lib/karafka/web/ui/models/metrics/charts/topics.rb +109 -0
  82. data/lib/karafka/web/ui/models/metrics/topics.rb +101 -0
  83. data/lib/karafka/web/ui/models/partition.rb +27 -0
  84. data/lib/karafka/web/ui/models/process.rb +12 -1
  85. data/lib/karafka/web/ui/models/status.rb +110 -22
  86. data/lib/karafka/web/ui/models/visibility_filter.rb +33 -0
  87. data/lib/karafka/web/ui/pro/app.rb +87 -19
  88. data/lib/karafka/web/ui/pro/controllers/cluster.rb +11 -0
  89. data/lib/karafka/web/ui/pro/controllers/consumers.rb +13 -7
  90. data/lib/karafka/web/ui/pro/controllers/dashboard.rb +54 -0
  91. data/lib/karafka/web/ui/pro/controllers/dlq.rb +1 -2
  92. data/lib/karafka/web/ui/pro/controllers/errors.rb +46 -10
  93. data/lib/karafka/web/ui/pro/controllers/explorer.rb +145 -15
  94. data/lib/karafka/web/ui/pro/controllers/health.rb +10 -2
  95. data/lib/karafka/web/ui/pro/controllers/messages.rb +62 -0
  96. data/lib/karafka/web/ui/pro/controllers/routing.rb +44 -0
  97. data/lib/karafka/web/ui/pro/views/consumers/_breadcrumbs.erb +7 -1
  98. data/lib/karafka/web/ui/pro/views/consumers/_consumer.erb +1 -1
  99. data/lib/karafka/web/ui/pro/views/consumers/_counters.erb +7 -5
  100. data/lib/karafka/web/ui/pro/views/consumers/consumer/_job.erb +3 -3
  101. data/lib/karafka/web/ui/pro/views/consumers/consumer/_metrics.erb +5 -4
  102. data/lib/karafka/web/ui/pro/views/consumers/consumer/_partition.erb +13 -4
  103. data/lib/karafka/web/ui/pro/views/consumers/consumer/_subscription_group.erb +3 -2
  104. data/lib/karafka/web/ui/pro/views/consumers/consumer/_tabs.erb +7 -0
  105. data/lib/karafka/web/ui/pro/views/consumers/details.erb +21 -0
  106. data/lib/karafka/web/ui/pro/views/consumers/index.erb +4 -2
  107. data/lib/karafka/web/ui/pro/views/dashboard/_ranges_selector.erb +39 -0
  108. data/lib/karafka/web/ui/pro/views/dashboard/index.erb +82 -0
  109. data/lib/karafka/web/ui/pro/views/dlq/_topic.erb +1 -1
  110. data/lib/karafka/web/ui/pro/views/errors/_breadcrumbs.erb +8 -6
  111. data/lib/karafka/web/ui/pro/views/errors/_error.erb +2 -2
  112. data/lib/karafka/web/ui/pro/views/errors/_partition_option.erb +1 -1
  113. data/lib/karafka/web/ui/pro/views/errors/_table.erb +21 -0
  114. data/lib/karafka/web/ui/pro/views/errors/_title_with_select.erb +31 -0
  115. data/lib/karafka/web/ui/pro/views/errors/index.erb +9 -56
  116. data/lib/karafka/web/ui/pro/views/errors/partition.erb +17 -0
  117. data/lib/karafka/web/ui/pro/views/errors/show.erb +1 -1
  118. data/lib/karafka/web/ui/pro/views/explorer/_breadcrumbs.erb +6 -4
  119. data/lib/karafka/web/ui/pro/views/explorer/_filtered.erb +16 -0
  120. data/lib/karafka/web/ui/pro/views/explorer/_message.erb +14 -4
  121. data/lib/karafka/web/ui/pro/views/explorer/_no_topics.erb +7 -0
  122. data/lib/karafka/web/ui/pro/views/explorer/_partition_option.erb +3 -3
  123. data/lib/karafka/web/ui/pro/views/explorer/_topic.erb +1 -1
  124. data/lib/karafka/web/ui/pro/views/explorer/index.erb +12 -8
  125. data/lib/karafka/web/ui/pro/views/explorer/messages/_headers.erb +15 -0
  126. data/lib/karafka/web/ui/pro/views/explorer/messages/_key.erb +12 -0
  127. data/lib/karafka/web/ui/pro/views/explorer/partition/_details.erb +35 -0
  128. data/lib/karafka/web/ui/pro/views/explorer/partition/_messages.erb +1 -0
  129. data/lib/karafka/web/ui/pro/views/explorer/partition.erb +6 -4
  130. data/lib/karafka/web/ui/pro/views/explorer/show.erb +48 -5
  131. data/lib/karafka/web/ui/pro/views/explorer/topic/_details.erb +23 -0
  132. data/lib/karafka/web/ui/pro/views/explorer/topic/_empty.erb +3 -0
  133. data/lib/karafka/web/ui/pro/views/explorer/topic/_limited.erb +4 -0
  134. data/lib/karafka/web/ui/pro/views/explorer/topic.erb +51 -0
  135. data/lib/karafka/web/ui/pro/views/health/_breadcrumbs.erb +16 -0
  136. data/lib/karafka/web/ui/pro/views/health/_no_data.erb +9 -0
  137. data/lib/karafka/web/ui/pro/views/health/_partition.erb +17 -15
  138. data/lib/karafka/web/ui/pro/views/health/_partition_offset.erb +40 -0
  139. data/lib/karafka/web/ui/pro/views/health/_tabs.erb +27 -0
  140. data/lib/karafka/web/ui/pro/views/health/offsets.erb +71 -0
  141. data/lib/karafka/web/ui/pro/views/health/overview.erb +68 -0
  142. data/lib/karafka/web/ui/pro/views/jobs/_job.erb +6 -3
  143. data/lib/karafka/web/ui/pro/views/jobs/index.erb +4 -1
  144. data/lib/karafka/web/ui/pro/views/routing/_consumer_group.erb +37 -0
  145. data/lib/karafka/web/ui/pro/views/routing/_detail.erb +25 -0
  146. data/lib/karafka/web/ui/pro/views/routing/_topic.erb +23 -0
  147. data/lib/karafka/web/ui/pro/views/routing/index.erb +10 -0
  148. data/lib/karafka/web/ui/pro/views/routing/show.erb +26 -0
  149. data/lib/karafka/web/ui/pro/views/shared/_navigation.erb +7 -10
  150. data/lib/karafka/web/ui/public/images/logo-gray.svg +28 -0
  151. data/lib/karafka/web/ui/public/javascripts/application.js +30 -0
  152. data/lib/karafka/web/ui/public/javascripts/chart.min.js +14 -0
  153. data/lib/karafka/web/ui/public/javascripts/charts.js +330 -0
  154. data/lib/karafka/web/ui/public/javascripts/datepicker.js +6 -0
  155. data/lib/karafka/web/ui/public/javascripts/live_poll.js +39 -12
  156. data/lib/karafka/web/ui/public/javascripts/offset_datetime.js +74 -0
  157. data/lib/karafka/web/ui/public/javascripts/tabs.js +59 -0
  158. data/lib/karafka/web/ui/public/stylesheets/application.css +11 -0
  159. data/lib/karafka/web/ui/public/stylesheets/datepicker.min.css +12 -0
  160. data/lib/karafka/web/ui/views/cluster/_no_partitions.erb +3 -0
  161. data/lib/karafka/web/ui/views/cluster/_partition.erb +20 -22
  162. data/lib/karafka/web/ui/views/cluster/index.erb +6 -1
  163. data/lib/karafka/web/ui/views/consumers/_consumer.erb +1 -1
  164. data/lib/karafka/web/ui/views/consumers/_counters.erb +6 -4
  165. data/lib/karafka/web/ui/views/consumers/_summary.erb +3 -3
  166. data/lib/karafka/web/ui/views/consumers/index.erb +3 -1
  167. data/lib/karafka/web/ui/views/dashboard/_feature_pro.erb +3 -0
  168. data/lib/karafka/web/ui/views/dashboard/_not_enough_data.erb +15 -0
  169. data/lib/karafka/web/ui/views/dashboard/_ranges_selector.erb +23 -0
  170. data/lib/karafka/web/ui/views/dashboard/index.erb +95 -0
  171. data/lib/karafka/web/ui/views/errors/_detail.erb +12 -0
  172. data/lib/karafka/web/ui/views/errors/_error.erb +2 -2
  173. data/lib/karafka/web/ui/views/errors/show.erb +1 -1
  174. data/lib/karafka/web/ui/views/jobs/index.erb +3 -1
  175. data/lib/karafka/web/ui/views/layout.erb +10 -3
  176. data/lib/karafka/web/ui/views/routing/_consumer_group.erb +8 -6
  177. data/lib/karafka/web/ui/views/routing/_detail.erb +2 -2
  178. data/lib/karafka/web/ui/views/routing/_topic.erb +1 -1
  179. data/lib/karafka/web/ui/views/routing/show.erb +1 -1
  180. data/lib/karafka/web/ui/views/shared/_brand.erb +2 -2
  181. data/lib/karafka/web/ui/views/shared/_chart.erb +14 -0
  182. data/lib/karafka/web/ui/views/shared/_content.erb +2 -2
  183. data/lib/karafka/web/ui/views/shared/_feature_pro.erb +1 -1
  184. data/lib/karafka/web/ui/views/shared/_flashes.erb +9 -0
  185. data/lib/karafka/web/ui/views/shared/_footer.erb +22 -0
  186. data/lib/karafka/web/ui/views/shared/_header.erb +15 -9
  187. data/lib/karafka/web/ui/views/shared/_live_poll.erb +7 -0
  188. data/lib/karafka/web/ui/views/shared/_navigation.erb +5 -8
  189. data/lib/karafka/web/ui/views/shared/_no_paginated_data.erb +9 -0
  190. data/lib/karafka/web/ui/views/shared/_pagination.erb +17 -13
  191. data/lib/karafka/web/ui/views/shared/_tab_nav.erb +7 -0
  192. data/lib/karafka/web/ui/views/shared/exceptions/not_found.erb +34 -32
  193. data/lib/karafka/web/ui/views/shared/exceptions/pro_only.erb +45 -43
  194. data/lib/karafka/web/ui/views/status/failures/_consumers_reports_schema_state.erb +15 -0
  195. data/lib/karafka/web/ui/views/status/failures/_enabled.erb +8 -0
  196. data/lib/karafka/web/ui/views/status/failures/_initial_consumers_metrics.erb +11 -0
  197. data/lib/karafka/web/ui/views/status/failures/{_initial_state.erb → _initial_consumers_state.erb} +3 -3
  198. data/lib/karafka/web/ui/views/status/failures/_partitions.erb +14 -6
  199. data/lib/karafka/web/ui/views/status/info/_components.erb +21 -1
  200. data/lib/karafka/web/ui/views/status/show.erb +62 -5
  201. data/lib/karafka/web/ui/views/status/successes/_enabled.erb +1 -0
  202. data/lib/karafka/web/ui/views/status/warnings/_replication.erb +19 -0
  203. data/lib/karafka/web/version.rb +1 -1
  204. data/lib/karafka/web.rb +11 -0
  205. data.tar.gz.sig +0 -0
  206. metadata +124 -39
  207. metadata.gz.sig +0 -0
  208. data/lib/karafka/web/processing/consumers/aggregator.rb +0 -130
  209. data/lib/karafka/web/tracking/contracts/base.rb +0 -34
  210. data/lib/karafka/web/ui/lib/paginate_array.rb +0 -38
  211. data/lib/karafka/web/ui/pro/views/explorer/_encryption_enabled.erb +0 -18
  212. data/lib/karafka/web/ui/pro/views/explorer/partition/_watermark_offsets.erb +0 -10
  213. data/lib/karafka/web/ui/pro/views/health/index.erb +0 -60
  214. /data/lib/karafka/web/ui/pro/views/explorer/{_detail.erb → messages/_detail.erb} +0 -0
@@ -6,6 +6,33 @@ module Karafka
6
6
  module Models
7
7
  # Single topic partition data representation model
8
8
  class Partition < Lib::HashProxy
9
+ # @return [Symbol] one of three states in which LSO can be in the correlation to given
10
+ # partition in the context of a consumer group.
11
+ #
12
+ # @note States descriptions:
13
+ # - `:active` all good. No hanging transactions, processing is ok
14
+ # - `:at_risk` - there may be hanging transactions but they do not affect processing
15
+ # before being stuck. This means, that the transaction still may be finished
16
+ # without affecting the processing, hence not having any impact.
17
+ # - `:stopped` - we have reached a hanging LSO and we cannot move forward despite more
18
+ # data being available. Unless the hanging transaction is killed or it finishes,
19
+ # we will not move forward.
20
+ def lso_risk_state
21
+ # If last stable is falling behind the high watermark
22
+ if ls_offset < hi_offset
23
+ # But it is changing and moving fast enough, it does not mean it is stuck
24
+ return :active if ls_offset_fd < ::Karafka::Web.config.ui.lso_threshold
25
+
26
+ # If it is stuck but we still have work to do, this is not a tragic situation because
27
+ # maybe it will unstuck before we reach it
28
+ return :at_risk if (committed_offset || 0) < ls_offset
29
+
30
+ # If it is not changing and falling behind high, it is stuck
31
+ :stopped
32
+ else
33
+ :active
34
+ end
35
+ end
9
36
  end
10
37
  end
11
38
  end
@@ -40,7 +40,7 @@ module Karafka
40
40
  .sort_by(&:started_at)
41
41
  end
42
42
 
43
- # @return [Integer] collective lag on this process
43
+ # @return [Integer] collective stored lag on this process
44
44
  def lag_stored
45
45
  consumer_groups
46
46
  .flat_map(&:subscription_groups)
@@ -51,6 +51,17 @@ module Karafka
51
51
  .sum
52
52
  end
53
53
 
54
+ # @return [Integer] collective lag on this process
55
+ def lag
56
+ consumer_groups
57
+ .flat_map(&:subscription_groups)
58
+ .flat_map(&:topics)
59
+ .flat_map(&:partitions)
60
+ .map(&:lag)
61
+ .delete_if(&:negative?)
62
+ .sum
63
+ end
64
+
54
65
  # @return [Integer] number of partitions to which we are currently subscribed
55
66
  def subscribed_partitions_count
56
67
  consumer_groups
@@ -25,6 +25,7 @@ module Karafka
25
25
  when :success then 'successes'
26
26
  when :warning then 'warnings'
27
27
  when :failure then 'failures'
28
+ when :halted then 'failures'
28
29
  else
29
30
  raise ::Karafka::Errors::UnsupportedCaseError, status
30
31
  end
@@ -36,22 +37,40 @@ module Karafka
36
37
  end
37
38
  end
38
39
 
39
- # Initializes the status object and tries to connect to Kafka
40
- def initialize
41
- connect
40
+ # Is karafka-web enabled in the `karafka.rb`
41
+ # Checks if the consumer group for web-ui is injected.
42
+ # It does **not** check if the group is active because this may depend on the
43
+ # configuration details, but for the Web-UI web app to work, the routing needs to be
44
+ # aware of the deserializer, etc
45
+ def enabled
46
+ enabled = ::Karafka::App.routes.map(&:name).include?(
47
+ ::Karafka::Web.config.processing.consumer_group
48
+ )
49
+
50
+ Step.new(
51
+ enabled ? :success : :failure,
52
+ nil
53
+ )
42
54
  end
43
55
 
44
56
  # @return [Status::Step] were we able to connect to Kafka or not and how fast.
45
57
  # Some people try to work with Kafka over the internet with really high latency and this
46
58
  # should be highlighted in the UI as often the connection just becomes unstable
47
59
  def connection
48
- level = if @connection_time < 1_000
49
- :success
50
- elsif @connection_time < 1_000_000
51
- :warning
52
- else
53
- :failure
54
- end
60
+ if enabled.success?
61
+ # Do not connect more than once during the status object lifetime
62
+ @connection_time || connect
63
+
64
+ level = if @connection_time < 1_000
65
+ :success
66
+ elsif @connection_time < 1_000_000
67
+ :warning
68
+ else
69
+ :failure
70
+ end
71
+ else
72
+ level = :halted
73
+ end
55
74
 
56
75
  Step.new(
57
76
  level,
@@ -81,6 +100,7 @@ module Karafka
81
100
  status = :success
82
101
  status = :failure if topics_details[topics_consumers_states][:partitions] != 1
83
102
  status = :failure if topics_details[topics_consumers_reports][:partitions] != 1
103
+ status = :failure if topics_details[topics_consumers_metrics][:partitions] != 1
84
104
  details = topics_details
85
105
  else
86
106
  status = :halted
@@ -93,10 +113,31 @@ module Karafka
93
113
  )
94
114
  end
95
115
 
96
- # @return [Status::Step] Is the initial state present in the setup or not
97
- def initial_state
116
+ # @return [Status::Step] do we have correct replication for given env
117
+ def replication
98
118
  if partitions.success?
99
- @current_state ||= Models::State.current
119
+ status = :success
120
+ # low replication is not an error but just a warning and a potential problem
121
+ # in case of a crash, this is why we do not fail but warn only
122
+ status = :warning if topics_details.values.any? { |det| det[:replication] < 2 }
123
+ # Allow for non-production setups to use replication 1 as it is not that relevant
124
+ status = :success unless Karafka.env.production?
125
+ details = topics_details
126
+ else
127
+ status = :halted
128
+ details = {}
129
+ end
130
+
131
+ Step.new(
132
+ status,
133
+ details
134
+ )
135
+ end
136
+
137
+ # @return [Status::Step] Is the initial consumers state present in Kafka
138
+ def initial_consumers_state
139
+ if replication.success?
140
+ @current_state ||= Models::ConsumersState.current
100
141
  status = @current_state ? :success : :failure
101
142
  else
102
143
  status = :halted
@@ -108,10 +149,25 @@ module Karafka
108
149
  )
109
150
  end
110
151
 
152
+ # @return [Status::Step] Is the initial consumers metrics record present in Kafka
153
+ def initial_consumers_metrics
154
+ if initial_consumers_state.success?
155
+ @current_metrics ||= Models::ConsumersMetrics.current
156
+ status = @current_metrics ? :success : :failure
157
+ else
158
+ status = :halted
159
+ end
160
+
161
+ Step.new(
162
+ status,
163
+ nil
164
+ )
165
+ end
166
+
111
167
  # @return [Status::Step] Is there at least one active karafka server reporting to the
112
168
  # Web UI
113
169
  def live_reporting
114
- if initial_state.success?
170
+ if initial_consumers_metrics.success?
115
171
  @processes ||= Models::Processes.active(@current_state)
116
172
  status = @processes.empty? ? :failure : :success
117
173
  else
@@ -128,7 +184,11 @@ module Karafka
128
184
  # consumed actively.
129
185
  def state_calculation
130
186
  if live_reporting.success?
131
- @subscriptions ||= Models::Health.current(@current_state).values.flat_map(&:keys)
187
+ @subscriptions ||= Models::Health
188
+ .current(@current_state)
189
+ .values.map { |consumer_group| consumer_group[:topics] }
190
+ .flat_map(&:keys)
191
+
132
192
  status = @subscriptions.include?(topics_consumers_reports) ? :success : :failure
133
193
  else
134
194
  status = :halted
@@ -140,11 +200,26 @@ module Karafka
140
200
  )
141
201
  end
142
202
 
203
+ # @return [Status::Step] Are we able to actually digest the consumers reports with the
204
+ # consumer that is consuming them.
205
+ def consumers_reports_schema_state
206
+ status = if state_calculation.success?
207
+ @current_state[:schema_state] == 'compatible' ? :success : :failure
208
+ else
209
+ :halted
210
+ end
211
+
212
+ Step.new(
213
+ status,
214
+ nil
215
+ )
216
+ end
217
+
143
218
  # @return [Status::Step] is Pro enabled with all of its features.
144
219
  # @note It's not an error not to have it but we want to warn, that some of the features
145
220
  # may not work without Pro.
146
221
  def pro_subscription
147
- status = if state_calculation.success?
222
+ status = if consumers_reports_schema_state.success?
148
223
  ::Karafka.pro? ? :success : :warning
149
224
  else
150
225
  :halted
@@ -168,6 +243,11 @@ module Karafka
168
243
  ::Karafka::Web.config.topics.consumers.reports.to_s
169
244
  end
170
245
 
246
+ # @return [String] consumers metrics topic name
247
+ def topics_consumers_metrics
248
+ ::Karafka::Web.config.topics.consumers.metrics.to_s
249
+ end
250
+
171
251
  # @return [String] errors topic name
172
252
  def topics_errors
173
253
  ::Karafka::Web.config.topics.errors
@@ -175,10 +255,13 @@ module Karafka
175
255
 
176
256
  # @return [Hash] hash with topics with which we work details (even if don't exist)
177
257
  def topics_details
258
+ base = { present: false, partitions: 0, replication: 1 }
259
+
178
260
  topics = {
179
- topics_consumers_states => { present: false, partitions: 0 },
180
- topics_consumers_reports => { present: false, partitions: 0 },
181
- topics_errors => { present: false, partitions: 0 }
261
+ topics_consumers_states => base.dup,
262
+ topics_consumers_reports => base.dup,
263
+ topics_consumers_metrics => base.dup,
264
+ topics_errors => base.dup
182
265
  }
183
266
 
184
267
  @cluster_info.topics.each do |topic|
@@ -186,8 +269,11 @@ module Karafka
186
269
 
187
270
  next unless topics.key?(name)
188
271
 
189
- topics[name][:present] = true
190
- topics[name][:partitions] = topic[:partition_count]
272
+ topics[name].merge!(
273
+ present: true,
274
+ partitions: topic[:partition_count],
275
+ replication: topic[:partitions].map { |part| part[:replica_count] }.max
276
+ )
191
277
  end
192
278
 
193
279
  topics
@@ -197,7 +283,9 @@ module Karafka
197
283
  # @note If fails, `connection_time` will be 1_000_000
198
284
  def connect
199
285
  started = Time.now.to_f
200
- @cluster_info = ::Karafka::Admin.cluster_info
286
+ # For status we always need uncached data, otherwise status could cache outdated
287
+ # info
288
+ @cluster_info = Models::ClusterInfo.fetch(cached: false)
201
289
  @connection_time = (Time.now.to_f - started) * 1_000
202
290
  rescue ::Rdkafka::RdkafkaError
203
291
  @connection_time = 1_000_000
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Web
5
+ module Ui
6
+ module Models
7
+ # Allows for a granular control over what parts of messages are being displayed
8
+ # There are scenarios where payload or other parts of messages should not be presented
9
+ # because they may contain sensitive data. This API allows to manage that on a per message
10
+ # basis.
11
+ class VisibilityFilter
12
+ # @param _message [::Karafka::Messages::Message]
13
+ # @return [Boolean] should message key be visible
14
+ def key?(_message)
15
+ true
16
+ end
17
+
18
+ # @param _message [::Karafka::Messages::Message]
19
+ # @return [Boolean] should message headers be visible
20
+ def headers?(_message)
21
+ true
22
+ end
23
+
24
+ # @param message [::Karafka::Messages::Message]
25
+ # @return [Boolean] should message payload be visible
26
+ def payload?(message)
27
+ !message.headers.key?('encryption')
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -32,90 +32,158 @@ module Karafka
32
32
  ]
33
33
 
34
34
  route do |r|
35
- r.root { r.redirect root_path('consumers') }
35
+ r.root { r.redirect root_path('dashboard') }
36
36
 
37
- @current_page = params.current_page
37
+ # Serve current version specific assets to prevent users from fetching old assets
38
+ # after upgrade
39
+ r.on(:assets, Karafka::Web::VERSION) do
40
+ r.public
41
+ end
42
+
43
+ r.get 'dashboard' do
44
+ @breadcrumbs = false
45
+ controller = Controllers::Dashboard.new(params)
46
+ controller.index
47
+ end
38
48
 
39
49
  r.on 'consumers' do
40
50
  controller = Controllers::Consumers.new(params)
41
51
 
42
52
  r.get String, 'jobs' do |process_id|
43
- render_response controller.jobs(process_id)
53
+ controller.jobs(process_id)
44
54
  end
45
55
 
46
56
  r.get String, 'subscriptions' do |process_id|
47
- render_response controller.subscriptions(process_id)
57
+ controller.subscriptions(process_id)
58
+ end
59
+
60
+ r.get String, 'details' do |process_id|
61
+ controller.details(process_id)
48
62
  end
49
63
 
50
64
  r.get do
51
65
  @breadcrumbs = false
52
- render_response controller.index
66
+ controller.index
53
67
  end
54
68
  end
55
69
 
56
70
  r.get 'jobs' do
57
71
  controller = Controllers::Jobs.new(params)
58
- render_response controller.index
72
+ controller.index
59
73
  end
60
74
 
61
75
  r.on 'routing' do
62
76
  controller = Controllers::Routing.new(params)
63
77
 
64
78
  r.get String do |topic_id|
65
- render_response controller.show(topic_id)
79
+ controller.show(topic_id)
66
80
  end
67
81
 
68
82
  r.get do
69
- render_response controller.index
83
+ controller.index
70
84
  end
71
85
  end
72
86
 
73
87
  r.on 'explorer' do
74
88
  controller = Controllers::Explorer.new(params)
75
89
 
90
+ r.get String, Integer, 'recent' do |topic_id, partition_id|
91
+ controller.recent(topic_id, partition_id)
92
+ end
93
+
94
+ r.get String, Integer, Integer, 'surrounding' do |topic_id, partition_id, offset|
95
+ controller.surrounding(topic_id, partition_id, offset)
96
+ end
97
+
98
+ r.get String, 'recent' do |topic_id|
99
+ controller.recent(topic_id, nil)
100
+ end
101
+
102
+ # Jumps to offset matching the expected time
103
+ r.get String, Integer, Time do |topic_id, partition_id, time|
104
+ controller.closest(topic_id, partition_id, time)
105
+ end
106
+
76
107
  r.get String, Integer, Integer do |topic_id, partition_id, offset|
77
- render_response controller.show(topic_id, partition_id, offset)
108
+ # If when viewing given message we get an offset of different message, we should
109
+ # redirect there. This allows us to support pagination with the current engine
110
+ if params.current_offset != -1
111
+ r.redirect explorer_path(topic_id, partition_id, params.current_offset)
112
+ else
113
+ controller.show(topic_id, partition_id, offset)
114
+ end
78
115
  end
79
116
 
80
117
  r.get String, Integer do |topic_id, partition_id|
81
- render_response controller.partition(topic_id, partition_id)
118
+ controller.partition(topic_id, partition_id)
119
+ end
120
+
121
+ r.get String do |topic_id|
122
+ controller.topic(topic_id)
82
123
  end
83
124
 
84
125
  r.get do
85
- render_response controller.index
126
+ controller.index
86
127
  end
87
128
  end
88
129
 
89
- r.get 'health' do
130
+ r.on 'messages' do
131
+ controller = Controllers::Messages.new(params)
132
+
133
+ r.post String, Integer, Integer, 'republish' do |topic_id, partition_id, offset|
134
+ controller.republish(topic_id, partition_id, offset)
135
+ end
136
+ end
137
+
138
+ r.on 'health' do
90
139
  controller = Controllers::Health.new(params)
91
- render_response controller.index
140
+
141
+ r.get 'offsets' do
142
+ controller.offsets
143
+ end
144
+
145
+ r.get 'overview' do
146
+ controller.overview
147
+ end
148
+
149
+ r.get do
150
+ r.redirect root_path('health/overview')
151
+ end
92
152
  end
93
153
 
94
154
  r.get 'cluster' do
95
155
  controller = Controllers::Cluster.new(params)
96
- render_response controller.index
156
+ controller.index
97
157
  end
98
158
 
99
159
  r.on 'errors' do
100
160
  controller = Controllers::Errors.new(params)
101
161
 
162
+ r.get Integer, Integer do |partition_id, offset|
163
+ if params.current_offset != -1
164
+ r.redirect root_path('errors', partition_id, params.current_offset)
165
+ else
166
+ controller.show(partition_id, offset)
167
+ end
168
+ end
169
+
102
170
  r.get Integer do |partition_id|
103
- render_response controller.index(partition_id)
171
+ controller.partition(partition_id)
104
172
  end
105
173
 
106
- r.get Integer, Integer do |partition_id, offset|
107
- render_response controller.show(partition_id, offset)
174
+ r.get do
175
+ controller.index
108
176
  end
109
177
  end
110
178
 
111
179
  r.get 'dlq' do
112
180
  controller = Controllers::Dlq.new(params)
113
- render_response controller.index
181
+ controller.index
114
182
  end
115
183
 
116
184
  r.get 'status' do
117
185
  controller = Controllers::Status.new(params)
118
- render_response controller.show
186
+ controller.show
119
187
  end
120
188
  end
121
189
  end
@@ -1,5 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
3
14
  module Karafka
4
15
  module Web
5
16
  module Ui
@@ -20,29 +20,35 @@ module Karafka
20
20
  class Consumers < Ui::Controllers::Base
21
21
  # Consumers list
22
22
  def index
23
- @current_state = Models::State.current!
23
+ @current_state = Models::ConsumersState.current!
24
24
  @counters = Models::Counters.new(@current_state)
25
- @processes, @next_page = Lib::PaginateArray.new.call(
25
+ @processes, last_page = Lib::Paginations::Paginators::Arrays.call(
26
26
  Models::Processes.active(@current_state),
27
27
  @params.current_page
28
28
  )
29
29
 
30
+ paginate(@params.current_page, !last_page)
31
+
30
32
  respond
31
33
  end
32
34
 
33
35
  # @param process_id [String] id of the process we're interested in
34
- def jobs(process_id)
35
- current_state = Models::State.current!
36
+ def details(process_id)
37
+ current_state = Models::ConsumersState.current!
36
38
  @process = Models::Process.find(current_state, process_id)
37
39
 
38
40
  respond
39
41
  end
40
42
 
41
43
  # @param process_id [String] id of the process we're interested in
42
- def subscriptions(process_id)
43
- current_state = Models::State.current!
44
- @process = Models::Process.find(current_state, process_id)
44
+ def jobs(process_id)
45
+ details(process_id)
46
+ respond
47
+ end
45
48
 
49
+ # @param process_id [String] id of the process we're interested in
50
+ def subscriptions(process_id)
51
+ details(process_id)
46
52
  respond
47
53
  end
48
54
  end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Web
16
+ module Ui
17
+ module Pro
18
+ module Controllers
19
+ # Main Karafka Pro Web-Ui dashboard controller
20
+ class Dashboard < Ui::Controllers::Base
21
+ # View with statistics dashboard details
22
+ def index
23
+ @current_state = Models::ConsumersState.current!
24
+ @counters = Models::Counters.new(@current_state)
25
+
26
+ current_metrics = Models::ConsumersMetrics.current!
27
+
28
+ # Build the charts data using the aggregated metrics
29
+ @aggregated = Models::Metrics::Aggregated.new(
30
+ current_metrics.to_h.fetch(:aggregated)
31
+ )
32
+
33
+ # Build the charts data about topics using the consumers groups metrics
34
+ @topics = Models::Metrics::Topics.new(
35
+ current_metrics.to_h.fetch(:consumer_groups)
36
+ )
37
+
38
+ # Load only historicals for the selected range
39
+ @aggregated_charts = Models::Metrics::Charts::Aggregated.new(
40
+ @aggregated, @params.current_range
41
+ )
42
+
43
+ @topics_charts = Models::Metrics::Charts::Topics.new(
44
+ @topics, @params.current_range
45
+ )
46
+
47
+ respond
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -28,8 +28,7 @@ module Karafka
28
28
  .compact
29
29
  .select(&:itself)
30
30
 
31
- @dlq_topics = Karafka::Admin
32
- .cluster_info
31
+ @dlq_topics = Models::ClusterInfo
33
32
  .topics
34
33
  .select { |topic| dlq_topic_names.include?(topic[:topic_name]) }
35
34
  .sort_by { |topic| topic[:topic_name] }