karafka-web 0.8.2 → 0.9.0.rc2

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.
Files changed (271) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/workflows/ci.yml +5 -16
  4. data/CHANGELOG.md +40 -0
  5. data/Gemfile.lock +21 -23
  6. data/LICENSE +3 -3
  7. data/bin/rspecs +1 -1
  8. data/config/locales/pro_errors.yml +11 -0
  9. data/config/locales/slogans.yml +62 -0
  10. data/docker-compose.yml +1 -1
  11. data/karafka-web.gemspec +4 -2
  12. data/lib/karafka/web/app.rb +1 -1
  13. data/lib/karafka/web/config.rb +23 -3
  14. data/lib/karafka/web/contracts/config.rb +7 -1
  15. data/lib/karafka/web/management/actions/create_topics.rb +21 -0
  16. data/lib/karafka/web/management/actions/delete_topics.rb +1 -0
  17. data/lib/karafka/web/management/actions/enable.rb +12 -6
  18. data/lib/karafka/web/management/migrations/0_base.rb +1 -1
  19. data/lib/karafka/web/pro/commanding/commands/base.rb +33 -0
  20. data/lib/karafka/web/pro/commanding/commands/probe.rb +41 -0
  21. data/lib/karafka/web/pro/commanding/commands/quiet.rb +31 -0
  22. data/lib/karafka/web/pro/commanding/commands/stop.rb +31 -0
  23. data/lib/karafka/web/pro/commanding/config.rb +51 -0
  24. data/lib/karafka/web/pro/commanding/contracts/config.rb +56 -0
  25. data/lib/karafka/web/pro/commanding/dispatcher.rb +93 -0
  26. data/lib/karafka/web/pro/commanding/listener.rb +97 -0
  27. data/lib/karafka/web/pro/commanding/manager.rb +98 -0
  28. data/lib/karafka/web/pro/commanding/matcher.rb +50 -0
  29. data/lib/karafka/web/pro/commanding.rb +40 -0
  30. data/lib/karafka/web/pro/loader.rb +40 -0
  31. data/lib/karafka/web/{ui/pro → pro/ui}/app.rb +103 -22
  32. data/lib/karafka/web/{ui/pro/controllers/cluster.rb → pro/ui/controllers/base_controller.rb} +4 -5
  33. data/lib/karafka/web/pro/ui/controllers/cluster_controller.rb +54 -0
  34. data/lib/karafka/web/pro/ui/controllers/commanding_controller.rb +118 -0
  35. data/lib/karafka/web/pro/ui/controllers/commands_controller.rb +96 -0
  36. data/lib/karafka/web/{ui/pro/controllers/consumers.rb → pro/ui/controllers/consumers_controller.rb} +31 -4
  37. data/lib/karafka/web/{ui/pro/controllers/dashboard.rb → pro/ui/controllers/dashboard_controller.rb} +5 -3
  38. data/lib/karafka/web/pro/ui/controllers/dlq_controller.rb +60 -0
  39. data/lib/karafka/web/{ui/pro/controllers/errors.rb → pro/ui/controllers/errors_controller.rb} +5 -7
  40. data/lib/karafka/web/{ui/pro/controllers/explorer.rb → pro/ui/controllers/explorer_controller.rb} +24 -19
  41. data/lib/karafka/web/{ui/pro/controllers/health.rb → pro/ui/controllers/health_controller.rb} +16 -3
  42. data/lib/karafka/web/{ui/pro/controllers/jobs.rb → pro/ui/controllers/jobs_controller.rb} +4 -4
  43. data/lib/karafka/web/{ui/pro/controllers/messages.rb → pro/ui/controllers/messages_controller.rb} +8 -6
  44. data/lib/karafka/web/{ui/pro/controllers/routing.rb → pro/ui/controllers/routing_controller.rb} +6 -22
  45. data/lib/karafka/web/{ui/pro/controllers/status.rb → pro/ui/controllers/status_controller.rb} +3 -3
  46. data/lib/karafka/web/pro/ui/controllers/topics_controller.rb +99 -0
  47. data/lib/karafka/web/pro/ui/lib/patterns_detector.rb +50 -0
  48. data/lib/karafka/web/pro/ui/views/cluster/_breadcrumbs.erb +29 -0
  49. data/lib/karafka/web/pro/ui/views/cluster/_broker.erb +13 -0
  50. data/lib/karafka/web/pro/ui/views/cluster/_config.erb +13 -0
  51. data/lib/karafka/web/pro/ui/views/cluster/_tabs.erb +27 -0
  52. data/lib/karafka/web/pro/ui/views/cluster/brokers.erb +27 -0
  53. data/lib/karafka/web/pro/ui/views/cluster/index.erb +27 -0
  54. data/lib/karafka/web/pro/ui/views/cluster/show.erb +27 -0
  55. data/lib/karafka/web/pro/ui/views/commands/_backtrace.erb +20 -0
  56. data/lib/karafka/web/pro/ui/views/commands/_breadcrumbs.erb +21 -0
  57. data/lib/karafka/web/pro/ui/views/commands/_command.erb +60 -0
  58. data/lib/karafka/web/pro/ui/views/commands/_command_details.erb +11 -0
  59. data/lib/karafka/web/pro/ui/views/commands/_details.erb +26 -0
  60. data/lib/karafka/web/pro/ui/views/commands/_empty.erb +3 -0
  61. data/lib/karafka/web/pro/ui/views/commands/_incompatible_schema.erb +14 -0
  62. data/lib/karafka/web/pro/ui/views/commands/_metadata.erb +50 -0
  63. data/lib/karafka/web/pro/ui/views/commands/_table.erb +23 -0
  64. data/lib/karafka/web/pro/ui/views/commands/index.erb +17 -0
  65. data/lib/karafka/web/pro/ui/views/commands/show.erb +38 -0
  66. data/lib/karafka/web/{ui/pro → pro/ui}/views/consumers/_breadcrumbs.erb +20 -4
  67. data/lib/karafka/web/{ui/pro → pro/ui}/views/consumers/_consumer.erb +2 -21
  68. data/lib/karafka/web/pro/ui/views/consumers/_consumer_controls.erb +78 -0
  69. data/lib/karafka/web/pro/ui/views/consumers/_consumer_performance.erb +59 -0
  70. data/lib/karafka/web/{ui/pro → pro/ui}/views/consumers/_counters.erb +7 -5
  71. data/lib/karafka/web/pro/ui/views/consumers/_tabs.erb +35 -0
  72. data/lib/karafka/web/pro/ui/views/consumers/consumer/_commands.erb +32 -0
  73. data/lib/karafka/web/pro/ui/views/consumers/consumer/_no_jobs.erb +7 -0
  74. data/lib/karafka/web/pro/ui/views/consumers/consumer/_no_subscriptions.erb +7 -0
  75. data/lib/karafka/web/{ui/pro → pro/ui}/views/consumers/consumer/_subscription_group.erb +13 -8
  76. data/lib/karafka/web/pro/ui/views/consumers/consumer/_title.erb +5 -0
  77. data/lib/karafka/web/pro/ui/views/consumers/controls.erb +67 -0
  78. data/lib/karafka/web/{ui/pro → pro/ui}/views/consumers/details.erb +6 -1
  79. data/lib/karafka/web/pro/ui/views/consumers/index.erb +39 -0
  80. data/lib/karafka/web/{ui/pro → pro/ui}/views/consumers/pending_jobs.erb +15 -7
  81. data/lib/karafka/web/pro/ui/views/consumers/performance.erb +52 -0
  82. data/lib/karafka/web/{ui/pro → pro/ui}/views/consumers/running_jobs.erb +15 -7
  83. data/lib/karafka/web/{ui/pro → pro/ui}/views/consumers/subscriptions.erb +6 -1
  84. data/lib/karafka/web/{ui/pro → pro/ui}/views/dashboard/index.erb +10 -10
  85. data/lib/karafka/web/pro/ui/views/dlq/_no_topics.erb +7 -0
  86. data/lib/karafka/web/{ui/pro → pro/ui}/views/dlq/index.erb +1 -1
  87. data/lib/karafka/web/{ui/pro → pro/ui}/views/errors/_error.erb +2 -6
  88. data/lib/karafka/web/pro/ui/views/errors/_table.erb +23 -0
  89. data/lib/karafka/web/pro/ui/views/explorer/_failed_deserialization.erb +4 -0
  90. data/lib/karafka/web/{ui/pro → pro/ui}/views/explorer/_message.erb +7 -1
  91. data/lib/karafka/web/pro/ui/views/explorer/_no_topics.erb +5 -0
  92. data/lib/karafka/web/{ui/pro → pro/ui}/views/explorer/index.erb +1 -6
  93. data/lib/karafka/web/{ui/pro → pro/ui}/views/explorer/message/_metadata.erb +33 -9
  94. data/lib/karafka/web/{ui/pro → pro/ui}/views/explorer/message/_payload.erb +4 -4
  95. data/lib/karafka/web/{ui/pro → pro/ui}/views/explorer/message/_payload_actions.erb +1 -1
  96. data/lib/karafka/web/pro/ui/views/explorer/messages/_headers.erb +33 -0
  97. data/lib/karafka/web/pro/ui/views/explorer/messages/_key.erb +20 -0
  98. data/lib/karafka/web/pro/ui/views/explorer/partition/_cleaned.erb +5 -0
  99. data/lib/karafka/web/pro/ui/views/explorer/partition/_empty.erb +5 -0
  100. data/lib/karafka/web/pro/ui/views/explorer/partition/_messages.erb +21 -0
  101. data/lib/karafka/web/pro/ui/views/explorer/topic/_empty.erb +5 -0
  102. data/lib/karafka/web/pro/ui/views/explorer/topic/_limited.erb +10 -0
  103. data/lib/karafka/web/{ui/pro → pro/ui}/views/health/_breadcrumbs.erb +8 -0
  104. data/lib/karafka/web/pro/ui/views/health/_no_data.erb +7 -0
  105. data/lib/karafka/web/{ui/pro → pro/ui}/views/health/_partition.erb +1 -1
  106. data/lib/karafka/web/{ui/pro → pro/ui}/views/health/_partition_lags.erb +6 -3
  107. data/lib/karafka/web/{ui/pro → pro/ui}/views/health/_tabs.erb +11 -1
  108. data/lib/karafka/web/{ui/pro → pro/ui}/views/health/changes.erb +13 -8
  109. data/lib/karafka/web/pro/ui/views/health/cluster_lags.erb +54 -0
  110. data/lib/karafka/web/{ui/pro → pro/ui}/views/health/lags.erb +14 -8
  111. data/lib/karafka/web/{ui/pro → pro/ui}/views/health/offsets.erb +15 -12
  112. data/lib/karafka/web/{ui/pro → pro/ui}/views/health/overview.erb +21 -7
  113. data/lib/karafka/web/{ui/pro → pro/ui}/views/jobs/_job.erb +1 -1
  114. data/lib/karafka/web/pro/ui/views/jobs/_no_jobs.erb +7 -0
  115. data/lib/karafka/web/{ui/pro → pro/ui}/views/jobs/pending.erb +12 -8
  116. data/lib/karafka/web/{ui/pro → pro/ui}/views/jobs/running.erb +12 -8
  117. data/lib/karafka/web/{ui/pro → pro/ui}/views/routing/_consumer_group.erb +2 -2
  118. data/lib/karafka/web/{ui/pro → pro/ui}/views/routing/index.erb +1 -1
  119. data/lib/karafka/web/{ui/pro → pro/ui}/views/routing/show.erb +1 -1
  120. data/lib/karafka/web/{ui/pro → pro/ui}/views/shared/_navigation.erb +14 -0
  121. data/lib/karafka/web/pro/ui/views/topics/_breadcrumbs.erb +37 -0
  122. data/lib/karafka/web/pro/ui/views/topics/_partition.erb +16 -0
  123. data/lib/karafka/web/pro/ui/views/topics/_tabs.erb +37 -0
  124. data/lib/karafka/web/pro/ui/views/topics/_topic.erb +12 -0
  125. data/lib/karafka/web/pro/ui/views/topics/config.erb +29 -0
  126. data/lib/karafka/web/pro/ui/views/topics/distribution/_badges.erb +7 -0
  127. data/lib/karafka/web/pro/ui/views/topics/distribution/_chart.erb +2 -0
  128. data/lib/karafka/web/pro/ui/views/topics/distribution/_empty_partitions.erb +1 -0
  129. data/lib/karafka/web/pro/ui/views/topics/distribution/_limited.erb +10 -0
  130. data/lib/karafka/web/pro/ui/views/topics/distribution/_partition.erb +10 -0
  131. data/lib/karafka/web/pro/ui/views/topics/distribution.erb +47 -0
  132. data/lib/karafka/web/pro/ui/views/topics/index.erb +16 -0
  133. data/lib/karafka/web/pro/ui/views/topics/replication.erb +28 -0
  134. data/lib/karafka/web/processing/consumers/aggregators/base.rb +1 -1
  135. data/lib/karafka/web/processing/consumers/aggregators/metrics.rb +1 -1
  136. data/lib/karafka/web/processing/consumers/aggregators/state.rb +4 -4
  137. data/lib/karafka/web/tracking/consumers/contracts/report.rb +1 -1
  138. data/lib/karafka/web/tracking/consumers/listeners/booting.rb +3 -1
  139. data/lib/karafka/web/tracking/consumers/listeners/errors.rb +2 -2
  140. data/lib/karafka/web/tracking/consumers/reporter.rb +3 -3
  141. data/lib/karafka/web/tracking/consumers/sampler.rb +2 -2
  142. data/lib/karafka/web/tracking/contracts/error.rb +1 -1
  143. data/lib/karafka/web/tracking/producers/listeners/booting.rb +3 -1
  144. data/lib/karafka/web/tracking/producers/listeners/errors.rb +2 -2
  145. data/lib/karafka/web/tracking/producers/reporter.rb +1 -1
  146. data/lib/karafka/web/tracking/producers/sampler.rb +1 -1
  147. data/lib/karafka/web/tracking/sampler.rb +3 -3
  148. data/lib/karafka/web/ui/app.rb +13 -9
  149. data/lib/karafka/web/ui/base.rb +1 -0
  150. data/lib/karafka/web/ui/controllers/{base.rb → base_controller.rb} +15 -2
  151. data/lib/karafka/web/ui/controllers/{become_pro.rb → become_pro_controller.rb} +1 -1
  152. data/lib/karafka/web/ui/controllers/{cluster.rb → cluster_controller.rb} +4 -4
  153. data/lib/karafka/web/ui/controllers/{consumers.rb → consumers_controller.rb} +3 -3
  154. data/lib/karafka/web/ui/controllers/{dashboard.rb → dashboard_controller.rb} +1 -1
  155. data/lib/karafka/web/ui/controllers/{errors.rb → errors_controller.rb} +2 -2
  156. data/lib/karafka/web/ui/controllers/{jobs.rb → jobs_controller.rb} +5 -5
  157. data/lib/karafka/web/ui/controllers/{routing.rb → routing_controller.rb} +2 -2
  158. data/lib/karafka/web/ui/controllers/{status.rb → status_controller.rb} +1 -1
  159. data/lib/karafka/web/ui/helpers/alerts_helper.rb +23 -0
  160. data/lib/karafka/web/ui/helpers/application_helper.rb +53 -1
  161. data/lib/karafka/web/ui/lib/paginations/offset_based.rb +3 -4
  162. data/lib/karafka/web/ui/lib/safe_runner.rb +59 -0
  163. data/lib/karafka/web/ui/models/broker.rb +66 -0
  164. data/lib/karafka/web/ui/models/health.rb +28 -2
  165. data/lib/karafka/web/ui/models/message.rb +9 -3
  166. data/lib/karafka/web/ui/models/process.rb +10 -5
  167. data/lib/karafka/web/ui/models/processes.rb +2 -2
  168. data/lib/karafka/web/ui/models/status.rb +1 -1
  169. data/lib/karafka/web/ui/models/topic.rb +78 -0
  170. data/lib/karafka/web/ui/public/javascripts/application.js +18 -1
  171. data/lib/karafka/web/ui/public/javascripts/charts/data_formatting_utility.js +71 -0
  172. data/lib/karafka/web/ui/public/javascripts/charts/dataset_state_manager.js +49 -0
  173. data/lib/karafka/web/ui/public/javascripts/charts/types/bar.js +123 -0
  174. data/lib/karafka/web/ui/public/javascripts/charts/types/line.js +143 -0
  175. data/lib/karafka/web/ui/public/javascripts/charts.js +10 -325
  176. data/lib/karafka/web/ui/public/javascripts/live_poll.js +5 -5
  177. data/lib/karafka/web/ui/public/javascripts/tabs_manager.js +57 -0
  178. data/lib/karafka/web/ui/public/stylesheets/application.css +7 -6
  179. data/lib/karafka/web/ui/views/cluster/_breadcrumbs.erb +3 -3
  180. data/lib/karafka/web/ui/views/cluster/_no_partitions.erb +1 -3
  181. data/lib/karafka/web/ui/views/cluster/_tabs.erb +3 -3
  182. data/lib/karafka/web/ui/views/cluster/brokers.erb +19 -19
  183. data/lib/karafka/web/ui/views/cluster/replication.erb +37 -0
  184. data/lib/karafka/web/ui/views/consumers/_assignments_badges.erb +24 -0
  185. data/lib/karafka/web/ui/views/consumers/_consumer.erb +2 -15
  186. data/lib/karafka/web/ui/views/consumers/_counters.erb +1 -1
  187. data/lib/karafka/web/ui/views/consumers/_summary.erb +5 -5
  188. data/lib/karafka/web/ui/views/consumers/index.erb +22 -20
  189. data/lib/karafka/web/ui/views/dashboard/index.erb +9 -9
  190. data/lib/karafka/web/ui/views/errors/_cleaned.erb +1 -3
  191. data/lib/karafka/web/ui/views/errors/_error.erb +2 -6
  192. data/lib/karafka/web/ui/views/errors/_no_errors.erb +1 -3
  193. data/lib/karafka/web/ui/views/errors/index.erb +22 -20
  194. data/lib/karafka/web/ui/views/jobs/_job.erb +4 -1
  195. data/lib/karafka/web/ui/views/jobs/_no_jobs.erb +1 -3
  196. data/lib/karafka/web/ui/views/jobs/pending.erb +4 -3
  197. data/lib/karafka/web/ui/views/jobs/running.erb +4 -3
  198. data/lib/karafka/web/ui/views/routing/_consumer_group.erb +2 -2
  199. data/lib/karafka/web/ui/views/routing/index.erb +1 -1
  200. data/lib/karafka/web/ui/views/routing/show.erb +1 -1
  201. data/lib/karafka/web/ui/views/shared/_become_pro.erb +3 -3
  202. data/lib/karafka/web/ui/views/shared/_header.erb +16 -10
  203. data/lib/karafka/web/ui/views/shared/_navigation.erb +17 -3
  204. data/lib/karafka/web/ui/views/shared/_not_a_message.erb +5 -0
  205. data/lib/karafka/web/ui/views/shared/alerts/_info.erb +3 -0
  206. data/lib/karafka/web/ui/views/shared/charts/_bar.erb +7 -0
  207. data/lib/karafka/web/ui/views/shared/{_chart.erb → charts/_line.erb} +1 -1
  208. data/lib/karafka/web/ui/views/shared/exceptions/not_found.erb +1 -1
  209. data/lib/karafka/web/ui/views/status/show.erb +1 -1
  210. data/lib/karafka/web/version.rb +1 -1
  211. data/lib/karafka/web.rb +17 -1
  212. data.tar.gz.sig +0 -0
  213. metadata +189 -120
  214. metadata.gz.sig +0 -0
  215. data/lib/karafka/web/ui/pro/controllers/dlq.rb +0 -43
  216. data/lib/karafka/web/ui/pro/views/consumers/consumer/_no_jobs.erb +0 -9
  217. data/lib/karafka/web/ui/pro/views/consumers/consumer/_no_subscriptions.erb +0 -9
  218. data/lib/karafka/web/ui/pro/views/consumers/index.erb +0 -36
  219. data/lib/karafka/web/ui/pro/views/dlq/_no_topics.erb +0 -9
  220. data/lib/karafka/web/ui/pro/views/errors/_table.erb +0 -21
  221. data/lib/karafka/web/ui/pro/views/explorer/_failed_deserialization.erb +0 -4
  222. data/lib/karafka/web/ui/pro/views/explorer/_no_topics.erb +0 -7
  223. data/lib/karafka/web/ui/pro/views/explorer/messages/_headers.erb +0 -15
  224. data/lib/karafka/web/ui/pro/views/explorer/messages/_key.erb +0 -12
  225. data/lib/karafka/web/ui/pro/views/explorer/partition/_cleaned.erb +0 -3
  226. data/lib/karafka/web/ui/pro/views/explorer/partition/_empty.erb +0 -3
  227. data/lib/karafka/web/ui/pro/views/explorer/partition/_messages.erb +0 -19
  228. data/lib/karafka/web/ui/pro/views/explorer/topic/_empty.erb +0 -3
  229. data/lib/karafka/web/ui/pro/views/explorer/topic/_limited.erb +0 -4
  230. data/lib/karafka/web/ui/pro/views/health/_no_data.erb +0 -9
  231. data/lib/karafka/web/ui/pro/views/jobs/_no_jobs.erb +0 -9
  232. data/lib/karafka/web/ui/public/javascripts/tabs.js +0 -59
  233. data/lib/karafka/web/ui/views/cluster/topics.erb +0 -35
  234. /data/lib/karafka/web/{ui/pro → pro/ui}/views/consumers/consumer/_consumer_group.erb +0 -0
  235. /data/lib/karafka/web/{ui/pro → pro/ui}/views/consumers/consumer/_job.erb +0 -0
  236. /data/lib/karafka/web/{ui/pro → pro/ui}/views/consumers/consumer/_metrics.erb +0 -0
  237. /data/lib/karafka/web/{ui/pro → pro/ui}/views/consumers/consumer/_partition.erb +0 -0
  238. /data/lib/karafka/web/{ui/pro → pro/ui}/views/consumers/consumer/_stopped.erb +0 -0
  239. /data/lib/karafka/web/{ui/pro → pro/ui}/views/consumers/consumer/_tabs.erb +0 -0
  240. /data/lib/karafka/web/{ui/pro → pro/ui}/views/dlq/_breadcrumbs.erb +0 -0
  241. /data/lib/karafka/web/{ui/pro → pro/ui}/views/dlq/_topic.erb +0 -0
  242. /data/lib/karafka/web/{ui/pro → pro/ui}/views/errors/_breadcrumbs.erb +0 -0
  243. /data/lib/karafka/web/{ui/pro → pro/ui}/views/errors/_partition_option.erb +0 -0
  244. /data/lib/karafka/web/{ui/pro → pro/ui}/views/errors/_title_with_select.erb +0 -0
  245. /data/lib/karafka/web/{ui/pro → pro/ui}/views/errors/index.erb +0 -0
  246. /data/lib/karafka/web/{ui/pro → pro/ui}/views/errors/partition.erb +0 -0
  247. /data/lib/karafka/web/{ui/pro → pro/ui}/views/errors/show.erb +0 -0
  248. /data/lib/karafka/web/{ui/pro → pro/ui}/views/explorer/_breadcrumbs.erb +0 -0
  249. /data/lib/karafka/web/{ui/pro → pro/ui}/views/explorer/_filtered.erb +0 -0
  250. /data/lib/karafka/web/{ui/pro → pro/ui}/views/explorer/_partition_option.erb +0 -0
  251. /data/lib/karafka/web/{ui/pro → pro/ui}/views/explorer/_topic.erb +0 -0
  252. /data/lib/karafka/web/{ui/pro → pro/ui}/views/explorer/message/_message_actions.erb +0 -0
  253. /data/lib/karafka/web/{ui/pro → pro/ui}/views/explorer/messages/_detail.erb +0 -0
  254. /data/lib/karafka/web/{ui/pro → pro/ui}/views/explorer/partition/_details.erb +0 -0
  255. /data/lib/karafka/web/{ui/pro → pro/ui}/views/explorer/partition.erb +0 -0
  256. /data/lib/karafka/web/{ui/pro → pro/ui}/views/explorer/show.erb +0 -0
  257. /data/lib/karafka/web/{ui/pro → pro/ui}/views/explorer/topic/_details.erb +0 -0
  258. /data/lib/karafka/web/{ui/pro → pro/ui}/views/explorer/topic.erb +0 -0
  259. /data/lib/karafka/web/{ui/pro → pro/ui}/views/health/_consumer_group_header.erb +0 -0
  260. /data/lib/karafka/web/{ui/pro → pro/ui}/views/health/_partition_offset.erb +0 -0
  261. /data/lib/karafka/web/{ui/pro → pro/ui}/views/health/_partition_times.erb +0 -0
  262. /data/lib/karafka/web/{ui/pro → pro/ui}/views/routing/_detail.erb +0 -0
  263. /data/lib/karafka/web/{ui/pro → pro/ui}/views/routing/_topic.erb +0 -0
  264. /data/lib/karafka/web/ui/public/javascripts/{bootstrap.min.js → libs/bootstrap.min.js} +0 -0
  265. /data/lib/karafka/web/ui/public/javascripts/{chart.min.js → libs/chart.min.js} +0 -0
  266. /data/lib/karafka/web/ui/public/javascripts/{datepicker.js → libs/datepicker.js} +0 -0
  267. /data/lib/karafka/web/ui/public/javascripts/{highlight.min.js → libs/highlight.min.js} +0 -0
  268. /data/lib/karafka/web/ui/public/javascripts/{timeago.min.js → libs/timeago.min.js} +0 -0
  269. /data/lib/karafka/web/ui/public/stylesheets/{bootstrap.min.css → libs/bootstrap.min.css} +0 -0
  270. /data/lib/karafka/web/ui/public/stylesheets/{datepicker.min.css → libs/datepicker.min.css} +0 -0
  271. /data/lib/karafka/web/ui/public/stylesheets/{highlight.min.css → libs/highlight.min.css} +0 -0
@@ -8,6 +8,29 @@ module Karafka
8
8
  module Helpers
9
9
  # Main application helper
10
10
  module ApplicationHelper
11
+ # Default attribute names mapped from the attributes themselves
12
+ # It makes it easier as we do not have to declare those all the time
13
+ SORT_NAMES = {
14
+ id: 'ID',
15
+ partition_id: 'Partition',
16
+ memory_usage: 'RSS',
17
+ started_at: 'Started',
18
+ committed_offset: 'Committed',
19
+ last_offset: 'Last',
20
+ first_offset: 'First',
21
+ lo_offset: 'Low',
22
+ hi_offset: 'High',
23
+ ls_offset: 'LSO',
24
+ lag_hybrid: 'Lag',
25
+ lag_stored: 'Stored',
26
+ stored_offset: 'Stored',
27
+ fetch_state: 'Fetch',
28
+ poll_state: 'Poll',
29
+ lso_risk_state: 'LSO'
30
+ }.freeze
31
+
32
+ private_constant :SORT_NAMES
33
+
11
34
  # Adds active class to the current location in the nav if needed
12
35
  # @param location [Hash]
13
36
  def nav_class(location)
@@ -260,7 +283,7 @@ module Karafka
260
283
  def sort_link(name, attribute = nil, rev: false)
261
284
  unless attribute
262
285
  attribute = name
263
- name = attribute.to_s.tr('_', ' ').capitalize
286
+ name = SORT_NAMES[attribute] || attribute.to_s.tr('_', ' ').tr('?', '').capitalize
264
287
  end
265
288
 
266
289
  arrow_both = '⇕'
@@ -284,6 +307,35 @@ module Karafka
284
307
 
285
308
  "<a class=\"sort\" href=\"#{path}\">#{full_name}</a>"
286
309
  end
310
+
311
+ # Truncates given text if it is too long and wraps it with a title with full text.
312
+ # Can use a middle-based strategy that keeps beginning and ending of a string instead of
313
+ # keeping just the beginning.
314
+ #
315
+ # The `:middle` strategy is useful when we have strings such as really long process names
316
+ # that have important beginning and end but middle can be removed without risk of not
317
+ # allowing user to recognize the content.
318
+ #
319
+ # @param string [String] string we want to truncate
320
+ # @param length [Integer] max length of the final string that we accept before truncating
321
+ # @param omission [String] truncation omission
322
+ # @param strategy [Symbol] `:default` or `:middle` how should we truncate
323
+ # @return [String] HTML span tag with truncated content and full content title
324
+ def truncate(string, length: 50, omission: '...', strategy: :default)
325
+ return string if string.length <= length
326
+
327
+ case strategy
328
+ when :default
329
+ truncated = string[0...(length - omission.length)] + omission
330
+ when :middle
331
+ part_length = (length - omission.length) / 2
332
+ truncated = string[0...part_length] + omission + string[-part_length..]
333
+ else
334
+ raise Karafka::Errors::UnsupportedCaseError, "Unknown strategy: #{strategy}"
335
+ end
336
+
337
+ %(<span title="#{string}">#{truncated}</span>)
338
+ end
287
339
  end
288
340
  end
289
341
  end
@@ -42,13 +42,12 @@ module Karafka
42
42
  # not consider it as a first page and we allow to "reset" to -1 via the first page
43
43
  # button
44
44
  def first_offset?
45
- @current_offset != -1
45
+ @current_offset != -1 && @previous_offset != false
46
46
  end
47
47
 
48
- # @return [Boolean] first page offset is always nothing because we use the default -1
49
- # for the offset.
48
+ # @return [Integer] -1 because it will then select the highest offsets
50
49
  def first_offset
51
- false
50
+ -1
52
51
  end
53
52
 
54
53
  # @return [Boolean] Active previous page link when it is not the first page
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Web
5
+ module Ui
6
+ module Lib
7
+ # Class used to execute code that can fail but we do not want to fail the whole operation.
8
+ # The primary use-case is for displaying deserialized data. We always need to assume, that
9
+ # part of the data can be corrupted and it should not crash the whole UI.
10
+ #
11
+ # It caches the result and does not run the code twice (only once)
12
+ class SafeRunner
13
+ attr_reader :error, :result
14
+
15
+ # @param block [Proc] code we want to safe-guard
16
+ def initialize(&block)
17
+ @code = block
18
+ @executed = false
19
+ @success = false
20
+ @error = nil
21
+ @result = nil
22
+ end
23
+
24
+ # @return [Boolean] was the code execution successful or not
25
+ def success?
26
+ return @success if executed?
27
+
28
+ call
29
+
30
+ @success
31
+ end
32
+
33
+ # @return [Boolean] was the code execution failed or not
34
+ def failure?
35
+ !success?
36
+ end
37
+
38
+ # Runs the execution and returns block result
39
+ def call
40
+ return @result if executed?
41
+
42
+ @executed = true
43
+ @result = @code.call
44
+ @success = true
45
+ @result
46
+ rescue StandardError => e
47
+ @error = e
48
+ @success = false
49
+ end
50
+
51
+ # @return [Boolean] was the code executed already or not yet
52
+ def executed?
53
+ @executed
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Web
5
+ module Ui
6
+ module Models
7
+ # Represents a single broker data within the cluster
8
+ class Broker < Lib::HashProxy
9
+ class << self
10
+ # @return [Array<Broker>] all brokers in the cluster
11
+ def all
12
+ # We do not cache here because we want the most recent state of brokers possible
13
+ ClusterInfo.fetch(cached: false).brokers.map do |broker|
14
+ new(broker)
15
+ end
16
+ end
17
+
18
+ # Finds requested broker
19
+ #
20
+ # @param broker_id [String, Integer] id of the broker
21
+ # @return [Broker]
22
+ # @raise [::Karafka::Web::Errors::Ui::NotFoundError]
23
+ def find(broker_id)
24
+ found = all.find { |broker| broker.id.to_s == broker_id }
25
+
26
+ return found if found
27
+
28
+ raise(::Karafka::Web::Errors::Ui::NotFoundError, broker_id)
29
+ end
30
+ end
31
+
32
+ # @return [Integer]
33
+ def id
34
+ broker_id
35
+ end
36
+
37
+ # @return [String]
38
+ def name
39
+ broker_name
40
+ end
41
+
42
+ # @return [Integer]
43
+ def port
44
+ broker_port
45
+ end
46
+
47
+ # @return [String] full broker name for presentation
48
+ def full_name
49
+ "#{id} - #{name}:#{port}"
50
+ end
51
+
52
+ # @return [Array<Karafka::Admin::Configs::Config>] all broker configs
53
+ def configs
54
+ # We copy the array because the result one is frozen and we sort
55
+ @configs ||= ::Karafka::Admin::Configs.describe(
56
+ ::Karafka::Admin::Configs::Resource.new(
57
+ type: :broker,
58
+ name: id
59
+ )
60
+ ).first.configs.dup
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -8,7 +8,7 @@ module Karafka
8
8
  class Health
9
9
  class << self
10
10
  # @param state [State] current system state
11
- # @return [Hash] has with aggregated statistics
11
+ # @return [Hash] hash with aggregated statistics
12
12
  def current(state)
13
13
  stats = {}
14
14
 
@@ -18,6 +18,32 @@ module Karafka
18
18
  sort_structure(stats)
19
19
  end
20
20
 
21
+ # @return [Hash] hash with cluster lag data
22
+ def cluster_lags_with_offsets
23
+ # We need to remap raw results so they comply with our sorting flows
24
+ mapped_lags = {}
25
+
26
+ ::Karafka::Admin.read_lags_with_offsets(
27
+ active_topics_only: Web.config.ui.visibility.active_topics_cluster_lags_only
28
+ ).each do |consumer_group, topics|
29
+ mapped_lags[consumer_group] ||= {}
30
+
31
+ topics.each do |topic_name, partitions_details|
32
+ mapped_lags[consumer_group][topic_name] ||= []
33
+
34
+ partitions_details.each do |partition_id, lags_with_offsets|
35
+ mapped_lags[consumer_group][topic_name] << {
36
+ id: partition_id,
37
+ lag: lags_with_offsets.fetch(:lag),
38
+ stored_offset: lags_with_offsets.fetch(:offset)
39
+ }
40
+ end
41
+ end
42
+ end
43
+
44
+ mapped_lags
45
+ end
46
+
21
47
  private
22
48
 
23
49
  # Aggregates data on a per topic basis (in the context of a consumer group)
@@ -62,7 +88,7 @@ module Karafka
62
88
  #
63
89
  # @param state [State]
64
90
  def iterate_partitions(state)
65
- # By default processes are sort by name and this is not what we want here
91
+ # By default processes are sort by id and this is not what we want here
66
92
  # We want to make sure that the newest data is processed the last, so we get
67
93
  # the most accurate state in case of deployments and shutdowns, etc without the
68
94
  # expired processes partitions data overwriting the newly created processes
@@ -100,10 +100,16 @@ module Karafka
100
100
 
101
101
  previous_offset = start_offset + count
102
102
 
103
+ if previous_offset >= high_offset
104
+ previous_offset = false
105
+ elsif previous_offset + (per_page - 1) > high_offset
106
+ previous_offset = high_offset - per_page
107
+ else
108
+ previous_offset
109
+ end
110
+
103
111
  return [
104
- # If there is a potential previous page with more recent data, compute its
105
- # offset
106
- previous_offset >= high_offset ? false : previous_offset,
112
+ previous_offset,
107
113
  fill_compacted(messages, partition_id, context_offset, context_count, high_offset).reverse,
108
114
  next_offset
109
115
  ]
@@ -18,11 +18,6 @@ module Karafka
18
18
  end
19
19
  end
20
20
 
21
- # @return [String] process id without the name and ip
22
- def id
23
- @id ||= name.split(':').last
24
- end
25
-
26
21
  # @return [Array<ConsumerGroup>] consumer groups to which this process is subscribed in
27
22
  # an alphabetical order
28
23
  def consumer_groups
@@ -41,6 +36,16 @@ module Karafka
41
36
  .then { |jobs| Jobs.new(jobs) }
42
37
  end
43
38
 
39
+ # @return [Integer] number of running jobs on a process
40
+ def running_jobs_count
41
+ jobs.running.count
42
+ end
43
+
44
+ # @return [Integer] number of pending jobs on a process
45
+ def pending_jobs_count
46
+ jobs.pending.count
47
+ end
48
+
44
49
  # @return [Integer] collective hybrid lag on this process
45
50
  def lag_hybrid
46
51
  consumer_groups
@@ -79,11 +79,11 @@ module Karafka
79
79
  end
80
80
  end
81
81
 
82
- # Ensures that we always return processes sorted by their name
82
+ # Ensures that we always return processes sorted by their id
83
83
  # @param processes [Array<Hash>]
84
84
  # @return [Array<Hash>] sorted processes data
85
85
  def sort_processes(processes)
86
- processes.sort_by { |consumer| consumer[:process][:name] }
86
+ processes.sort_by { |consumer| consumer[:process][:id] }
87
87
  end
88
88
  end
89
89
  end
@@ -44,7 +44,7 @@ module Karafka
44
44
  # aware of the deserializer, etc
45
45
  def enabled
46
46
  enabled = ::Karafka::App.routes.map(&:name).include?(
47
- ::Karafka::Web.config.processing.consumer_group
47
+ ::Karafka::Web.config.group_id
48
48
  )
49
49
 
50
50
  Step.new(
@@ -6,6 +6,28 @@ module Karafka
6
6
  module Models
7
7
  # Single topic data representation model
8
8
  class Topic < Lib::HashProxy
9
+ class << self
10
+ # @return [Array<Broker>] all topics in the cluster
11
+ def all
12
+ ClusterInfo.fetch.topics.map do |topic|
13
+ new(topic)
14
+ end
15
+ end
16
+
17
+ # Finds requested topic
18
+ #
19
+ # @param topic_name [String] name of the topic
20
+ # @return [Topic]
21
+ # @raise [::Karafka::Web::Errors::Ui::NotFoundError]
22
+ def find(topic_name)
23
+ found = all.find { |topic| topic.topic_name == topic_name }
24
+
25
+ return found if found
26
+
27
+ raise(::Karafka::Web::Errors::Ui::NotFoundError, topic_name)
28
+ end
29
+ end
30
+
9
31
  # @return [Array<Partition>] All topic partitions data
10
32
  def partitions
11
33
  super.map do |partition_id, partition_hash|
@@ -14,6 +36,62 @@ module Karafka
14
36
  Partition.new(partition_hash)
15
37
  end
16
38
  end
39
+
40
+ # @return [Array<Karafka::Admin::Configs::Config>] all topic configs
41
+ def configs
42
+ @configs ||= ::Karafka::Admin::Configs.describe(
43
+ ::Karafka::Admin::Configs::Resource.new(
44
+ type: :topic,
45
+ name: topic_name
46
+ )
47
+ ).first.configs.dup
48
+ end
49
+
50
+ # Generates info about estimated messages distribution in partitions, allowing for
51
+ # inspection and detection of imbalances
52
+ #
53
+ # @param partitions [Array<Integer>] partitions we're interested in
54
+ #
55
+ # @return [Array<HashProxy, Array<HashProxy>>] array where first value contains
56
+ # aggregated statistics and then the second value is an array with per partition data
57
+ def distribution(partitions)
58
+ sum = 0.0
59
+ avg = 0.0
60
+
61
+ counts = partitions.map do |partition_id|
62
+ offsets = Admin.read_watermark_offsets(topic_name, partition_id)
63
+ count = offsets.last - offsets.first
64
+
65
+ sum += count
66
+
67
+ {
68
+ count: count,
69
+ partition_id: partition_id
70
+ }
71
+ end
72
+
73
+ avg = sum / counts.size
74
+
75
+ counts.each do |part_stats|
76
+ count = part_stats[:count]
77
+
78
+ part_stats[:share] = ((count / sum) * 100).round(2)
79
+ part_stats[:diff] = ((count - avg) / avg) * 100
80
+ end
81
+
82
+ variance = counts
83
+ .map { |part_stats| part_stats[:count] }
84
+ .sum { |count| (count - avg)**2 } / counts.size
85
+
86
+ std_dev = Math.sqrt(variance)
87
+ std_dev_rel = ((std_dev / avg) * 100).round(2)
88
+
89
+ [
90
+ # round stdev since its message count
91
+ Lib::HashProxy.new(std_dev: std_dev.round, std_dev_rel: std_dev_rel, sum: sum),
92
+ counts.map { |part_stats| Lib::HashProxy.new(part_stats) }
93
+ ]
94
+ end
17
95
  end
18
96
  end
19
97
  end
@@ -18,6 +18,18 @@ function updateTimeAgo() {
18
18
  }
19
19
  }
20
20
 
21
+ // Cheap way to do breadcrumbs
22
+ function refreshTitle() {
23
+ const breadcrumbs = document.querySelectorAll('.breadcrumb a');
24
+ let breadcrumbTexts = Array.from(breadcrumbs).slice(1).map(crumb => crumb.textContent.trim());
25
+
26
+ if (breadcrumbTexts.length > 0) {
27
+ document.title = breadcrumbTexts.join(' > ') + ' - Karafka Web UI';
28
+ } else {
29
+ document.title = 'Karafka Web UI';
30
+ }
31
+ }
32
+
21
33
  // To prevent from flickering, the UI is initially hidden and visible when all the JS components
22
34
  // are fully initialized
23
35
  function displayUi() {
@@ -66,11 +78,16 @@ function addListeners() {
66
78
  hljs.highlightAll();
67
79
  updateTimeAgo();
68
80
  redirectToPartition();
69
- manageTabs();
81
+
82
+ const tabsManager = new TabsManager();
83
+ tabsManager.manageTabs();
84
+
70
85
  manageCharts();
71
86
  bindActionsConfirmations();
72
87
  loadOffsetLookupDatePicker();
73
88
  displayUi();
89
+
90
+ refreshTitle()
74
91
  }
75
92
 
76
93
  var ready = (callback) => {
@@ -0,0 +1,71 @@
1
+ const DataFormattingUtils = {
2
+ niceBytes(x, precision = 2) {
3
+ const units = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
4
+ let l = 0, n = parseInt(x, 10) || 0;
5
+
6
+ while (n >= 1024 && ++l) {
7
+ n /= 1024;
8
+ }
9
+
10
+ return `${n.toFixed(n < 10 && l > 0 ? 1 : precision)} ${units[l]}`;
11
+ },
12
+
13
+ formatLabelX(value, type) {
14
+ switch (type) {
15
+ case 'date':
16
+ let date = new Date(value * 1000)
17
+ let date_str =
18
+ ("00" + (date.getMonth() + 1)).slice(-2) + "/" +
19
+ ("00" + date.getDate()).slice(-2) + "/" +
20
+ date.getFullYear() + " " +
21
+ ("00" + date.getHours()).slice(-2) + ":" +
22
+ ("00" + date.getMinutes()).slice(-2) + ":" +
23
+ ("00" + date.getSeconds()).slice(-2);
24
+
25
+ return date_str
26
+ default:
27
+ return value
28
+ }
29
+ },
30
+
31
+ formatTooltip(type, tooltipItem) {
32
+ let value = tooltipItem.parsed.y;
33
+ let label = tooltipItem.dataset.label;
34
+
35
+ switch(type) {
36
+ case 'percentage':
37
+ if (Math.floor(value) === value) {
38
+ return label + ': ' + value + ' %';
39
+ } else {
40
+ return label + ': ' + (Math.round(value * 100) / 100) + ' %';
41
+ }
42
+ case 'memory':
43
+ return label + ': ' + DataFormattingUtils.niceBytes(value * 1024, 2);
44
+ default:
45
+ return tooltipItem.yLabel
46
+ }
47
+ },
48
+
49
+ formatLabelY(type, label) {
50
+ switch(type) {
51
+ case 'percentage':
52
+ if (Math.floor(label) === label) {
53
+ return label + '%'
54
+ } else {
55
+ return (Math.round(label * 100) / 100) + '%'
56
+ }
57
+ case 'memory':
58
+ return DataFormattingUtils.niceBytes(label * 1024, 1)
59
+ default:
60
+ if (Math.floor(label) === label) {
61
+ return label
62
+ } else {
63
+ return Math.round(label * 100) / 100
64
+ }
65
+ }
66
+ },
67
+
68
+ isFractionalPrecision(value) {
69
+ return value !== Math.floor(value);
70
+ }
71
+ };
@@ -0,0 +1,49 @@
1
+ class DatasetStateManager {
2
+ constructor() {
3
+ this.storageKey = 'karafkaDisabledDatasets';
4
+ }
5
+
6
+ // Reads all disabled datasets from localStorage
7
+ readAll() {
8
+ const raw = localStorage.getItem(this.storageKey);
9
+ return raw ? JSON.parse(raw) : {};
10
+ }
11
+
12
+ // Saves all disabled datasets to localStorage
13
+ saveAll(data) {
14
+ localStorage.setItem(this.storageKey, JSON.stringify(data));
15
+ }
16
+
17
+ // Saves the current disabled datasets for all '.chartjs-line' charts
18
+ saveCurrent() {
19
+ const charts = document.querySelectorAll('.chartjs');
20
+ const url = window.location.href.split('?')[0];
21
+ let currentDisabled = {};
22
+ let allDisabled = this.readAll();
23
+
24
+ charts.forEach(chart => {
25
+ const chartId = chart.id;
26
+ const chartInstance = Chart.getChart(chartId);
27
+ if (!chartInstance || !chartInstance.legend || !chartInstance.legend.legendItems) return;
28
+
29
+ let disabledIndices = chartInstance.legend.legendItems
30
+ .map((item, index) => item.hidden ? index : null)
31
+ .filter(index => index !== null);
32
+
33
+ if (disabledIndices.length > 0) {
34
+ currentDisabled[chartId] = disabledIndices;
35
+ }
36
+ });
37
+
38
+ allDisabled[url] = currentDisabled;
39
+ this.saveAll(allDisabled);
40
+ }
41
+
42
+ // Retrieves the disabled datasets for a specific chart ID
43
+ getCurrentChart(chartId) {
44
+ const url = window.location.href.split('?')[0];
45
+ let allDisabled = this.readAll();
46
+ let currentDisabled = allDisabled[url] || {};
47
+ return currentDisabled[chartId] || [];
48
+ }
49
+ }