karafka-web 0.9.2 → 0.10.0.beta1

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 (417) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/workflows/ci.yml +30 -0
  4. data/.gitignore +2 -0
  5. data/.ruby-version +1 -1
  6. data/CHANGELOG.md +84 -3
  7. data/Gemfile +1 -0
  8. data/Gemfile.lock +30 -25
  9. data/LICENSE +1 -1
  10. data/bin/build_assets +51 -0
  11. data/bin/release +6 -0
  12. data/config/locales/pro_errors.yml +18 -0
  13. data/docker-compose.yml +1 -1
  14. data/gulpfile.js +73 -0
  15. data/karafka-web.gemspec +7 -2
  16. data/lib/karafka/web/config.rb +9 -10
  17. data/lib/karafka/web/contracts/base.rb +2 -0
  18. data/lib/karafka/web/contracts/config.rb +2 -1
  19. data/lib/karafka/web/errors.rb +12 -0
  20. data/lib/karafka/web/inflector.rb +1 -1
  21. data/lib/karafka/web/management/actions/enable.rb +11 -0
  22. data/lib/karafka/web/management/migrations/consumers_metrics/0_set_initial.rb +39 -0
  23. data/lib/karafka/web/management/migrations/consumers_metrics/1699543515_fill_missing_received_and_sent_bytes.rb +28 -0
  24. data/lib/karafka/web/management/migrations/consumers_metrics/1700234522_introduce_waiting.rb +26 -0
  25. data/lib/karafka/web/management/migrations/consumers_metrics/1700234522_remove_processing.rb +26 -0
  26. data/lib/karafka/web/management/migrations/consumers_metrics/1704722380_split_listeners_into_active_and_paused.rb +38 -0
  27. data/lib/karafka/web/management/migrations/consumers_metrics/1706607960_introduce_lag_total.rb +40 -0
  28. data/lib/karafka/web/management/migrations/consumers_metrics/1706611396_rename_lag_total_to_lag_hybrid.rb +38 -0
  29. data/lib/karafka/web/management/migrations/consumers_metrics/1716218393_populate_jobs_metrics.rb +26 -0
  30. data/lib/karafka/web/management/migrations/consumers_states/0_set_initial.rb +46 -0
  31. data/lib/karafka/web/management/migrations/consumers_states/1699543515_fill_missing_received_and_sent_bytes.rb +25 -0
  32. data/lib/karafka/web/management/migrations/consumers_states/1700234522_introduce_waiting.rb +22 -0
  33. data/lib/karafka/web/management/migrations/consumers_states/1700234522_remove_processing.rb +22 -0
  34. data/lib/karafka/web/management/migrations/consumers_states/1704722380_split_listeners_into_active_and_paused.rb +34 -0
  35. data/lib/karafka/web/management/migrations/consumers_states/1706607960_introduce_lag_total.rb +24 -0
  36. data/lib/karafka/web/management/migrations/consumers_states/1706611396_rename_lag_total_to_lag_hybrid.rb +23 -0
  37. data/lib/karafka/web/management/migrations/consumers_states/1716218393_add_jobs_counter.rb +24 -0
  38. data/lib/karafka/web/management/migrator.rb +5 -5
  39. data/lib/karafka/web/pro/commanding/commands/base.rb +8 -0
  40. data/lib/karafka/web/pro/commanding/commands/quiet.rb +4 -1
  41. data/lib/karafka/web/pro/commanding/commands/stop.rb +4 -1
  42. data/lib/karafka/web/pro/loader.rb +8 -0
  43. data/lib/karafka/web/pro/ui/app.rb +44 -7
  44. data/lib/karafka/web/pro/ui/controllers/dlq_controller.rb +1 -1
  45. data/lib/karafka/web/pro/ui/controllers/errors_controller.rb +1 -0
  46. data/lib/karafka/web/pro/ui/controllers/explorer_controller.rb +6 -14
  47. data/lib/karafka/web/pro/ui/controllers/messages_controller.rb +5 -4
  48. data/lib/karafka/web/pro/ui/controllers/search_controller.rb +73 -0
  49. data/lib/karafka/web/pro/ui/controllers/support_controller.rb +26 -0
  50. data/lib/karafka/web/pro/ui/controllers/topics_controller.rb +31 -0
  51. data/lib/karafka/web/pro/ui/controllers/ux_controller.rb +26 -0
  52. data/lib/karafka/web/pro/ui/lib/policies/config.rb +39 -0
  53. data/lib/karafka/web/pro/ui/lib/policies/contracts/config.rb +46 -0
  54. data/lib/karafka/web/pro/ui/lib/policies/messages.rb +76 -0
  55. data/lib/karafka/web/pro/ui/lib/policies/requests.rb +36 -0
  56. data/lib/karafka/web/pro/ui/lib/policies.rb +34 -0
  57. data/lib/karafka/web/pro/ui/lib/safe_runner.rb +98 -0
  58. data/lib/karafka/web/pro/ui/lib/search/config.rb +53 -0
  59. data/lib/karafka/web/pro/ui/lib/search/contracts/config.rb +101 -0
  60. data/lib/karafka/web/pro/ui/lib/search/contracts/form.rb +111 -0
  61. data/lib/karafka/web/pro/ui/lib/search/matchers/base.rb +59 -0
  62. data/lib/karafka/web/pro/ui/lib/search/matchers/raw_header_includes.rb +57 -0
  63. data/lib/karafka/web/pro/ui/lib/search/matchers/raw_key_includes.rb +41 -0
  64. data/lib/karafka/web/pro/ui/lib/search/matchers/raw_payload_includes.rb +45 -0
  65. data/lib/karafka/web/pro/ui/lib/search/normalizer.rb +47 -0
  66. data/lib/karafka/web/pro/ui/lib/search/runner.rb +230 -0
  67. data/lib/karafka/web/pro/ui/lib/search.rb +36 -0
  68. data/lib/karafka/web/pro/ui/views/cluster/_breadcrumbs.erb +4 -4
  69. data/lib/karafka/web/pro/ui/views/cluster/_tabs.erb +14 -24
  70. data/lib/karafka/web/pro/ui/views/cluster/index.erb +20 -22
  71. data/lib/karafka/web/pro/ui/views/cluster/show.erb +21 -25
  72. data/lib/karafka/web/pro/ui/views/commands/_backtrace.erb +4 -19
  73. data/lib/karafka/web/pro/ui/views/commands/_breadcrumbs.erb +3 -3
  74. data/lib/karafka/web/pro/ui/views/commands/_command.erb +6 -6
  75. data/lib/karafka/web/pro/ui/views/commands/_command_details.erb +1 -11
  76. data/lib/karafka/web/pro/ui/views/commands/_incompatible_schema.erb +3 -14
  77. data/lib/karafka/web/pro/ui/views/commands/_metadata.erb +33 -42
  78. data/lib/karafka/web/pro/ui/views/commands/_table.erb +9 -3
  79. data/lib/karafka/web/pro/ui/views/commands/index.erb +18 -12
  80. data/lib/karafka/web/pro/ui/views/commands/show.erb +24 -29
  81. data/lib/karafka/web/pro/ui/views/consumers/_breadcrumbs.erb +8 -8
  82. data/lib/karafka/web/pro/ui/views/consumers/_consumer.erb +13 -23
  83. data/lib/karafka/web/pro/ui/views/consumers/_consumer_controls.erb +51 -35
  84. data/lib/karafka/web/pro/ui/views/consumers/_consumer_performance.erb +1 -1
  85. data/lib/karafka/web/pro/ui/views/consumers/_tabs.erb +28 -30
  86. data/lib/karafka/web/pro/ui/views/consumers/consumer/_commands.erb +68 -28
  87. data/lib/karafka/web/pro/ui/views/consumers/consumer/_job.erb +1 -1
  88. data/lib/karafka/web/pro/ui/views/consumers/consumer/_metrics.erb +114 -133
  89. data/lib/karafka/web/pro/ui/views/consumers/consumer/_partition.erb +4 -4
  90. data/lib/karafka/web/pro/ui/views/consumers/consumer/_stopped.erb +6 -9
  91. data/lib/karafka/web/pro/ui/views/consumers/consumer/_subscription_group.erb +116 -126
  92. data/lib/karafka/web/pro/ui/views/consumers/consumer/_tabs.erb +26 -31
  93. data/lib/karafka/web/pro/ui/views/consumers/controls.erb +53 -57
  94. data/lib/karafka/web/pro/ui/views/consumers/details.erb +4 -17
  95. data/lib/karafka/web/pro/ui/views/consumers/index.erb +31 -34
  96. data/lib/karafka/web/pro/ui/views/consumers/pending_jobs.erb +41 -46
  97. data/lib/karafka/web/pro/ui/views/consumers/performance.erb +43 -47
  98. data/lib/karafka/web/pro/ui/views/consumers/running_jobs.erb +41 -46
  99. data/lib/karafka/web/pro/ui/views/consumers/subscriptions.erb +14 -17
  100. data/lib/karafka/web/pro/ui/views/dashboard/index.erb +67 -76
  101. data/lib/karafka/web/pro/ui/views/dlq/_breadcrumbs.erb +1 -1
  102. data/lib/karafka/web/pro/ui/views/dlq/_no_topics.erb +1 -7
  103. data/lib/karafka/web/pro/ui/views/dlq/_topic.erb +7 -10
  104. data/lib/karafka/web/pro/ui/views/dlq/index.erb +8 -10
  105. data/lib/karafka/web/pro/ui/views/errors/_breadcrumbs.erb +3 -3
  106. data/lib/karafka/web/pro/ui/views/errors/_error.erb +8 -5
  107. data/lib/karafka/web/pro/ui/views/errors/_selector.erb +12 -0
  108. data/lib/karafka/web/pro/ui/views/errors/_table.erb +5 -4
  109. data/lib/karafka/web/pro/ui/views/errors/index.erb +50 -15
  110. data/lib/karafka/web/pro/ui/views/errors/partition.erb +61 -14
  111. data/lib/karafka/web/pro/ui/views/errors/show.erb +28 -46
  112. data/lib/karafka/web/pro/ui/views/explorer/_breadcrumbs.erb +11 -3
  113. data/lib/karafka/web/pro/ui/views/explorer/_failed_deserialization.erb +8 -3
  114. data/lib/karafka/web/pro/ui/views/explorer/_message.erb +12 -6
  115. data/lib/karafka/web/pro/ui/views/explorer/_no_topics.erb +1 -5
  116. data/lib/karafka/web/pro/ui/views/explorer/_selector.erb +12 -0
  117. data/lib/karafka/web/pro/ui/views/explorer/_topic.erb +6 -8
  118. data/lib/karafka/web/pro/ui/views/explorer/index.erb +13 -15
  119. data/lib/karafka/web/pro/ui/views/explorer/message/_metadata.erb +68 -32
  120. data/lib/karafka/web/pro/ui/views/explorer/message/_payload.erb +17 -16
  121. data/lib/karafka/web/pro/ui/views/explorer/message/_resources_utilization.erb +127 -0
  122. data/lib/karafka/web/pro/ui/views/explorer/message/_too_big_to_be_displayed.erb +20 -0
  123. data/lib/karafka/web/pro/ui/views/explorer/messages/_detail.erb +1 -1
  124. data/lib/karafka/web/pro/ui/views/explorer/partition/_cleaned.erb +3 -5
  125. data/lib/karafka/web/pro/ui/views/explorer/partition/_empty.erb +3 -5
  126. data/lib/karafka/web/pro/ui/views/explorer/partition/_messages.erb +6 -3
  127. data/lib/karafka/web/pro/ui/views/explorer/partition.erb +67 -46
  128. data/lib/karafka/web/pro/ui/views/explorer/show.erb +85 -21
  129. data/lib/karafka/web/pro/ui/views/explorer/topic/_actions.erb +27 -0
  130. data/lib/karafka/web/pro/ui/views/explorer/topic/_empty.erb +3 -5
  131. data/lib/karafka/web/pro/ui/views/explorer/topic/_limited.erb +8 -10
  132. data/lib/karafka/web/pro/ui/views/explorer/topic.erb +24 -44
  133. data/lib/karafka/web/pro/ui/views/health/_breadcrumbs.erb +7 -7
  134. data/lib/karafka/web/pro/ui/views/health/_no_data.erb +1 -7
  135. data/lib/karafka/web/pro/ui/views/health/_partition.erb +3 -3
  136. data/lib/karafka/web/pro/ui/views/health/_partition_lags.erb +3 -3
  137. data/lib/karafka/web/pro/ui/views/health/_partition_offset.erb +2 -2
  138. data/lib/karafka/web/pro/ui/views/health/_partition_times.erb +3 -7
  139. data/lib/karafka/web/pro/ui/views/health/_table_metadata.erb +8 -0
  140. data/lib/karafka/web/pro/ui/views/health/_tabs.erb +32 -49
  141. data/lib/karafka/web/pro/ui/views/health/changes.erb +51 -51
  142. data/lib/karafka/web/pro/ui/views/health/cluster_lags.erb +28 -41
  143. data/lib/karafka/web/pro/ui/views/health/lags.erb +52 -52
  144. data/lib/karafka/web/pro/ui/views/health/offsets.erb +55 -55
  145. data/lib/karafka/web/pro/ui/views/health/overview.erb +60 -60
  146. data/lib/karafka/web/pro/ui/views/jobs/_job.erb +1 -1
  147. data/lib/karafka/web/pro/ui/views/jobs/_no_jobs.erb +1 -7
  148. data/lib/karafka/web/pro/ui/views/jobs/pending.erb +36 -38
  149. data/lib/karafka/web/pro/ui/views/jobs/running.erb +36 -38
  150. data/lib/karafka/web/pro/ui/views/routing/_consumer_group.erb +7 -12
  151. data/lib/karafka/web/pro/ui/views/routing/_topic.erb +13 -11
  152. data/lib/karafka/web/pro/ui/views/routing/index.erb +7 -9
  153. data/lib/karafka/web/pro/ui/views/routing/show.erb +41 -33
  154. data/lib/karafka/web/pro/ui/views/search/_fix_errors.erb +3 -0
  155. data/lib/karafka/web/pro/ui/views/search/_metadata.erb +71 -0
  156. data/lib/karafka/web/pro/ui/views/search/_no_results.erb +3 -0
  157. data/lib/karafka/web/pro/ui/views/search/_no_search_criteria.erb +5 -0
  158. data/lib/karafka/web/pro/ui/views/search/_search_criteria.erb +37 -0
  159. data/lib/karafka/web/pro/ui/views/search/_search_modal.erb +139 -0
  160. data/lib/karafka/web/pro/ui/views/search/_timeout.erb +3 -0
  161. data/lib/karafka/web/pro/ui/views/search/index.erb +29 -0
  162. data/lib/karafka/web/pro/ui/views/shared/_navigation.erb +80 -28
  163. data/lib/karafka/web/pro/ui/views/topics/_breadcrumbs.erb +14 -6
  164. data/lib/karafka/web/pro/ui/views/topics/_partition_offsets.erb +10 -0
  165. data/lib/karafka/web/pro/ui/views/topics/_tabs.erb +26 -32
  166. data/lib/karafka/web/pro/ui/views/topics/_topic.erb +7 -10
  167. data/lib/karafka/web/pro/ui/views/topics/config.erb +21 -25
  168. data/lib/karafka/web/pro/ui/views/topics/distribution/_badges.erb +10 -5
  169. data/lib/karafka/web/pro/ui/views/topics/distribution/_chart.erb +3 -1
  170. data/lib/karafka/web/pro/ui/views/topics/distribution/_limited.erb +1 -1
  171. data/lib/karafka/web/pro/ui/views/topics/distribution.erb +34 -39
  172. data/lib/karafka/web/pro/ui/views/topics/index.erb +13 -15
  173. data/lib/karafka/web/pro/ui/views/topics/offsets.erb +24 -0
  174. data/lib/karafka/web/pro/ui/views/topics/replication.erb +20 -24
  175. data/lib/karafka/web/processing/consumers/aggregators/metrics.rb +1 -1
  176. data/lib/karafka/web/processing/consumers/aggregators/state.rb +1 -1
  177. data/lib/karafka/web/processing/consumers/contracts/aggregated_stats.rb +1 -0
  178. data/lib/karafka/web/tracking/consumers/contracts/report.rb +6 -0
  179. data/lib/karafka/web/tracking/consumers/listeners/connections.rb +8 -6
  180. data/lib/karafka/web/tracking/consumers/listeners/processing.rb +2 -0
  181. data/lib/karafka/web/tracking/consumers/listeners/tags.rb +1 -1
  182. data/lib/karafka/web/tracking/consumers/reporter.rb +6 -8
  183. data/lib/karafka/web/tracking/consumers/sampler.rb +16 -5
  184. data/lib/karafka/web/ui/app.rb +20 -1
  185. data/lib/karafka/web/ui/base.rb +26 -20
  186. data/lib/karafka/web/ui/controllers/base_controller.rb +6 -4
  187. data/lib/karafka/web/ui/controllers/dashboard_controller.rb +8 -0
  188. data/lib/karafka/web/ui/controllers/requests/params.rb +16 -2
  189. data/lib/karafka/web/ui/controllers/support_controller.rb +17 -0
  190. data/lib/karafka/web/ui/controllers/ux_controller.rb +17 -0
  191. data/lib/karafka/web/ui/helpers/application_helper.rb +75 -42
  192. data/lib/karafka/web/ui/helpers/paths_helper.rb +24 -0
  193. data/lib/karafka/web/ui/helpers/tailwind_helper.rb +90 -0
  194. data/lib/karafka/web/ui/lib/sorter.rb +1 -1
  195. data/lib/karafka/web/ui/models/metrics/aggregated.rb +1 -0
  196. data/lib/karafka/web/ui/models/metrics/charts/topics.rb +36 -20
  197. data/lib/karafka/web/ui/models/status.rb +28 -1
  198. data/lib/karafka/web/ui/public/images/calendar.svg +3 -0
  199. data/lib/karafka/web/ui/public/javascripts/application.js +39 -15
  200. data/lib/karafka/web/ui/public/javascripts/application.min.js +64 -0
  201. data/lib/karafka/web/ui/public/javascripts/application.min.js.br +0 -0
  202. data/lib/karafka/web/ui/public/javascripts/application.min.js.gz +0 -0
  203. data/lib/karafka/web/ui/public/javascripts/charts/types/line.js +41 -9
  204. data/lib/karafka/web/ui/public/javascripts/components/btn_toggle_manager.js +37 -0
  205. data/lib/karafka/web/ui/public/javascripts/{live_poll.js → components/live_poll.js} +44 -8
  206. data/lib/karafka/web/ui/public/javascripts/{offset_datetime.js → components/offset_datetime.js} +1 -1
  207. data/lib/karafka/web/ui/public/javascripts/components/search.js +102 -0
  208. data/lib/karafka/web/ui/public/javascripts/components/tabs_manager.js +84 -0
  209. data/lib/karafka/web/ui/public/javascripts/components/theme_manager.js +59 -0
  210. data/lib/karafka/web/ui/public/javascripts/components/turbo_tracker.js +30 -0
  211. data/lib/karafka/web/ui/public/javascripts/libs/datepicker.js +2 -2
  212. data/lib/karafka/web/ui/public/javascripts/libs/turbo.js +6618 -0
  213. data/lib/karafka/web/ui/public/stylesheets/application.css +16 -113
  214. data/lib/karafka/web/ui/public/stylesheets/application.min.css +13 -0
  215. data/lib/karafka/web/ui/public/stylesheets/application.min.css.br +0 -0
  216. data/lib/karafka/web/ui/public/stylesheets/application.min.css.gz +0 -0
  217. data/lib/karafka/web/ui/public/stylesheets/libs/highlight_dark.min.css +8 -0
  218. data/lib/karafka/web/ui/public/stylesheets/libs/highlight_dark.min.css.br +0 -0
  219. data/lib/karafka/web/ui/public/stylesheets/libs/highlight_dark.min.css.gz +0 -0
  220. data/lib/karafka/web/ui/public/stylesheets/libs/highlight_light.min.css.br +0 -0
  221. data/lib/karafka/web/ui/public/stylesheets/libs/highlight_light.min.css.gz +0 -0
  222. data/lib/karafka/web/ui/public/stylesheets/libs/tailwind.css +391 -0
  223. data/lib/karafka/web/ui/views/cluster/_breadcrumbs.erb +3 -3
  224. data/lib/karafka/web/ui/views/cluster/_tabs.erb +14 -24
  225. data/lib/karafka/web/ui/views/cluster/brokers.erb +20 -22
  226. data/lib/karafka/web/ui/views/cluster/replication.erb +28 -32
  227. data/lib/karafka/web/ui/views/consumers/_assignments_badges.erb +1 -1
  228. data/lib/karafka/web/ui/views/consumers/_breadcrumbs.erb +5 -0
  229. data/lib/karafka/web/ui/views/consumers/_consumer.erb +9 -13
  230. data/lib/karafka/web/ui/views/consumers/_no_consumers.erb +2 -8
  231. data/lib/karafka/web/ui/views/consumers/_summary.erb +34 -45
  232. data/lib/karafka/web/ui/views/consumers/_tabs.erb +35 -0
  233. data/lib/karafka/web/ui/views/consumers/index.erb +31 -33
  234. data/lib/karafka/web/ui/views/dashboard/_counters.erb +76 -0
  235. data/lib/karafka/web/ui/views/dashboard/_feature_pro.erb +6 -2
  236. data/lib/karafka/web/ui/views/dashboard/_not_enough_data.erb +3 -15
  237. data/lib/karafka/web/ui/views/dashboard/_ranges_selector.erb +12 -12
  238. data/lib/karafka/web/ui/views/dashboard/index.erb +78 -52
  239. data/lib/karafka/web/ui/views/errors/_breadcrumbs.erb +2 -2
  240. data/lib/karafka/web/ui/views/errors/_detail.erb +1 -3
  241. data/lib/karafka/web/ui/views/errors/_error.erb +3 -5
  242. data/lib/karafka/web/ui/views/errors/index.erb +34 -44
  243. data/lib/karafka/web/ui/views/errors/show.erb +29 -47
  244. data/lib/karafka/web/ui/views/jobs/_breadcrumbs.erb +3 -3
  245. data/lib/karafka/web/ui/views/jobs/_job.erb +1 -1
  246. data/lib/karafka/web/ui/views/jobs/_no_jobs.erb +1 -7
  247. data/lib/karafka/web/ui/views/jobs/_tabs.erb +14 -24
  248. data/lib/karafka/web/ui/views/jobs/pending.erb +30 -32
  249. data/lib/karafka/web/ui/views/jobs/running.erb +30 -32
  250. data/lib/karafka/web/ui/views/layout.erb +37 -21
  251. data/lib/karafka/web/ui/views/routing/_breadcrumbs.erb +2 -2
  252. data/lib/karafka/web/ui/views/routing/_consumer_group.erb +7 -12
  253. data/lib/karafka/web/ui/views/routing/_topic.erb +3 -5
  254. data/lib/karafka/web/ui/views/routing/index.erb +7 -9
  255. data/lib/karafka/web/ui/views/routing/show.erb +30 -22
  256. data/lib/karafka/web/ui/views/shared/_become_pro.erb +8 -8
  257. data/lib/karafka/web/ui/views/shared/_brand.erb +2 -2
  258. data/lib/karafka/web/ui/views/shared/_breadcrumbs.erb +23 -0
  259. data/lib/karafka/web/ui/views/shared/_content.erb +2 -28
  260. data/lib/karafka/web/ui/views/shared/_controls.erb +15 -0
  261. data/lib/karafka/web/ui/views/shared/_flashes.erb +5 -7
  262. data/lib/karafka/web/ui/views/shared/_header.erb +14 -19
  263. data/lib/karafka/web/ui/views/shared/_navigation.erb +84 -28
  264. data/lib/karafka/web/ui/views/shared/_no_paginated_data.erb +5 -9
  265. data/lib/karafka/web/ui/views/shared/_pagination.erb +11 -11
  266. data/lib/karafka/web/ui/views/shared/_tab_nav.erb +4 -5
  267. data/lib/karafka/web/ui/views/shared/_title.erb +5 -0
  268. data/lib/karafka/web/ui/views/shared/alerts/_box_error.erb +15 -0
  269. data/lib/karafka/web/ui/views/shared/alerts/_box_info.erb +15 -0
  270. data/lib/karafka/web/ui/views/shared/alerts/_box_primary.erb +15 -0
  271. data/lib/karafka/web/ui/views/shared/alerts/_box_secondary.erb +15 -0
  272. data/lib/karafka/web/ui/views/shared/alerts/_box_success.erb +15 -0
  273. data/lib/karafka/web/ui/views/shared/alerts/_box_warning.erb +15 -0
  274. data/lib/karafka/web/ui/views/shared/alerts/_error.erb +4 -0
  275. data/lib/karafka/web/ui/views/shared/alerts/_info.erb +5 -2
  276. data/lib/karafka/web/ui/views/shared/alerts/_primary.erb +4 -0
  277. data/lib/karafka/web/ui/views/shared/alerts/_secondary.erb +4 -0
  278. data/lib/karafka/web/ui/views/shared/alerts/_success.erb +4 -0
  279. data/lib/karafka/web/ui/views/shared/alerts/_warning.erb +4 -0
  280. data/lib/karafka/web/ui/views/shared/charts/_line.erb +1 -1
  281. data/lib/karafka/web/ui/views/shared/exceptions/not_allowed.erb +14 -19
  282. data/lib/karafka/web/ui/views/shared/exceptions/not_found.erb +16 -21
  283. data/lib/karafka/web/ui/views/shared/exceptions/pro_only.erb +16 -28
  284. data/lib/karafka/web/ui/views/shared/icons/_arrow_down_on_square.erb +3 -0
  285. data/lib/karafka/web/ui/views/shared/icons/_arrow_down_tray.erb +3 -0
  286. data/lib/karafka/web/ui/views/shared/icons/_arrow_on_squares.erb +3 -0
  287. data/lib/karafka/web/ui/views/shared/icons/_arrow_path_rounded.erb +3 -0
  288. data/lib/karafka/web/ui/views/shared/icons/_arrow_uturn_right.erb +3 -0
  289. data/lib/karafka/web/ui/views/shared/icons/_arrows_right_left.erb +3 -0
  290. data/lib/karafka/web/ui/views/shared/icons/_blocks.erb +3 -0
  291. data/lib/karafka/web/ui/views/shared/icons/_book_open.erb +3 -0
  292. data/lib/karafka/web/ui/views/shared/icons/_bug.erb +3 -0
  293. data/lib/karafka/web/ui/views/shared/icons/_burger.erb +14 -0
  294. data/lib/karafka/web/ui/views/shared/icons/_calendar_days.erb +3 -0
  295. data/lib/karafka/web/ui/views/shared/icons/_chart_bar.erb +3 -0
  296. data/lib/karafka/web/ui/views/shared/icons/_check_badge.erb +3 -0
  297. data/lib/karafka/web/ui/views/shared/icons/_check_circle.erb +3 -0
  298. data/lib/karafka/web/ui/views/shared/icons/_circle_stack.erb +3 -0
  299. data/lib/karafka/web/ui/views/shared/icons/_cpu.erb +3 -0
  300. data/lib/karafka/web/ui/views/shared/icons/_document_glass.erb +3 -0
  301. data/lib/karafka/web/ui/views/shared/icons/_exclamation_triangle.erb +3 -0
  302. data/lib/karafka/web/ui/views/shared/icons/_eye.erb +4 -0
  303. data/lib/karafka/web/ui/views/shared/icons/_gear.erb +4 -0
  304. data/lib/karafka/web/ui/views/shared/icons/_github.erb +13 -0
  305. data/lib/karafka/web/ui/views/shared/icons/_globe.erb +3 -0
  306. data/lib/karafka/web/ui/views/shared/icons/_heart.erb +3 -0
  307. data/lib/karafka/web/ui/views/shared/icons/_home.erb +3 -0
  308. data/lib/karafka/web/ui/views/shared/icons/_info_circle.erb +3 -0
  309. data/lib/karafka/web/ui/views/shared/icons/_lifebuoy.erb +3 -0
  310. data/lib/karafka/web/ui/views/shared/icons/_light_bulb.erb +3 -0
  311. data/lib/karafka/web/ui/views/shared/icons/_list_bullets.erb +3 -0
  312. data/lib/karafka/web/ui/views/shared/icons/_magnifying_glass.erb +3 -0
  313. data/lib/karafka/web/ui/views/shared/icons/_moon.erb +3 -0
  314. data/lib/karafka/web/ui/views/shared/icons/_offices.erb +3 -0
  315. data/lib/karafka/web/ui/views/shared/icons/_pause.erb +3 -0
  316. data/lib/karafka/web/ui/views/shared/icons/_pause_circle.erb +3 -0
  317. data/lib/karafka/web/ui/views/shared/icons/_play_circle.erb +4 -0
  318. data/lib/karafka/web/ui/views/shared/icons/_question_circle.erb +3 -0
  319. data/lib/karafka/web/ui/views/shared/icons/_queue_list.erb +3 -0
  320. data/lib/karafka/web/ui/views/shared/icons/_refresh.erb +3 -0
  321. data/lib/karafka/web/ui/views/shared/icons/_slack.erb +16 -0
  322. data/lib/karafka/web/ui/views/shared/icons/_stop.erb +3 -0
  323. data/lib/karafka/web/ui/views/shared/icons/_sun.erb +3 -0
  324. data/lib/karafka/web/ui/views/shared/icons/_x_circle.erb +3 -0
  325. data/lib/karafka/web/ui/views/shared/icons/_x_mark.erb +3 -0
  326. data/lib/karafka/web/ui/views/status/_breadcrumbs.erb +1 -1
  327. data/lib/karafka/web/ui/views/status/_failure.erb +2 -13
  328. data/lib/karafka/web/ui/views/status/_halted.erb +2 -10
  329. data/lib/karafka/web/ui/views/status/_info.erb +2 -13
  330. data/lib/karafka/web/ui/views/status/_success.erb +2 -10
  331. data/lib/karafka/web/ui/views/status/_warning.erb +2 -13
  332. data/lib/karafka/web/ui/views/status/failures/_connection.erb +2 -2
  333. data/lib/karafka/web/ui/views/status/failures/_consumers_reports.erb +3 -3
  334. data/lib/karafka/web/ui/views/status/failures/_consumers_reports_schema_state.erb +4 -4
  335. data/lib/karafka/web/ui/views/status/failures/_enabled.erb +2 -2
  336. data/lib/karafka/web/ui/views/status/failures/_initial_consumers_metrics.erb +6 -6
  337. data/lib/karafka/web/ui/views/status/failures/_initial_consumers_state.erb +6 -6
  338. data/lib/karafka/web/ui/views/status/failures/_live_reporting.erb +2 -2
  339. data/lib/karafka/web/ui/views/status/failures/_materializing_lag.erb +11 -0
  340. data/lib/karafka/web/ui/views/status/failures/_partitions.erb +3 -3
  341. data/lib/karafka/web/ui/views/status/failures/_state_calculation.erb +2 -2
  342. data/lib/karafka/web/ui/views/status/failures/_topics.erb +3 -3
  343. data/lib/karafka/web/ui/views/status/info/_components.erb +14 -41
  344. data/lib/karafka/web/ui/views/status/show.erb +165 -154
  345. data/lib/karafka/web/ui/views/status/warnings/_connection.erb +3 -3
  346. data/lib/karafka/web/ui/views/status/warnings/_pro_subscription.erb +2 -2
  347. data/lib/karafka/web/ui/views/status/warnings/_replication.erb +2 -2
  348. data/lib/karafka/web/ui/views/status/warnings/_routing_topics_presence.erb +1 -1
  349. data/lib/karafka/web/ui/views/support/_breadcrumbs.erb +5 -0
  350. data/lib/karafka/web/ui/views/support/show.erb +71 -0
  351. data/lib/karafka/web/ui/views/ux/_alerts.erb +25 -0
  352. data/lib/karafka/web/ui/views/ux/_badges.erb +21 -0
  353. data/lib/karafka/web/ui/views/ux/_breadcrumbs.erb +5 -0
  354. data/lib/karafka/web/ui/views/ux/_buttons.erb +47 -0
  355. data/lib/karafka/web/ui/views/ux/_card_detail.erb +15 -0
  356. data/lib/karafka/web/ui/views/ux/_card_metric.erb +123 -0
  357. data/lib/karafka/web/ui/views/ux/_card_summary.erb +72 -0
  358. data/lib/karafka/web/ui/views/ux/_card_support.erb +39 -0
  359. data/lib/karafka/web/ui/views/ux/_code.erb +9 -0
  360. data/lib/karafka/web/ui/views/ux/_data_table.erb +52 -0
  361. data/lib/karafka/web/ui/views/ux/_headers.erb +2 -0
  362. data/lib/karafka/web/ui/views/ux/_icons.erb +9 -0
  363. data/lib/karafka/web/ui/views/ux/_pagination.erb +32 -0
  364. data/lib/karafka/web/ui/views/ux/_row_table.erb +52 -0
  365. data/lib/karafka/web/ui/views/ux/_status_rows.erb +53 -0
  366. data/lib/karafka/web/ui/views/ux/_tabs.erb +14 -0
  367. data/lib/karafka/web/ui/views/ux/_text.erb +2 -0
  368. data/lib/karafka/web/ui/views/ux/_topic_tiles.erb +42 -0
  369. data/lib/karafka/web/ui/views/ux/show.erb +19 -0
  370. data/lib/karafka/web/version.rb +1 -1
  371. data/lib/karafka/web.rb +2 -0
  372. data/package-lock.json +4158 -0
  373. data/package.json +15 -0
  374. data/postcss.config.js +6 -0
  375. data/tailwind.config.js +16 -0
  376. data.tar.gz.sig +4 -0
  377. metadata +203 -51
  378. metadata.gz.sig +0 -0
  379. data/lib/karafka/web/management/migrations/0_set_initial_consumers_metrics.rb +0 -36
  380. data/lib/karafka/web/management/migrations/0_set_initial_consumers_state.rb +0 -43
  381. data/lib/karafka/web/management/migrations/1699543515_fill_missing_received_and_sent_bytes_in_consumers_metrics.rb +0 -26
  382. data/lib/karafka/web/management/migrations/1699543515_fill_missing_received_and_sent_bytes_in_consumers_state.rb +0 -23
  383. data/lib/karafka/web/management/migrations/1700234522_introduce_waiting_in_consumers_metrics.rb +0 -24
  384. data/lib/karafka/web/management/migrations/1700234522_introduce_waiting_in_consumers_state.rb +0 -20
  385. data/lib/karafka/web/management/migrations/1700234522_remove_processing_from_consumers_metrics.rb +0 -24
  386. data/lib/karafka/web/management/migrations/1700234522_remove_processing_from_consumers_state.rb +0 -20
  387. data/lib/karafka/web/management/migrations/1704722380_split_listeners_into_active_and_paused_in_metrics.rb +0 -36
  388. data/lib/karafka/web/management/migrations/1704722380_split_listeners_into_active_and_paused_in_states.rb +0 -32
  389. data/lib/karafka/web/management/migrations/1706607960_introduce_lag_total_in_metrics.rb +0 -38
  390. data/lib/karafka/web/management/migrations/1706607960_introduce_lag_total_in_states.rb +0 -22
  391. data/lib/karafka/web/management/migrations/1706611396_rename_lag_total_to_lag_hybrid_in_metrics.rb +0 -36
  392. data/lib/karafka/web/management/migrations/1706611396_rename_lag_total_to_lag_hybrid_in_states.rb +0 -21
  393. data/lib/karafka/web/pro/ui/views/cluster/brokers.erb +0 -27
  394. data/lib/karafka/web/pro/ui/views/commands/_details.erb +0 -26
  395. data/lib/karafka/web/pro/ui/views/consumers/_counters.erb +0 -72
  396. data/lib/karafka/web/pro/ui/views/consumers/consumer/_title.erb +0 -5
  397. data/lib/karafka/web/pro/ui/views/errors/_title_with_select.erb +0 -31
  398. data/lib/karafka/web/pro/ui/views/explorer/message/_message_actions.erb +0 -18
  399. data/lib/karafka/web/pro/ui/views/explorer/message/_payload_actions.erb +0 -19
  400. data/lib/karafka/web/pro/ui/views/explorer/partition/_details.erb +0 -35
  401. data/lib/karafka/web/pro/ui/views/explorer/topic/_details.erb +0 -23
  402. data/lib/karafka/web/pro/ui/views/health/_consumer_group_header.erb +0 -14
  403. data/lib/karafka/web/ui/controllers/responses/deny.rb +0 -15
  404. data/lib/karafka/web/ui/helpers/alerts_helper.rb +0 -23
  405. data/lib/karafka/web/ui/lib/safe_runner.rb +0 -59
  406. data/lib/karafka/web/ui/models/visibility_filter.rb +0 -49
  407. data/lib/karafka/web/ui/public/javascripts/libs/bootstrap.min.js +0 -6
  408. data/lib/karafka/web/ui/public/javascripts/tabs_manager.js +0 -57
  409. data/lib/karafka/web/ui/public/stylesheets/libs/bootstrap.min.css +0 -6
  410. data/lib/karafka/web/ui/views/consumers/_counters.erb +0 -62
  411. data/lib/karafka/web/ui/views/errors/_watermark_offsets.erb +0 -10
  412. data/lib/karafka/web/ui/views/shared/_feature_pro.erb +0 -4
  413. data/lib/karafka/web/ui/views/shared/_footer.erb +0 -22
  414. data/lib/karafka/web/ui/views/shared/_live_poll.erb +0 -7
  415. /data/lib/karafka/web/management/migrations/{0_base.rb → base.rb} +0 -0
  416. /data/lib/karafka/web/ui/public/javascripts/{charts.js → components/charts.js} +0 -0
  417. /data/lib/karafka/web/ui/public/stylesheets/libs/{highlight.min.css → highlight_light.min.css} +0 -0
@@ -66,16 +66,18 @@ module Karafka
66
66
  Responses::File.new(content, file_name)
67
67
  end
68
68
 
69
- # Builds a halt 403 response
70
- def deny
71
- Responses::Deny.new
69
+ # Raises the deny error so we can render a deny block
70
+ # We handle this that way so we can raise this from any place we want as long as within
71
+ # the Roda flow and not only from controllers
72
+ def deny!
73
+ raise Errors::Ui::ForbiddenError
72
74
  end
73
75
 
74
76
  # @param resources [Hash, Array, Lib::HashProxy] object for sorting
75
77
  # @return [Hash, Array, Lib::HashProxy] sorted results
76
78
  def refine(resources)
77
79
  Lib::Sorter.new(
78
- @params.sort,
80
+ @params.current_sort,
79
81
  allowed_attributes: self.class.sortable_attributes
80
82
  ).call(resources)
81
83
  end
@@ -23,6 +23,14 @@ module Karafka
23
23
  @aggregated, @params.current_range
24
24
  )
25
25
 
26
+ @topics = Models::Metrics::Topics.new(
27
+ current_metrics.to_h.fetch(:consumer_groups)
28
+ )
29
+
30
+ @topics_charts = Models::Metrics::Charts::Topics.new(
31
+ @topics, @params.current_range
32
+ )
33
+
26
34
  render
27
35
  end
28
36
  end
@@ -24,9 +24,18 @@ module Karafka
24
24
  @request_params = request_params
25
25
  end
26
26
 
27
+ # @return [Hash] current search or empty if no search query present
28
+ def current_search
29
+ return @current_search if @current_search
30
+
31
+ search = @request_params['search']
32
+
33
+ @current_search = search.is_a?(Hash) ? search : {}
34
+ end
35
+
27
36
  # @return [String] sort query value
28
- def sort
29
- @sort ||= @request_params['sort'].to_s.downcase
37
+ def current_sort
38
+ @current_sort ||= @request_params['sort'].to_s.downcase
30
39
  end
31
40
 
32
41
  # @return [Integer] current page for paginated views
@@ -55,6 +64,11 @@ module Karafka
55
64
  offset < -1 ? -1 : offset
56
65
  end
57
66
  end
67
+
68
+ # @return [Integer] currently selected partition or -1 if nothing provided
69
+ def current_partition
70
+ @current_partition ||= @request_params.fetch('partition', -1).to_i
71
+ end
58
72
  end
59
73
  end
60
74
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Web
5
+ module Ui
6
+ module Controllers
7
+ # Controller to display support page
8
+ class SupportController < BaseController
9
+ # Display the support page
10
+ def show
11
+ render
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Web
5
+ module Ui
6
+ module Controllers
7
+ # Controller to display UX components page
8
+ class UxController < BaseController
9
+ # Display the UX components page
10
+ def show
11
+ render
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -61,16 +61,16 @@ module Karafka
61
61
  #
62
62
  # @param status [String] status
63
63
  # @return [String] background style
64
- def status_bg(status)
64
+ def status_badge(status)
65
65
  case status
66
- when 'initialized' then 'bg-success'
67
- when 'supervising' then 'bg-success'
68
- when 'running' then 'bg-success'
69
- when 'quieting' then 'bg-warning'
70
- when 'quiet' then 'bg-warning text-dark'
71
- when 'stopping' then 'bg-warning text-dark'
72
- when 'stopped' then 'bg-danger'
73
- when 'terminated' then 'bg-danger'
66
+ when 'initialized' then 'badge-success'
67
+ when 'supervising' then 'badge-success'
68
+ when 'running' then 'badge-success'
69
+ when 'quieting' then 'badge-warning'
70
+ when 'quiet' then 'badge-warning'
71
+ when 'stopping' then 'badge-warning'
72
+ when 'stopped' then 'badge-error'
73
+ when 'terminated' then 'badge-error'
74
74
  else
75
75
  raise ::Karafka::Errors::UnsupportedCaseError, status
76
76
  end
@@ -80,10 +80,10 @@ module Karafka
80
80
  #
81
81
  # @param trend [Numeric] lag trend
82
82
  # @return [String] bg classes
83
- def lag_trend_bg(trend)
84
- bg = 'bg-success' if trend.negative?
85
- bg ||= 'bg-warning text-dark' if trend.positive?
86
- bg ||= 'bg-secondary'
83
+ def lag_trend_badge(trend)
84
+ bg = 'badge-success' if trend.negative?
85
+ bg ||= 'badge-warning' if trend.positive?
86
+ bg ||= 'badge-secondary'
87
87
  bg
88
88
  end
89
89
 
@@ -93,20 +93,20 @@ module Karafka
93
93
  # @return [String] tags badges
94
94
  def tags(tags_array)
95
95
  tags_array
96
- .map { |tag| %(<span class="badge bg-info">#{tag}</span>) }
96
+ .map { |tag| %(<span class="badge badge-info">#{tag}</span>) }
97
97
  .join(' ')
98
98
  end
99
99
 
100
100
  # Takes a kafka report state and recommends background style color
101
101
  # @param state [String] state
102
102
  # @return [String] background style
103
- def kafka_state_bg(state)
103
+ def kafka_state_badge(state)
104
104
  case state
105
- when 'up' then 'bg-success text-white'
106
- when 'active' then 'bg-success text-white'
107
- when 'steady' then 'bg-success text-white'
105
+ when 'up' then 'badge-success'
106
+ when 'active' then 'badge-success'
107
+ when 'steady' then 'badge-success'
108
108
  else
109
- 'bg-warning text-dark'
109
+ 'badge-warning'
110
110
  end
111
111
  end
112
112
 
@@ -116,7 +116,7 @@ module Karafka
116
116
  return '0' if !mem_kb || mem_kb.zero?
117
117
 
118
118
  if mem_kb < 10_240
119
- "#{number_with_delimiter(mem_kb)} KB"
119
+ "#{number_with_delimiter(mem_kb.round(4))} KB"
120
120
  elsif mem_kb < 1_000_000
121
121
  "#{number_with_delimiter((mem_kb / 1024.0).to_i)} MB"
122
122
  else
@@ -151,6 +151,27 @@ module Karafka
151
151
  %(<span title="#{stamp}">#{time}</span>)
152
152
  end
153
153
 
154
+ # Converts raw second count into human readable form like "12.2 minutes". etc based on
155
+ # number of seconds
156
+ #
157
+ # @param seconds [Numeric] number of seconds
158
+ # @return [String] human readable time
159
+ def human_readable_time(seconds)
160
+ case seconds
161
+ when 0..59
162
+ "#{seconds.round(2)} seconds"
163
+ when 60..3_599
164
+ minutes = seconds / 60.0
165
+ "#{minutes.round(2)} minutes"
166
+ when 3_600..86_399
167
+ hours = seconds / 3_600.0
168
+ "#{hours.round(2)} hours"
169
+ else
170
+ days = seconds / 86_400.0
171
+ "#{days.round(2)} days"
172
+ end
173
+ end
174
+
154
175
  # @param state [String] poll state
155
176
  # @param state_ch [Integer] time until next change of the poll state
156
177
  # (from paused to active)
@@ -162,12 +183,12 @@ module Karafka
162
183
  # If state is active, there is no date of change
163
184
  if state == 'active'
164
185
  %(
165
- <span class="badge #{kafka_state_bg(state)} mt-1 mb-1">#{state}</span>
186
+ <span class="badge #{kafka_state_badge(state)}">#{state}</span>
166
187
  )
167
188
  elsif state_ch_in_seconds > year_in_seconds
168
189
  %(
169
190
  <span
170
- class="badge #{kafka_state_bg(state)} mt-1 mb-1"
191
+ class="badge #{kafka_state_badge(state)}"
171
192
  title="until manual resume"
172
193
  >
173
194
  #{state}
@@ -176,7 +197,7 @@ module Karafka
176
197
  else
177
198
  %(
178
199
  <span
179
- class="badge #{kafka_state_bg(state)} time-title mt-1 mb-1"
200
+ class="badge #{kafka_state_badge(state)} time-title"
180
201
  title="#{Time.now + state_ch_in_seconds}"
181
202
  >
182
203
  #{state}
@@ -191,7 +212,7 @@ module Karafka
191
212
  def lag_with_label(lag)
192
213
  if lag.negative?
193
214
  title = 'Not available until first offset commit'
194
- %(<span class="badge bg-secondary" title="#{title}">N/A</span>)
215
+ %(<span class="badge badge-secondary" title="#{title}">N/A</span>)
195
216
  else
196
217
  lag.to_s
197
218
  end
@@ -207,7 +228,7 @@ module Karafka
207
228
  def offset_with_label(topic_name, partition_id, offset, explore: false)
208
229
  if offset.negative?
209
230
  title = 'Not available until first offset commit'
210
- %(<span class="badge bg-secondary" title="#{title}">N/A</span>)
231
+ %(<span class="badge badge-secondary" title="#{title}">N/A</span>)
211
232
  elsif explore
212
233
  path = explorer_path(topic_name, partition_id, offset)
213
234
  %(<a href="#{path}">#{offset}</a>)
@@ -226,29 +247,34 @@ module Karafka
226
247
  when :at_risk
227
248
  'bg-warning bg-opacity-25'
228
249
  when :stopped
229
- 'bg-danger bg-opacity-25'
250
+ 'bg-error bg-opacity-25'
251
+ else
252
+ raise ::Karafka::Errors::UnsupportedCaseError
253
+ end
254
+ end
255
+
256
+ # @param details [::Karafka::Web::Ui::Models::Partition] partition information with
257
+ # lso risk state info
258
+ # @return [String] background classes for row marking
259
+ def lso_risk_state_badge(details)
260
+ case details.lso_risk_state
261
+ when :active
262
+ ''
263
+ when :at_risk
264
+ 'badge-warning'
265
+ when :stopped
266
+ 'badge-error'
230
267
  else
231
268
  raise ::Karafka::Errors::UnsupportedCaseError
232
269
  end
233
270
  end
234
271
 
235
- # Returns the view title html code
272
+ # Sets the particular page title
236
273
  #
237
274
  # @param title [String] page title
238
- # @param hr [Boolean] should we add the hr tag at the end
239
275
  # @return [String] title html
240
- def view_title(title, hr: false)
241
- <<-HTML
242
- <div class="container mb-5">
243
- <div class="row">
244
- <h3>
245
- #{title}
246
- </h3>
247
- </div>
248
-
249
- #{hr ? '<hr/>' : ''}
250
- </div>
251
- HTML
276
+ def view_title(title)
277
+ content_for(:title) { title }
252
278
  end
253
279
 
254
280
  # @param hash [Hash] we want to flatten
@@ -295,12 +321,12 @@ module Karafka
295
321
  path = current_path(sort: desc)
296
322
  full_name = "#{name}&nbsp;#{arrow_both}"
297
323
 
298
- if params.sort == desc
324
+ if params.current_sort == desc
299
325
  path = current_path(sort: asc)
300
326
  full_name = "#{name}&nbsp;#{rev ? arrow_up : arrow_down}"
301
327
  end
302
328
 
303
- if params.sort == asc
329
+ if params.current_sort == asc
304
330
  path = current_path(sort: desc)
305
331
  full_name = "#{name}&nbsp;#{rev ? arrow_down : arrow_up}"
306
332
  end
@@ -336,6 +362,13 @@ module Karafka
336
362
 
337
363
  %(<span title="#{string}">#{truncated}</span>)
338
364
  end
365
+
366
+ # Renders the svg icon out of our icon set
367
+ # @param name [String, Symbol] name of the icon
368
+ # @return [String] svg icon
369
+ def icon(name)
370
+ render "shared/icons/_#{name}"
371
+ end
339
372
  end
340
373
  end
341
374
  end
@@ -6,6 +6,30 @@ module Karafka
6
6
  module Helpers
7
7
  # Helper for web ui paths builders
8
8
  module PathsHelper
9
+ # Helper method to flatten nested hashes and arrays
10
+ # @param prefix [String] The prefix for nested keys, initially an empty string.
11
+ # @param hash [Hash, Array] The nested hash or array to be flattened.
12
+ # @param [Hash] result The hash to store the flattened key-value pairs.
13
+ # @return [Hash] The flattened hash with keys in bracket notation suitable for URL
14
+ # encoding.
15
+ def flatten_params(prefix, hash, result = {})
16
+ if hash.is_a?(Hash)
17
+ hash.each do |k, v|
18
+ new_prefix = prefix.empty? ? k.to_s : "#{prefix}[#{k}]"
19
+ flatten_params(new_prefix, v, result)
20
+ end
21
+ elsif hash.is_a?(Array)
22
+ hash.each_with_index do |v, i|
23
+ new_prefix = "#{prefix}[#{i}]"
24
+ flatten_params(new_prefix, v, result)
25
+ end
26
+ else
27
+ result[prefix] = hash.to_s
28
+ end
29
+
30
+ result
31
+ end
32
+
9
33
  # Generates a full path with the root path out of the provided arguments
10
34
  #
11
35
  # @param args [Array<String, Numeric>] arguments that will make the path
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Web
5
+ module Ui
6
+ module Helpers
7
+ # Helper for generating tailwind rendering components with Ruby
8
+ # Simplifies many places in the UI
9
+ module TailwindHelper
10
+ # style types of components we support
11
+ TYPES = %i[
12
+ info
13
+ error
14
+ warning
15
+ success
16
+ primary
17
+ secondary
18
+ ].freeze
19
+
20
+ # @return [Array<Symbol>] style types of components we support
21
+ def tailwind_types
22
+ TYPES
23
+ end
24
+
25
+ # Renders a plain badge
26
+ # @param content [String] badge content
27
+ # @param classes [String] extra css classes
28
+ # @return [String] badge html
29
+ def badge(content, classes: '')
30
+ %(<span class="badge #{classes}">#{content}</span>)
31
+ end
32
+
33
+ # Renders a link to with button styling
34
+ # @param name [String] button name
35
+ # @param path [String] path to where to go
36
+ # @param classes [String] extra css classes
37
+ # @return [String] button link html
38
+ def link_button(name, path, classes: '')
39
+ %(<a href="#{path}" class="btn #{classes}">#{name}</a>)
40
+ end
41
+
42
+ # Defines various methods for badges and links that simplify defining them without
43
+ # having to provide whole classes scopes always.
44
+ TYPES.each do |type|
45
+ define_method :"badge_#{type}" do |content, classes: ''|
46
+ badge(content, classes: "#{classes} badge-#{type}")
47
+ end
48
+
49
+ define_method :"badge_#{type}_sm" do |content, classes: ''|
50
+ badge(content, classes: "#{classes} badge-#{type} badge-sm")
51
+ end
52
+
53
+ define_method :"link_button_#{type}" do |name, path, classes: ''|
54
+ link_button(name, path, classes: "#{classes} btn-#{type}")
55
+ end
56
+
57
+ define_method :"link_button_#{type}_sm" do |name, path, classes: ''|
58
+ link_button(name, path, classes: "#{classes} btn-#{type} btn-sm")
59
+ end
60
+
61
+ # @param message [String] alert message
62
+ # @return [String] html with alert
63
+ define_method :"alert_#{type}" do |message|
64
+ partial(
65
+ "shared/alerts/#{type}",
66
+ locals: {
67
+ message: message
68
+ }
69
+ )
70
+ end
71
+
72
+ # @param message [String] alert message
73
+ # @return [String] html with alert
74
+ define_method :"alert_box_#{type}" do |title, description = nil, &block|
75
+ description = capture_erb(&block) if block
76
+
77
+ inject_erb partial(
78
+ "shared/alerts/box_#{type}",
79
+ locals: {
80
+ title: title,
81
+ description: description
82
+ }
83
+ )
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -33,7 +33,7 @@ module Karafka
33
33
  @field = '' unless allowed_attributes.include?(@field)
34
34
 
35
35
  # Things we have already seen and sorted. Prevents crashing on the circular
36
- # dependencies sorting when same resources are present in different parts of the three
36
+ # dependencies sorting when same resources are present in different parts of the tree
37
37
  @seen = {}
38
38
  end
39
39
 
@@ -24,6 +24,7 @@ module Karafka
24
24
  #
25
25
  # If at least two elements do not exist for given delta range, we keep it empty
26
26
  DELTA_KEYS = %i[
27
+ jobs
27
28
  batches
28
29
  messages
29
30
  errors
@@ -47,48 +47,64 @@ module Karafka
47
47
  per_topic.merge('total sum' => total.to_a).to_json
48
48
  end
49
49
 
50
- # @return [String] JSON with producers pace that represents high-watermarks sum for
51
- # each topic
52
- def topics_pace
53
- topics = {}
50
+ # @return [String] JSON with per-topic, highest LSO freeze duration. Useful for
51
+ # debugging of issues arising from hanging transactions
52
+ def max_lso
53
+ topics = Hash.new { |h, k| h[k] = Hash.new { |h2, k2| h2[k2] = [] } }
54
54
 
55
55
  @data.to_h.each do |topic, metrics|
56
56
  topic_without_cg = topic.split('[').first
57
57
 
58
- # If we've already seen this topic data, we can skip
59
- next if topics.include?(topic_without_cg)
58
+ metrics.each do |current|
59
+ ls_offset_fd = current.last[:ls_offset_fd] || 0
60
60
 
61
- topics[topic_without_cg] = metrics.map do |current|
62
- [current.first, current.last[:pace]]
61
+ # We convert this to seconds from milliseconds due to our Web UI precision
62
+ # Reporting is in ms for consistency
63
+ normalized_fd = (ls_offset_fd / 1_000.0).round
64
+
65
+ topics[topic_without_cg][current.first] << normalized_fd
63
66
  end
64
67
  end
65
68
 
66
69
  topics.each_value(&:compact!)
70
+ topics.each_value { |metrics| metrics.transform_values!(&:max) }
71
+ topics.transform_values! { |values| values.to_a.sort_by!(&:first) }
67
72
  topics.to_json
68
73
  end
69
74
 
70
- # @return [String] JSON with per-topic, highest LSO freeze duration. Useful for
71
- # debugging of issues arising from hanging transactions
72
- def max_lso_time
73
- topics = Hash.new { |h, k| h[k] = Hash.new { |h2, k2| h2[k2] = [] } }
75
+ # @return [String] JSON with producers pace that represents high-watermarks sum for
76
+ # each topic.
77
+ #
78
+ # @note There is a case where data reported (sum on a topic) is lower then the
79
+ # previous value. This can happen around rebalances because consumer may not
80
+ # have all watermark offsets reported. This may cause consumers not to report some
81
+ # of the partitions, effectively lowering the sum. Since high-watermark offsets can
82
+ # only move forward, we compensate this by assuming that a lower value than
83
+ # previous is an artefact of that type and we replace it with the max value we had
84
+ # effectively compensating for under-reporting
85
+ def pace
86
+ topics = {}
74
87
 
75
88
  @data.to_h.each do |topic, metrics|
76
89
  topic_without_cg = topic.split('[').first
77
90
 
78
- metrics.each do |current|
79
- ls_offset_fd = current.last[:ls_offset_fd] || 0
91
+ # If we've already seen this topic data, we can skip
92
+ next if topics.include?(topic_without_cg)
80
93
 
81
- # We convert this to seconds from milliseconds due to our Web UI precision
82
- # Reporting is in ms for consistency
83
- normalized_fd = (ls_offset_fd / 1_000.0).round
94
+ max_pace = 0
84
95
 
85
- topics[topic_without_cg][current.first] << normalized_fd
96
+ topics[topic_without_cg] = metrics.map do |current|
97
+ # Pace may be empty when for a given moment in time we got no info on
98
+ # one of the topics. In such case we can compensate with max or 0
99
+ current_pace = current.last[:pace] || 0
100
+
101
+ max_pace = current_pace if current_pace > max_pace
102
+
103
+ [current.first, max_pace]
86
104
  end
87
105
  end
88
106
 
89
107
  topics.each_value(&:compact!)
90
- topics.each_value { |metrics| metrics.transform_values!(&:max) }
91
- topics.transform_values! { |values| values.to_a.sort_by!(&:first) }
92
108
  topics.to_json
93
109
  end
94
110
  end
@@ -209,10 +209,37 @@ module Karafka
209
209
  )
210
210
  end
211
211
 
212
+ # @return [Status::Step] Is there a significant lag in the reporting of aggregated data
213
+ # back to the Kafka. If yes, it means that the results in the Web UI will be delayed
214
+ # against the reality. Often it means, that there is over-saturation on the consumer
215
+ # that is materializing the states.
216
+ #
217
+ # @note Since both states and metrics are reported together, it is enough for us to check
218
+ # on one of them.
219
+ def materializing_lag
220
+ max_lag = (Web.config.tracking.interval * 2) / 1_000
221
+
222
+ details = { lag: 0, max_lag: max_lag }
223
+
224
+ status = if live_reporting.success?
225
+ lag = Time.now.to_f - @current_state.dispatched_at
226
+ details[:lag] = lag
227
+
228
+ lag > max_lag ? :failure : :success
229
+ else
230
+ :halted
231
+ end
232
+
233
+ Step.new(
234
+ status,
235
+ details
236
+ )
237
+ end
238
+
212
239
  # @return [Status::Step] is there a subscription to our reports topic that is being
213
240
  # consumed actively.
214
241
  def state_calculation
215
- if live_reporting.success?
242
+ if materializing_lag.success?
216
243
  @subscriptions ||= Models::Health
217
244
  .current(@current_state)
218
245
  .values.map { |consumer_group| consumer_group[:topics] }
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
2
+ <path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5m-9-6h.008v.008H12v-.008ZM12 15h.008v.008H12V15Zm0 2.25h.008v.008H12v-.008ZM9.75 15h.008v.008H9.75V15Zm0 2.25h.008v.008H9.75v-.008ZM7.5 15h.008v.008H7.5V15Zm0 2.25h.008v.008H7.5v-.008Zm6.75-4.5h.008v.008h-.008v-.008Zm0 2.25h.008v.008h-.008V15Zm0 2.25h.008v.008h-.008v-.008Zm2.25-4.5h.008v.008H16.5v-.008Zm0 2.25h.008v.008H16.5V15Z" />
3
+ </svg>