karafka-web 0.8.2 → 0.9.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
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
+ }