karafka-web 0.10.4 → 0.11.0

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 (494) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +62 -176
  3. data/Gemfile +4 -0
  4. data/Gemfile.lock +88 -44
  5. data/LICENSE +6 -2
  6. data/Rakefile +4 -0
  7. data/bin/verify_kafka_warnings +35 -0
  8. data/bin/verify_topics_naming +35 -0
  9. data/config/locales/pro_errors.yml +1 -0
  10. data/config/locales/slogans.yml +1 -1
  11. data/docker-compose.yml +1 -1
  12. data/gulpfile.js +0 -2
  13. data/karafka-web.gemspec +2 -7
  14. data/lib/karafka/web/config.rb +80 -9
  15. data/lib/karafka/web/contracts/config.rb +44 -5
  16. data/lib/karafka/web/errors.rb +10 -12
  17. data/lib/karafka/web/management/actions/create_initial_states.rb +6 -6
  18. data/lib/karafka/web/management/actions/create_topics.rb +30 -64
  19. data/lib/karafka/web/management/actions/delete_topics.rb +5 -5
  20. data/lib/karafka/web/management/actions/enable.rb +5 -5
  21. data/lib/karafka/web/pro/commanding/commands/base.rb +37 -13
  22. data/lib/karafka/web/pro/commanding/commands/consumers/quiet.rb +33 -0
  23. data/lib/karafka/web/pro/commanding/commands/consumers/stop.rb +32 -0
  24. data/lib/karafka/web/pro/commanding/commands/consumers/trace.rb +37 -0
  25. data/lib/karafka/web/pro/commanding/commands/partitions/pause.rb +30 -0
  26. data/lib/karafka/web/pro/commanding/commands/partitions/resume.rb +30 -0
  27. data/lib/karafka/web/pro/commanding/commands/partitions/seek.rb +30 -0
  28. data/lib/karafka/web/pro/commanding/config.rb +6 -10
  29. data/lib/karafka/web/pro/commanding/contracts/config.rb +2 -10
  30. data/lib/karafka/web/pro/commanding/dispatcher.rb +45 -24
  31. data/lib/karafka/web/pro/commanding/handlers/partitions/commands/base.rb +67 -0
  32. data/lib/karafka/web/pro/commanding/handlers/partitions/commands/pause.rb +44 -0
  33. data/lib/karafka/web/pro/commanding/handlers/partitions/commands/resume.rb +29 -0
  34. data/lib/karafka/web/pro/commanding/handlers/partitions/commands/seek.rb +86 -0
  35. data/lib/karafka/web/pro/commanding/handlers/partitions/executor.rb +56 -0
  36. data/lib/karafka/web/pro/commanding/handlers/partitions/listener.rb +55 -0
  37. data/lib/karafka/web/pro/commanding/handlers/partitions/tracker.rb +62 -0
  38. data/lib/karafka/web/pro/commanding/listener.rb +4 -12
  39. data/lib/karafka/web/pro/commanding/manager.rb +36 -24
  40. data/lib/karafka/web/pro/commanding/matcher.rb +7 -17
  41. data/lib/karafka/web/pro/commanding/request.rb +39 -0
  42. data/lib/karafka/web/pro/commanding.rb +2 -10
  43. data/lib/karafka/web/pro/loader.rb +13 -10
  44. data/lib/karafka/web/pro/ui/app.rb +31 -390
  45. data/lib/karafka/web/pro/ui/controllers/base_controller.rb +8 -10
  46. data/lib/karafka/web/pro/ui/controllers/cluster_controller.rb +2 -10
  47. data/lib/karafka/web/pro/ui/controllers/consumers/base_controller.rb +21 -0
  48. data/lib/karafka/web/pro/ui/controllers/consumers/commanding_controller.rb +148 -0
  49. data/lib/karafka/web/pro/ui/controllers/consumers/commands_controller.rb +96 -0
  50. data/lib/karafka/web/pro/ui/controllers/consumers/consumers_controller.rb +99 -0
  51. data/lib/karafka/web/pro/ui/controllers/consumers/controls_controller.rb +36 -0
  52. data/lib/karafka/web/pro/ui/controllers/consumers/jobs_controller.rb +57 -0
  53. data/lib/karafka/web/pro/ui/controllers/consumers/partitions/base_controller.rb +86 -0
  54. data/lib/karafka/web/pro/ui/controllers/consumers/partitions/offsets_controller.rb +75 -0
  55. data/lib/karafka/web/pro/ui/controllers/consumers/partitions/pauses_controller.rb +110 -0
  56. data/lib/karafka/web/pro/ui/controllers/dashboard_controller.rb +2 -10
  57. data/lib/karafka/web/pro/ui/controllers/dlq_controller.rb +2 -10
  58. data/lib/karafka/web/pro/ui/controllers/errors_controller.rb +11 -15
  59. data/lib/karafka/web/pro/ui/controllers/explorer/base_controller.rb +21 -0
  60. data/lib/karafka/web/pro/ui/controllers/explorer/explorer_controller.rb +225 -0
  61. data/lib/karafka/web/pro/ui/controllers/explorer/messages_controller.rb +145 -0
  62. data/lib/karafka/web/pro/ui/controllers/explorer/search_controller.rb +68 -0
  63. data/lib/karafka/web/pro/ui/controllers/health_controller.rb +2 -10
  64. data/lib/karafka/web/pro/ui/controllers/jobs_controller.rb +2 -10
  65. data/lib/karafka/web/pro/ui/controllers/recurring_tasks_controller.rb +12 -13
  66. data/lib/karafka/web/pro/ui/controllers/routing_controller.rb +2 -10
  67. data/lib/karafka/web/pro/ui/controllers/scheduled_messages/base_controller.rb +2 -10
  68. data/lib/karafka/web/pro/ui/controllers/scheduled_messages/explorer_controller.rb +8 -16
  69. data/lib/karafka/web/pro/ui/controllers/scheduled_messages/messages_controller.rb +9 -15
  70. data/lib/karafka/web/pro/ui/controllers/scheduled_messages/schedules_controller.rb +2 -10
  71. data/lib/karafka/web/pro/ui/controllers/status_controller.rb +2 -10
  72. data/lib/karafka/web/pro/ui/controllers/support_controller.rb +2 -10
  73. data/lib/karafka/web/pro/ui/controllers/topics/base_controller.rb +21 -0
  74. data/lib/karafka/web/pro/ui/controllers/topics/configs_controller.rb +86 -0
  75. data/lib/karafka/web/pro/ui/controllers/topics/distributions_controller.rb +91 -0
  76. data/lib/karafka/web/pro/ui/controllers/topics/offsets_controller.rb +55 -0
  77. data/lib/karafka/web/pro/ui/controllers/topics/replications_controller.rb +37 -0
  78. data/lib/karafka/web/pro/ui/controllers/topics/topics_controller.rb +101 -0
  79. data/lib/karafka/web/pro/ui/controllers/ux_controller.rb +2 -10
  80. data/lib/karafka/web/pro/ui/lib/branding/config.rb +2 -10
  81. data/lib/karafka/web/pro/ui/lib/branding/contracts/config.rb +2 -10
  82. data/lib/karafka/web/pro/ui/lib/branding.rb +2 -10
  83. data/lib/karafka/web/pro/ui/lib/features.rb +53 -0
  84. data/lib/karafka/web/pro/ui/lib/patterns_detector.rb +2 -10
  85. data/lib/karafka/web/pro/ui/lib/policies/config.rb +2 -10
  86. data/lib/karafka/web/pro/ui/lib/policies/contracts/config.rb +2 -10
  87. data/lib/karafka/web/pro/ui/lib/policies/messages.rb +2 -10
  88. data/lib/karafka/web/pro/ui/lib/policies/requests.rb +2 -10
  89. data/lib/karafka/web/pro/ui/lib/policies.rb +2 -10
  90. data/lib/karafka/web/pro/ui/lib/safe_runner.rb +5 -0
  91. data/lib/karafka/web/pro/ui/lib/search/config.rb +2 -10
  92. data/lib/karafka/web/pro/ui/lib/search/contracts/config.rb +2 -10
  93. data/lib/karafka/web/pro/ui/lib/search/contracts/form.rb +2 -10
  94. data/lib/karafka/web/pro/ui/lib/search/matchers/base.rb +2 -10
  95. data/lib/karafka/web/pro/ui/lib/search/matchers/raw_header_includes.rb +10 -11
  96. data/lib/karafka/web/pro/ui/lib/search/matchers/raw_key_includes.rb +2 -10
  97. data/lib/karafka/web/pro/ui/lib/search/matchers/raw_payload_includes.rb +23 -11
  98. data/lib/karafka/web/pro/ui/lib/search/normalizer.rb +2 -10
  99. data/lib/karafka/web/pro/ui/lib/search/runner.rb +3 -11
  100. data/lib/karafka/web/pro/ui/lib/search.rb +2 -10
  101. data/lib/karafka/web/pro/ui/routes/base.rb +19 -0
  102. data/lib/karafka/web/pro/ui/routes/cluster.rb +37 -0
  103. data/lib/karafka/web/pro/ui/routes/consumers.rb +145 -0
  104. data/lib/karafka/web/pro/ui/routes/dashboard.rb +25 -0
  105. data/lib/karafka/web/pro/ui/routes/dlq.rb +24 -0
  106. data/lib/karafka/web/pro/ui/routes/errors.rb +39 -0
  107. data/lib/karafka/web/pro/ui/routes/explorer.rb +118 -0
  108. data/lib/karafka/web/pro/ui/routes/health.rb +47 -0
  109. data/lib/karafka/web/pro/ui/routes/jobs.rb +33 -0
  110. data/lib/karafka/web/pro/ui/routes/recurring_tasks.rb +59 -0
  111. data/lib/karafka/web/pro/ui/routes/routing.rb +31 -0
  112. data/lib/karafka/web/pro/ui/routes/scheduled_messages.rb +75 -0
  113. data/lib/karafka/web/pro/ui/routes/status.rb +24 -0
  114. data/lib/karafka/web/pro/ui/routes/support.rb +24 -0
  115. data/lib/karafka/web/pro/ui/routes/topics.rb +90 -0
  116. data/lib/karafka/web/pro/ui/routes/ux.rb +24 -0
  117. data/lib/karafka/web/pro/ui/views/cluster/_breadcrumbs.erb +3 -0
  118. data/lib/karafka/web/pro/ui/views/cluster/_broker.erb +3 -0
  119. data/lib/karafka/web/pro/ui/views/cluster/_config.erb +3 -0
  120. data/lib/karafka/web/pro/ui/views/cluster/_tabs.erb +3 -0
  121. data/lib/karafka/web/pro/ui/views/cluster/index.erb +4 -1
  122. data/lib/karafka/web/pro/ui/views/cluster/show.erb +3 -0
  123. data/lib/karafka/web/pro/ui/views/{commands → consumers/commands}/_backtrace.erb +3 -0
  124. data/lib/karafka/web/pro/ui/views/consumers/commands/_breadcrumbs.erb +24 -0
  125. data/lib/karafka/web/pro/ui/views/{commands → consumers/commands}/_command.erb +22 -6
  126. data/lib/karafka/web/pro/ui/views/consumers/commands/_command_details.erb +4 -0
  127. data/lib/karafka/web/pro/ui/views/consumers/commands/_empty.erb +6 -0
  128. data/lib/karafka/web/pro/ui/views/{commands → consumers/commands}/_incompatible_schema.erb +3 -0
  129. data/lib/karafka/web/pro/ui/views/{commands → consumers/commands}/_metadata.erb +4 -1
  130. data/lib/karafka/web/pro/ui/views/{commands → consumers/commands}/_table.erb +5 -2
  131. data/lib/karafka/web/pro/ui/views/{commands → consumers/commands}/index.erb +7 -4
  132. data/lib/karafka/web/pro/ui/views/consumers/commands/show.erb +32 -0
  133. data/lib/karafka/web/pro/ui/views/consumers/consumers/_breadcrumbs.erb +46 -0
  134. data/lib/karafka/web/pro/ui/views/consumers/{_consumer.erb → consumers/_consumer.erb} +4 -1
  135. data/lib/karafka/web/pro/ui/views/consumers/{_consumer_performance.erb → consumers/_consumer_performance.erb} +4 -1
  136. data/lib/karafka/web/pro/ui/views/consumers/consumers/_tabs.erb +38 -0
  137. data/lib/karafka/web/pro/ui/views/consumers/consumers/consumer/_commands.erb +80 -0
  138. data/lib/karafka/web/pro/ui/views/consumers/consumers/consumer/_consumer_group.erb +11 -0
  139. data/lib/karafka/web/pro/ui/views/consumers/{consumer → consumers/consumer}/_metrics.erb +3 -0
  140. data/lib/karafka/web/pro/ui/views/consumers/consumers/consumer/_no_subscriptions.erb +10 -0
  141. data/lib/karafka/web/pro/ui/views/consumers/{consumer → consumers/consumer}/_partition.erb +16 -0
  142. data/lib/karafka/web/pro/ui/views/consumers/consumers/consumer/_partition_edit_options.erb +33 -0
  143. data/lib/karafka/web/pro/ui/views/consumers/{consumer → consumers/consumer}/_stopped.erb +3 -0
  144. data/lib/karafka/web/pro/ui/views/consumers/{consumer → consumers/consumer}/_subscription_group.erb +7 -3
  145. data/lib/karafka/web/pro/ui/views/consumers/{consumer → consumers/consumer}/_tabs.erb +7 -4
  146. data/lib/karafka/web/pro/ui/views/consumers/consumers/details.erb +15 -0
  147. data/lib/karafka/web/pro/ui/views/consumers/{index.erb → consumers/index.erb} +6 -3
  148. data/lib/karafka/web/pro/ui/views/consumers/{performance.erb → consumers/performance.erb} +6 -3
  149. data/lib/karafka/web/pro/ui/views/consumers/consumers/subscriptions.erb +24 -0
  150. data/lib/karafka/web/pro/ui/views/consumers/controls/_breadcrumbs.erb +16 -0
  151. data/lib/karafka/web/pro/ui/views/consumers/{_consumer_controls.erb → controls/_controls.erb} +10 -7
  152. data/lib/karafka/web/pro/ui/views/consumers/{controls.erb → controls/index.erb} +8 -5
  153. data/lib/karafka/web/pro/ui/views/consumers/jobs/_breadcrumbs.erb +36 -0
  154. data/lib/karafka/web/pro/ui/views/consumers/{consumer → jobs}/_job.erb +4 -2
  155. data/lib/karafka/web/pro/ui/views/consumers/jobs/_no_jobs.erb +6 -0
  156. data/lib/karafka/web/pro/ui/views/consumers/{pending_jobs.erb → jobs/pending.erb} +7 -8
  157. data/lib/karafka/web/pro/ui/views/consumers/{running_jobs.erb → jobs/running.erb} +7 -8
  158. data/lib/karafka/web/pro/ui/views/consumers/partitions/offsets/_basics.erb +77 -0
  159. data/lib/karafka/web/pro/ui/views/consumers/partitions/offsets/_breadcrumbs.erb +58 -0
  160. data/lib/karafka/web/pro/ui/views/consumers/partitions/offsets/_form.erb +109 -0
  161. data/lib/karafka/web/pro/ui/views/consumers/partitions/offsets/_not_running_error.erb +16 -0
  162. data/lib/karafka/web/pro/ui/views/consumers/partitions/offsets/_running_warning.erb +15 -0
  163. data/lib/karafka/web/pro/ui/views/consumers/partitions/offsets/edit.erb +16 -0
  164. data/lib/karafka/web/pro/ui/views/consumers/partitions/pauses/_active_not_editable.erb +22 -0
  165. data/lib/karafka/web/pro/ui/views/consumers/partitions/pauses/_adjusting_warning.erb +27 -0
  166. data/lib/karafka/web/pro/ui/views/consumers/partitions/pauses/_breadcrumbs.erb +60 -0
  167. data/lib/karafka/web/pro/ui/views/consumers/partitions/pauses/_edit_form.erb +59 -0
  168. data/lib/karafka/web/pro/ui/views/consumers/partitions/pauses/_lrj_not_manageable.erb +24 -0
  169. data/lib/karafka/web/pro/ui/views/consumers/partitions/pauses/_new_form.erb +78 -0
  170. data/lib/karafka/web/pro/ui/views/consumers/partitions/pauses/_not_running.erb +16 -0
  171. data/lib/karafka/web/pro/ui/views/consumers/partitions/pauses/edit.erb +24 -0
  172. data/lib/karafka/web/pro/ui/views/consumers/partitions/pauses/new.erb +20 -0
  173. data/lib/karafka/web/pro/ui/views/dashboard/index.erb +4 -1
  174. data/lib/karafka/web/pro/ui/views/dlq/_breadcrumbs.erb +3 -0
  175. data/lib/karafka/web/pro/ui/views/dlq/_no_topics.erb +3 -0
  176. data/lib/karafka/web/pro/ui/views/dlq/_topic.erb +4 -1
  177. data/lib/karafka/web/pro/ui/views/dlq/index.erb +3 -0
  178. data/lib/karafka/web/pro/ui/views/errors/_breadcrumbs.erb +4 -6
  179. data/lib/karafka/web/pro/ui/views/errors/_error.erb +9 -1
  180. data/lib/karafka/web/pro/ui/views/errors/_partition_option.erb +3 -0
  181. data/lib/karafka/web/pro/ui/views/errors/_selector.erb +3 -0
  182. data/lib/karafka/web/pro/ui/views/errors/_table.erb +4 -1
  183. data/lib/karafka/web/pro/ui/views/errors/index.erb +6 -3
  184. data/lib/karafka/web/pro/ui/views/errors/partition.erb +5 -2
  185. data/lib/karafka/web/pro/ui/views/errors/show.erb +42 -33
  186. data/lib/karafka/web/pro/ui/views/explorer/{_breadcrumbs.erb → explorer/_breadcrumbs.erb} +7 -4
  187. data/lib/karafka/web/pro/ui/views/explorer/{_failed_deserialization.erb → explorer/_failed_deserialization.erb} +3 -0
  188. data/lib/karafka/web/pro/ui/views/explorer/{_filtered.erb → explorer/_filtered.erb} +3 -0
  189. data/lib/karafka/web/pro/ui/views/explorer/{_message.erb → explorer/_message.erb} +4 -1
  190. data/lib/karafka/web/pro/ui/views/explorer/explorer/_no_topics.erb +4 -0
  191. data/lib/karafka/web/pro/ui/views/explorer/{_partition_option.erb → explorer/_partition_option.erb} +4 -1
  192. data/lib/karafka/web/pro/ui/views/explorer/{_selector.erb → explorer/_selector.erb} +4 -1
  193. data/lib/karafka/web/pro/ui/views/explorer/explorer/_topic.erb +13 -0
  194. data/lib/karafka/web/pro/ui/views/explorer/explorer/index.erb +17 -0
  195. data/lib/karafka/web/pro/ui/views/explorer/{message → explorer/message}/_metadata.erb +10 -7
  196. data/lib/karafka/web/pro/ui/views/explorer/{message → explorer/message}/_payload.erb +6 -3
  197. data/lib/karafka/web/pro/ui/views/explorer/{message → explorer/message}/_resources_utilization.erb +7 -4
  198. data/lib/karafka/web/pro/ui/views/explorer/{message → explorer/message}/_too_big_to_be_displayed.erb +3 -0
  199. data/lib/karafka/web/pro/ui/views/explorer/{messages → explorer/messages}/_detail.erb +3 -0
  200. data/lib/karafka/web/pro/ui/views/explorer/explorer/messages/_headers.erb +51 -0
  201. data/lib/karafka/web/pro/ui/views/explorer/{messages → explorer/messages}/_key.erb +3 -0
  202. data/lib/karafka/web/pro/ui/views/explorer/explorer/partition/_cleaned.erb +6 -0
  203. data/lib/karafka/web/pro/ui/views/explorer/explorer/partition/_empty.erb +6 -0
  204. data/lib/karafka/web/pro/ui/views/explorer/{partition → explorer/partition}/_messages.erb +4 -1
  205. data/lib/karafka/web/pro/ui/views/explorer/explorer/partition/_time_selector.erb +16 -0
  206. data/lib/karafka/web/pro/ui/views/explorer/explorer/partition/_timestamp_selector.erb +33 -0
  207. data/lib/karafka/web/pro/ui/views/explorer/{partition.erb → explorer/partition.erb} +24 -17
  208. data/lib/karafka/web/pro/ui/views/explorer/explorer/show.erb +100 -0
  209. data/lib/karafka/web/pro/ui/views/explorer/{topic → explorer/topic}/_actions.erb +5 -2
  210. data/lib/karafka/web/pro/ui/views/explorer/explorer/topic/_empty.erb +6 -0
  211. data/lib/karafka/web/pro/ui/views/explorer/{topic → explorer/topic}/_limited.erb +3 -0
  212. data/lib/karafka/web/pro/ui/views/explorer/{topic.erb → explorer/topic.erb} +7 -4
  213. data/lib/karafka/web/pro/ui/views/explorer/messages/_breadcrumbs.erb +32 -0
  214. data/lib/karafka/web/pro/ui/views/explorer/messages/forward.erb +143 -0
  215. data/lib/karafka/web/pro/ui/views/explorer/search/_breadcrumbs.erb +4 -0
  216. data/lib/karafka/web/pro/ui/views/explorer/search/_fix_errors.erb +6 -0
  217. data/lib/karafka/web/pro/ui/views/{search → explorer/search}/_metadata.erb +3 -0
  218. data/lib/karafka/web/pro/ui/views/explorer/search/_no_results.erb +6 -0
  219. data/lib/karafka/web/pro/ui/views/{search → explorer/search}/_no_search_criteria.erb +3 -0
  220. data/lib/karafka/web/pro/ui/views/{search → explorer/search}/_search_criteria.erb +3 -0
  221. data/lib/karafka/web/pro/ui/views/{search → explorer/search}/_search_modal.erb +5 -2
  222. data/lib/karafka/web/pro/ui/views/explorer/search/_timeout.erb +6 -0
  223. data/lib/karafka/web/pro/ui/views/explorer/search/index.erb +32 -0
  224. data/lib/karafka/web/pro/ui/views/health/_breadcrumbs.erb +3 -0
  225. data/lib/karafka/web/pro/ui/views/health/_no_data.erb +3 -0
  226. data/lib/karafka/web/pro/ui/views/health/_partition.erb +16 -1
  227. data/lib/karafka/web/pro/ui/views/health/_partition_lags.erb +3 -0
  228. data/lib/karafka/web/pro/ui/views/health/_partition_offset.erb +3 -0
  229. data/lib/karafka/web/pro/ui/views/health/_partition_times.erb +3 -0
  230. data/lib/karafka/web/pro/ui/views/health/_table_metadata.erb +4 -1
  231. data/lib/karafka/web/pro/ui/views/health/_tabs.erb +3 -0
  232. data/lib/karafka/web/pro/ui/views/health/changes.erb +4 -1
  233. data/lib/karafka/web/pro/ui/views/health/cluster_lags.erb +3 -0
  234. data/lib/karafka/web/pro/ui/views/health/lags.erb +5 -2
  235. data/lib/karafka/web/pro/ui/views/health/offsets.erb +4 -1
  236. data/lib/karafka/web/pro/ui/views/health/overview.erb +8 -3
  237. data/lib/karafka/web/pro/ui/views/jobs/_job.erb +5 -3
  238. data/lib/karafka/web/pro/ui/views/jobs/_no_jobs.erb +3 -0
  239. data/lib/karafka/web/pro/ui/views/jobs/pending.erb +4 -1
  240. data/lib/karafka/web/pro/ui/views/jobs/running.erb +4 -1
  241. data/lib/karafka/web/pro/ui/views/recurring_tasks/_actions.erb +3 -0
  242. data/lib/karafka/web/pro/ui/views/recurring_tasks/_batch_actions.erb +3 -0
  243. data/lib/karafka/web/pro/ui/views/recurring_tasks/_breadcrumbs.erb +3 -0
  244. data/lib/karafka/web/pro/ui/views/recurring_tasks/_log.erb +3 -0
  245. data/lib/karafka/web/pro/ui/views/recurring_tasks/_not_active.erb +3 -0
  246. data/lib/karafka/web/pro/ui/views/recurring_tasks/_tabs.erb +3 -0
  247. data/lib/karafka/web/pro/ui/views/recurring_tasks/_task.erb +3 -0
  248. data/lib/karafka/web/pro/ui/views/recurring_tasks/logs.erb +3 -0
  249. data/lib/karafka/web/pro/ui/views/recurring_tasks/schedule.erb +3 -0
  250. data/lib/karafka/web/pro/ui/views/routing/_consumer_group.erb +3 -0
  251. data/lib/karafka/web/pro/ui/views/routing/_detail.erb +3 -0
  252. data/lib/karafka/web/pro/ui/views/routing/_topic.erb +3 -0
  253. data/lib/karafka/web/pro/ui/views/routing/index.erb +3 -0
  254. data/lib/karafka/web/pro/ui/views/routing/show.erb +3 -0
  255. data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/_breadcrumbs.erb +6 -3
  256. data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/_key.erb +3 -0
  257. data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/_message.erb +28 -115
  258. data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/_messages.erb +3 -0
  259. data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/message/_cancel.erb +49 -0
  260. data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/message/_compacted.erb +16 -0
  261. data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/message/_schedule.erb +83 -0
  262. data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/message/_tombstone.erb +69 -0
  263. data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/message/_unknown.erb +26 -0
  264. data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/partition.erb +23 -16
  265. data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/topic.erb +6 -3
  266. data/lib/karafka/web/pro/ui/views/scheduled_messages/schedules/_breadcrumbs.erb +3 -0
  267. data/lib/karafka/web/pro/ui/views/scheduled_messages/schedules/_no_groups.erb +3 -0
  268. data/lib/karafka/web/pro/ui/views/scheduled_messages/schedules/index.erb +4 -1
  269. data/lib/karafka/web/pro/ui/views/scheduled_messages/schedules/show.erb +17 -1
  270. data/lib/karafka/web/pro/ui/views/shared/_navigation.erb +25 -17
  271. data/lib/karafka/web/pro/ui/views/shared/_rdkafka_form_error_alert_box.erb +16 -0
  272. data/lib/karafka/web/pro/ui/views/shared/branding/_label.erb +3 -0
  273. data/lib/karafka/web/pro/ui/views/shared/branding/_notice.erb +3 -0
  274. data/lib/karafka/web/pro/ui/views/topics/configs/_breadcrumbs.erb +34 -0
  275. data/lib/karafka/web/pro/ui/views/topics/configs/_config.erb +26 -0
  276. data/lib/karafka/web/pro/ui/views/topics/configs/_delete_button.erb +13 -0
  277. data/lib/karafka/web/pro/ui/views/topics/configs/_edit_form.erb +50 -0
  278. data/lib/karafka/web/pro/ui/views/topics/configs/_edit_plan.erb +16 -0
  279. data/lib/karafka/web/pro/ui/views/topics/configs/_edit_warning.erb +12 -0
  280. data/lib/karafka/web/pro/ui/views/topics/configs/edit.erb +16 -0
  281. data/lib/karafka/web/pro/ui/views/topics/{config.erb → configs/index.erb} +9 -3
  282. data/lib/karafka/web/pro/ui/views/topics/distributions/_add_partitions_button.erb +13 -0
  283. data/lib/karafka/web/pro/ui/views/topics/{distribution → distributions}/_badges.erb +3 -0
  284. data/lib/karafka/web/pro/ui/views/topics/distributions/_breadcrumbs.erb +28 -0
  285. data/lib/karafka/web/pro/ui/views/topics/{distribution → distributions}/_chart.erb +3 -0
  286. data/lib/karafka/web/pro/ui/views/topics/distributions/_edit_form.erb +47 -0
  287. data/lib/karafka/web/pro/ui/views/topics/distributions/_edit_hints.erb +15 -0
  288. data/lib/karafka/web/pro/ui/views/topics/distributions/_edit_warnings.erb +14 -0
  289. data/lib/karafka/web/pro/ui/views/topics/distributions/_empty_partitions.erb +4 -0
  290. data/lib/karafka/web/pro/ui/views/topics/{distribution → distributions}/_limited.erb +3 -0
  291. data/lib/karafka/web/pro/ui/views/topics/distributions/_partition.erb +13 -0
  292. data/lib/karafka/web/pro/ui/views/topics/distributions/edit.erb +16 -0
  293. data/lib/karafka/web/pro/ui/views/topics/{distribution.erb → distributions/show.erb} +11 -7
  294. data/lib/karafka/web/pro/ui/views/topics/offsets/_breadcrumbs.erb +20 -0
  295. data/lib/karafka/web/pro/ui/views/topics/offsets/_partition.erb +13 -0
  296. data/lib/karafka/web/pro/ui/views/topics/{offsets.erb → offsets/show.erb} +6 -3
  297. data/lib/karafka/web/pro/ui/views/topics/replications/_breadcrumbs.erb +20 -0
  298. data/lib/karafka/web/pro/ui/views/topics/{_partition.erb → replications/_partition.erb} +4 -1
  299. data/lib/karafka/web/pro/ui/views/topics/{replication.erb → replications/show.erb} +6 -3
  300. data/lib/karafka/web/pro/ui/views/topics/topics/_breadcrumbs.erb +32 -0
  301. data/lib/karafka/web/pro/ui/views/topics/topics/_create_button.erb +13 -0
  302. data/lib/karafka/web/pro/ui/views/topics/topics/_create_hints.erb +15 -0
  303. data/lib/karafka/web/pro/ui/views/topics/topics/_delete_form.erb +36 -0
  304. data/lib/karafka/web/pro/ui/views/topics/topics/_delete_hints.erb +15 -0
  305. data/lib/karafka/web/pro/ui/views/topics/topics/_delete_warning.erb +13 -0
  306. data/lib/karafka/web/pro/ui/views/topics/topics/_new_form.erb +80 -0
  307. data/lib/karafka/web/pro/ui/views/topics/{_tabs.erb → topics/_tabs.erb} +7 -4
  308. data/lib/karafka/web/pro/ui/views/topics/topics/_topic.erb +12 -0
  309. data/lib/karafka/web/pro/ui/views/topics/topics/edit.erb +10 -0
  310. data/lib/karafka/web/pro/ui/views/topics/topics/index.erb +19 -0
  311. data/lib/karafka/web/pro/ui/views/topics/topics/new.erb +12 -0
  312. data/lib/karafka/web/processing/consumer.rb +7 -7
  313. data/lib/karafka/web/processing/consumers/aggregators/state.rb +14 -14
  314. data/lib/karafka/web/processing/consumers/metrics.rb +1 -1
  315. data/lib/karafka/web/processing/consumers/state.rb +1 -1
  316. data/lib/karafka/web/processing/publisher.rb +4 -4
  317. data/lib/karafka/web/tracking/consumers/contracts/partition.rb +1 -0
  318. data/lib/karafka/web/tracking/consumers/listeners/errors.rb +1 -0
  319. data/lib/karafka/web/tracking/consumers/listeners/pausing.rb +2 -2
  320. data/lib/karafka/web/tracking/consumers/listeners/transactions.rb +44 -0
  321. data/lib/karafka/web/tracking/consumers/reporter.rb +2 -2
  322. data/lib/karafka/web/tracking/consumers/sampler.rb +81 -14
  323. data/lib/karafka/web/tracking/helpers/sysconf.rb +33 -0
  324. data/lib/karafka/web/tracking/producers/reporter.rb +1 -1
  325. data/lib/karafka/web/ui/app.rb +19 -112
  326. data/lib/karafka/web/ui/base.rb +60 -3
  327. data/lib/karafka/web/ui/controllers/base_controller.rb +43 -1
  328. data/lib/karafka/web/ui/controllers/cluster_controller.rb +5 -2
  329. data/lib/karafka/web/ui/controllers/errors_controller.rb +13 -4
  330. data/lib/karafka/web/ui/controllers/requests/execution_wrapper.rb +52 -0
  331. data/lib/karafka/web/ui/controllers/requests/hookable.rb +99 -0
  332. data/lib/karafka/web/ui/controllers/requests/params.rb +39 -1
  333. data/lib/karafka/web/ui/controllers/responses/redirect.rb +0 -5
  334. data/lib/karafka/web/ui/controllers/status_controller.rb +3 -0
  335. data/lib/karafka/web/ui/helpers/application_helper.rb +10 -71
  336. data/lib/karafka/web/ui/helpers/paths_helper.rb +54 -10
  337. data/lib/karafka/web/ui/helpers/time_helper.rb +82 -0
  338. data/lib/karafka/web/ui/helpers/topics_helper.rb +156 -0
  339. data/lib/karafka/web/ui/lib/admin.rb +1 -1
  340. data/lib/karafka/web/ui/lib/cache.rb +135 -0
  341. data/lib/karafka/web/ui/models/broker.rb +1 -2
  342. data/lib/karafka/web/ui/models/cluster_info.rb +15 -21
  343. data/lib/karafka/web/ui/models/consumers_metrics.rb +1 -1
  344. data/lib/karafka/web/ui/models/consumers_state.rb +1 -1
  345. data/lib/karafka/web/ui/models/counters.rb +1 -1
  346. data/lib/karafka/web/ui/models/health.rb +9 -7
  347. data/lib/karafka/web/ui/models/message.rb +20 -2
  348. data/lib/karafka/web/ui/models/process.rb +16 -0
  349. data/lib/karafka/web/ui/models/processes.rb +29 -8
  350. data/lib/karafka/web/ui/models/recurring_tasks/schedule.rb +1 -1
  351. data/lib/karafka/web/ui/models/status.rb +28 -9
  352. data/lib/karafka/web/ui/models/topic.rb +1 -2
  353. data/lib/karafka/web/ui/public/javascripts/application.js +8 -98
  354. data/lib/karafka/web/ui/public/javascripts/application.min.js +12 -4
  355. data/lib/karafka/web/ui/public/javascripts/application.min.js.br +0 -0
  356. data/lib/karafka/web/ui/public/javascripts/application.min.js.gz +0 -0
  357. data/lib/karafka/web/ui/public/javascripts/components/action_confirmation_manager.js +30 -0
  358. data/lib/karafka/web/ui/public/javascripts/components/alerts.js +39 -0
  359. data/lib/karafka/web/ui/public/javascripts/components/button_lock_manager.js +50 -0
  360. data/lib/karafka/web/ui/public/javascripts/components/live_poll.js +71 -19
  361. data/lib/karafka/web/ui/public/javascripts/components/message_republish_manager.js +50 -0
  362. data/lib/karafka/web/ui/public/javascripts/components/page_title_tracker.js +21 -0
  363. data/lib/karafka/web/ui/public/javascripts/components/partition_redirect_manager.js +21 -0
  364. data/lib/karafka/web/ui/public/javascripts/components/time_ago_manager.js +25 -0
  365. data/lib/karafka/web/ui/public/javascripts/components/timestamp_selector.js +30 -0
  366. data/lib/karafka/web/ui/public/javascripts/libs/datepicker.js +2 -2
  367. data/lib/karafka/web/ui/public/stylesheets/application.css +30 -0
  368. data/lib/karafka/web/ui/public/stylesheets/application.min.css +5110 -13
  369. data/lib/karafka/web/ui/public/stylesheets/application.min.css.br +0 -0
  370. data/lib/karafka/web/ui/public/stylesheets/application.min.css.gz +0 -0
  371. data/lib/karafka/web/ui/public/stylesheets/libs/highlight_dark.min.css.gz +0 -0
  372. data/lib/karafka/web/ui/public/stylesheets/libs/highlight_light.min.css.gz +0 -0
  373. data/lib/karafka/web/ui/public/stylesheets/libs/tailwind.css +507 -214
  374. data/lib/karafka/web/ui/routes/assets.rb +53 -0
  375. data/lib/karafka/web/ui/routes/base.rb +36 -0
  376. data/lib/karafka/web/ui/routes/cluster.rb +28 -0
  377. data/lib/karafka/web/ui/routes/consumers.rb +35 -0
  378. data/lib/karafka/web/ui/routes/dashboard.rb +20 -0
  379. data/lib/karafka/web/ui/routes/errors.rb +30 -0
  380. data/lib/karafka/web/ui/routes/jobs.rb +28 -0
  381. data/lib/karafka/web/ui/routes/pro_only.rb +27 -0
  382. data/lib/karafka/web/ui/routes/routing.rb +26 -0
  383. data/lib/karafka/web/ui/routes/status.rb +19 -0
  384. data/lib/karafka/web/ui/routes/support.rb +19 -0
  385. data/lib/karafka/web/ui/routes/ux.rb +19 -0
  386. data/lib/karafka/web/ui/views/cluster/_partition.erb +2 -2
  387. data/lib/karafka/web/ui/views/cluster/brokers.erb +1 -1
  388. data/lib/karafka/web/ui/views/consumers/_assignments_badges.erb +2 -7
  389. data/lib/karafka/web/ui/views/consumers/_breadcrumbs.erb +7 -1
  390. data/lib/karafka/web/ui/views/consumers/_consumer.erb +1 -1
  391. data/lib/karafka/web/ui/views/consumers/_no_consumers.erb +2 -2
  392. data/lib/karafka/web/ui/views/consumers/_tabs.erb +4 -4
  393. data/lib/karafka/web/ui/views/consumers/index.erb +1 -1
  394. data/lib/karafka/web/ui/views/dashboard/_feature_pro.erb +1 -1
  395. data/lib/karafka/web/ui/views/dashboard/_not_enough_data.erb +2 -2
  396. data/lib/karafka/web/ui/views/dashboard/_ranges_selector.erb +1 -1
  397. data/lib/karafka/web/ui/views/dashboard/index.erb +6 -49
  398. data/lib/karafka/web/ui/views/errors/_breadcrumbs.erb +3 -8
  399. data/lib/karafka/web/ui/views/errors/_detail.erb +3 -3
  400. data/lib/karafka/web/ui/views/errors/_error.erb +6 -1
  401. data/lib/karafka/web/ui/views/errors/index.erb +1 -1
  402. data/lib/karafka/web/ui/views/errors/show.erb +39 -33
  403. data/lib/karafka/web/ui/views/jobs/_job.erb +2 -3
  404. data/lib/karafka/web/ui/views/jobs/pending.erb +1 -1
  405. data/lib/karafka/web/ui/views/jobs/running.erb +1 -1
  406. data/lib/karafka/web/ui/views/layout.erb +7 -5
  407. data/lib/karafka/web/ui/views/shared/_become_pro.erb +1 -1
  408. data/lib/karafka/web/ui/views/shared/_brand.erb +1 -1
  409. data/lib/karafka/web/ui/views/shared/_breadcrumbs.erb +1 -1
  410. data/lib/karafka/web/ui/views/shared/_compacted_message_info.erb +16 -0
  411. data/lib/karafka/web/ui/views/shared/_content.erb +1 -1
  412. data/lib/karafka/web/ui/views/shared/_controls.erb +10 -3
  413. data/lib/karafka/web/ui/views/shared/_custom_nav.erb +9 -0
  414. data/lib/karafka/web/ui/views/shared/_flashes.erb +3 -5
  415. data/lib/karafka/web/ui/views/shared/_header.erb +25 -2
  416. data/lib/karafka/web/ui/views/shared/_navigation.erb +17 -15
  417. data/lib/karafka/web/ui/views/shared/alerts/_error.erb +8 -0
  418. data/lib/karafka/web/ui/views/shared/alerts/_info.erb +8 -0
  419. data/lib/karafka/web/ui/views/shared/alerts/_primary.erb +8 -0
  420. data/lib/karafka/web/ui/views/shared/alerts/_secondary.erb +8 -0
  421. data/lib/karafka/web/ui/views/shared/alerts/_success.erb +8 -0
  422. data/lib/karafka/web/ui/views/shared/alerts/_warning.erb +8 -0
  423. data/lib/karafka/web/ui/views/shared/exceptions/not_allowed.erb +4 -0
  424. data/lib/karafka/web/ui/views/shared/exceptions/not_found.erb +5 -1
  425. data/lib/karafka/web/ui/views/shared/exceptions/pro_only.erb +4 -0
  426. data/lib/karafka/web/ui/views/shared/icons/_arrow_left.erb +3 -0
  427. data/lib/karafka/web/ui/views/shared/icons/_arrow_up_tray.erb +3 -0
  428. data/lib/karafka/web/ui/views/shared/icons/_clock.erb +3 -0
  429. data/lib/karafka/web/ui/views/shared/icons/_pencil.erb +3 -0
  430. data/lib/karafka/web/ui/views/shared/icons/_pencil_square.erb +3 -0
  431. data/lib/karafka/web/ui/views/shared/icons/_play_pause.erb +3 -0
  432. data/lib/karafka/web/ui/views/shared/icons/_plus.erb +3 -0
  433. data/lib/karafka/web/ui/views/shared/icons/_trash.erb +3 -0
  434. data/lib/karafka/web/ui/views/status/failures/_live_reporting.erb +1 -1
  435. data/lib/karafka/web/ui/views/status/failures/_partitions.erb +3 -3
  436. data/lib/karafka/web/ui/views/status/failures/_state_calculation.erb +2 -2
  437. data/lib/karafka/web/ui/views/status/info/_components.erb +6 -6
  438. data/lib/karafka/web/ui/views/status/show.erb +15 -0
  439. data/lib/karafka/web/ui/views/status/warnings/_consumers_schemas.erb +31 -0
  440. data/lib/karafka/web/ui/views/ux/_icons.erb +1 -1
  441. data/lib/karafka/web/version.rb +1 -1
  442. data/lib/karafka/web.rb +3 -0
  443. data/package-lock.json +868 -1255
  444. data/package.json +6 -7
  445. data/postcss.config.js +1 -2
  446. data/renovate.json +20 -1
  447. data/tailwind.config.js +0 -4
  448. metadata +235 -135
  449. checksums.yaml.gz.sig +0 -0
  450. data/certs/cert.pem +0 -26
  451. data/lib/karafka/web/pro/commanding/commands/quiet.rb +0 -34
  452. data/lib/karafka/web/pro/commanding/commands/stop.rb +0 -34
  453. data/lib/karafka/web/pro/commanding/commands/trace.rb +0 -41
  454. data/lib/karafka/web/pro/ui/controllers/commanding_controller.rb +0 -118
  455. data/lib/karafka/web/pro/ui/controllers/commands_controller.rb +0 -96
  456. data/lib/karafka/web/pro/ui/controllers/consumers_controller.rb +0 -138
  457. data/lib/karafka/web/pro/ui/controllers/explorer_controller.rb +0 -220
  458. data/lib/karafka/web/pro/ui/controllers/messages_controller.rb +0 -107
  459. data/lib/karafka/web/pro/ui/controllers/search_controller.rb +0 -73
  460. data/lib/karafka/web/pro/ui/controllers/topics_controller.rb +0 -130
  461. data/lib/karafka/web/pro/ui/views/commands/_breadcrumbs.erb +0 -21
  462. data/lib/karafka/web/pro/ui/views/commands/_command_details.erb +0 -1
  463. data/lib/karafka/web/pro/ui/views/commands/_empty.erb +0 -3
  464. data/lib/karafka/web/pro/ui/views/commands/show.erb +0 -33
  465. data/lib/karafka/web/pro/ui/views/consumers/_breadcrumbs.erb +0 -55
  466. data/lib/karafka/web/pro/ui/views/consumers/_tabs.erb +0 -33
  467. data/lib/karafka/web/pro/ui/views/consumers/consumer/_commands.erb +0 -72
  468. data/lib/karafka/web/pro/ui/views/consumers/consumer/_consumer_group.erb +0 -8
  469. data/lib/karafka/web/pro/ui/views/consumers/consumer/_no_jobs.erb +0 -7
  470. data/lib/karafka/web/pro/ui/views/consumers/consumer/_no_subscriptions.erb +0 -7
  471. data/lib/karafka/web/pro/ui/views/consumers/details.erb +0 -13
  472. data/lib/karafka/web/pro/ui/views/consumers/subscriptions.erb +0 -25
  473. data/lib/karafka/web/pro/ui/views/explorer/_no_topics.erb +0 -1
  474. data/lib/karafka/web/pro/ui/views/explorer/_topic.erb +0 -10
  475. data/lib/karafka/web/pro/ui/views/explorer/index.erb +0 -14
  476. data/lib/karafka/web/pro/ui/views/explorer/messages/_headers.erb +0 -33
  477. data/lib/karafka/web/pro/ui/views/explorer/partition/_cleaned.erb +0 -3
  478. data/lib/karafka/web/pro/ui/views/explorer/partition/_empty.erb +0 -3
  479. data/lib/karafka/web/pro/ui/views/explorer/show.erb +0 -97
  480. data/lib/karafka/web/pro/ui/views/explorer/topic/_empty.erb +0 -3
  481. data/lib/karafka/web/pro/ui/views/search/_breadcrumbs.erb +0 -1
  482. data/lib/karafka/web/pro/ui/views/search/_fix_errors.erb +0 -3
  483. data/lib/karafka/web/pro/ui/views/search/_no_results.erb +0 -3
  484. data/lib/karafka/web/pro/ui/views/search/_timeout.erb +0 -3
  485. data/lib/karafka/web/pro/ui/views/search/index.erb +0 -29
  486. data/lib/karafka/web/pro/ui/views/topics/_breadcrumbs.erb +0 -45
  487. data/lib/karafka/web/pro/ui/views/topics/_partition_offsets.erb +0 -10
  488. data/lib/karafka/web/pro/ui/views/topics/_topic.erb +0 -9
  489. data/lib/karafka/web/pro/ui/views/topics/distribution/_empty_partitions.erb +0 -1
  490. data/lib/karafka/web/pro/ui/views/topics/distribution/_partition.erb +0 -10
  491. data/lib/karafka/web/pro/ui/views/topics/index.erb +0 -14
  492. data/lib/karafka/web/ui/lib/ttl_cache.rb +0 -82
  493. data.tar.gz.sig +0 -0
  494. metadata.gz.sig +0 -0
@@ -129,76 +129,6 @@ module Karafka
129
129
  parts.join('.')
130
130
  end
131
131
 
132
- # @param time [Float] UTC time float
133
- # @return [String] relative time tag for timeago.js
134
- def relative_time(time)
135
- stamp = Time.at(time).getutc.iso8601(3)
136
- %(<time class="ltr" dir="ltr" title="#{stamp}" datetime="#{stamp}">#{time}</time>)
137
- end
138
-
139
- # @param time [Time] time object we want to present with detailed ms label
140
- # @return [String] span tag with raw timestamp as a title and time as a value
141
- def time_with_label(time)
142
- stamp = (time.to_f * 1000).to_i
143
-
144
- %(<span title="#{stamp}">#{time}</span>)
145
- end
146
-
147
- # Converts raw second count into human readable form like "12.2 minutes". etc based on
148
- # number of seconds
149
- #
150
- # @param seconds [Numeric] number of seconds
151
- # @return [String] human readable time
152
- def human_readable_time(seconds)
153
- case seconds
154
- when 0..59
155
- "#{seconds.round(2)} seconds"
156
- when 60..3_599
157
- minutes = seconds / 60.0
158
- "#{minutes.round(2)} minutes"
159
- when 3_600..86_399
160
- hours = seconds / 3_600.0
161
- "#{hours.round(2)} hours"
162
- else
163
- days = seconds / 86_400.0
164
- "#{days.round(2)} days"
165
- end
166
- end
167
-
168
- # @param state [String] poll state
169
- # @param state_ch [Integer] time until next change of the poll state
170
- # (from paused to active)
171
- # @return [String] span tag with label and title with change time if present
172
- def poll_state_with_change_time_label(state, state_ch)
173
- year_in_seconds = 131_556_926
174
- state_ch_in_seconds = state_ch / 1_000.0
175
-
176
- # If state is active, there is no date of change
177
- if state == 'active'
178
- %(
179
- <span class="badge #{kafka_state_badge(state)}">#{state}</span>
180
- )
181
- elsif state_ch_in_seconds > year_in_seconds
182
- %(
183
- <span
184
- class="badge #{kafka_state_badge(state)}"
185
- title="until manual resume"
186
- >
187
- #{state}
188
- </span>
189
- )
190
- else
191
- %(
192
- <span
193
- class="badge #{kafka_state_badge(state)} time-title"
194
- title="#{Time.now + state_ch_in_seconds}"
195
- >
196
- #{state}
197
- </span>
198
- )
199
- end
200
- end
201
-
202
132
  # @param lag [Integer] lag
203
133
  # @return [String] lag if correct or `N/A` with labeled explanation
204
134
  # @see #offset_with_label
@@ -223,13 +153,22 @@ module Karafka
223
153
  title = 'Not available until first offset commit'
224
154
  %(<span class="badge badge-secondary" title="#{title}">N/A</span>)
225
155
  elsif explore
226
- path = explorer_path(topic_name, partition_id, offset)
156
+ path = explorer_topics_path(topic_name, partition_id, offset)
227
157
  %(<a href="#{path}">#{offset}</a>)
228
158
  else
229
159
  offset.to_s
230
160
  end
231
161
  end
232
162
 
163
+ # Normalizes the metric value for display. Negative values coming from statistics usually
164
+ # mean, that the value is not (yet) available.
165
+ #
166
+ # @param value [Integer]
167
+ # @return [String] input value if not negative or N/A
168
+ def normalized_metric(value)
169
+ value.negative? ? 'N/A' : value.to_s
170
+ end
171
+
233
172
  # @param details [::Karafka::Web::Ui::Models::Partition] partition information with
234
173
  # lso risk state info
235
174
  # @return [String] background classes for row marking
@@ -6,6 +6,15 @@ module Karafka
6
6
  module Helpers
7
7
  # Helper for web ui paths builders
8
8
  module PathsHelper
9
+ # Method that can be used to have conditions in breadcrumbs, etc based on the action
10
+ # we're in
11
+ #
12
+ # @param args [Array<Symbol>] action names we want to check
13
+ # @return [Boolean] true if any matches the current action name
14
+ def action?(*args)
15
+ args.include?(@current_action_name)
16
+ end
17
+
9
18
  # Helper method to flatten nested hashes and arrays
10
19
  # @param prefix [String] The prefix for nested keys, initially an empty string.
11
20
  # @param hash [Hash, Array] The nested hash or array to be flattened.
@@ -54,16 +63,51 @@ module Karafka
54
63
 
55
64
  # Helps build explorer paths. We often link offsets to proper messages, etc so this
56
65
  # allows us to short-track this
57
- # @param topic_name [String, nil] name of the topic where we want to go within the
58
- # explorer or nil if we want to just go to the explorer root
59
- # @param partition_id [Integer, nil] partition we want to display in the explorer or nil
60
- # if we want to go to the topic root
61
- # @param offset [Integer, nil] offset of particular message or nil of we want to just go
62
- # to the partition root
63
- # @param action [String, nil] specific routed action or nil
66
+ # @param args [Array<String>] sub-paths
64
67
  # @return [String] path to the expected location
65
- def explorer_path(topic_name = nil, partition_id = nil, offset = nil, action = nil)
66
- root_path(*['explorer', topic_name, partition_id, offset, action].compact)
68
+ def explorer_path(*args)
69
+ root_path(*['explorer', args.compact].flatten)
70
+ end
71
+
72
+ # Generates routes for explorer topics paths
73
+ #
74
+ # @param args [Array<String>] sub-paths
75
+ # @return explorer topics path
76
+ def explorer_topics_path(*args)
77
+ explorer_path(*['topics', args.compact].flatten)
78
+ end
79
+
80
+ # Generates routes for explorer messages paths
81
+ #
82
+ # @param args [Array<String>] sub-paths
83
+ # @return [String] explorer messages path
84
+ def explorer_messages_path(*args)
85
+ explorer_path(*['messages', args.compact].flatten)
86
+ end
87
+
88
+ # Helps build topics paths
89
+ #
90
+ # @param args [Array<String>] path params for the topics scope
91
+ # @return [String] topics scope path
92
+ def topics_path(*args)
93
+ root_path('topics', *args)
94
+ end
95
+
96
+ # Helps build consumers paths
97
+ #
98
+ # @param args [Array<String>] path params for consumers scope
99
+ # @return [String] consumers scope path
100
+ def consumers_path(*args)
101
+ root_path('consumers', *args)
102
+ end
103
+
104
+ # Helps build per-consumer scope paths
105
+ #
106
+ # @param consumer_id [String] consumer process id
107
+ # @param args [Array<String>] other path components
108
+ # @return [String] per consumer specific path
109
+ def consumer_path(consumer_id, *args)
110
+ consumers_path(consumer_id, *args)
67
111
  end
68
112
 
69
113
  # Helps build scheduled messages paths.
@@ -80,7 +124,7 @@ module Karafka
80
124
  action = nil
81
125
  )
82
126
  root_path(
83
- *['scheduled_messages', 'explorer', topic_name, partition_id, offset, action].compact
127
+ *['scheduled_messages', 'explorer', 'topics', topic_name, partition_id, offset, action].compact
84
128
  )
85
129
  end
86
130
  end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Web
5
+ module Ui
6
+ module Helpers
7
+ # Helper with time-related methods
8
+ module TimeHelper
9
+ # @param time [Float] UTC time float
10
+ # @return [String] relative time tag for timeago.js
11
+ def relative_time(time)
12
+ stamp = Time.at(time).getutc.iso8601(3)
13
+ %(<time class="ltr" dir="ltr" title="#{stamp}" datetime="#{stamp}">#{time}</time>)
14
+ end
15
+
16
+ # @param time [Time] time object we want to present with detailed ms label
17
+ # @return [String] span tag with raw timestamp as a title and time as a value
18
+ def time_with_label(time)
19
+ stamp = (time.to_f * 1000).to_i
20
+
21
+ %(<span title="#{stamp}">#{time}</span>)
22
+ end
23
+
24
+ # Converts raw second count into human readable form like "12.2 minutes". etc based on
25
+ # number of seconds
26
+ #
27
+ # @param seconds [Numeric] number of seconds
28
+ # @return [String] human readable time
29
+ def human_readable_time(seconds)
30
+ case seconds
31
+ when 0..59
32
+ "#{seconds.round(2)} seconds"
33
+ when 60..3_599
34
+ minutes = seconds / 60.0
35
+ "#{minutes.round(2)} minutes"
36
+ when 3_600..86_399
37
+ hours = seconds / 3_600.0
38
+ "#{hours.round(2)} hours"
39
+ else
40
+ days = seconds / 86_400.0
41
+ "#{days.round(2)} days"
42
+ end
43
+ end
44
+
45
+ # @param state [String] poll state
46
+ # @param state_ch [Integer] time until next change of the poll state
47
+ # (from paused to active)
48
+ # @return [String] span tag with label and title with change time if present
49
+ def poll_state_with_change_time_label(state, state_ch)
50
+ year_in_seconds = 131_556_926
51
+ state_ch_in_seconds = state_ch / 1_000.0
52
+
53
+ # If state is active, there is no date of change
54
+ if state == 'active'
55
+ %(
56
+ <span class="badge #{kafka_state_badge(state)}">#{state}</span>
57
+ )
58
+ elsif state_ch_in_seconds > year_in_seconds
59
+ %(
60
+ <span
61
+ class="badge #{kafka_state_badge(state)}"
62
+ title="until manual resume"
63
+ >
64
+ #{state}
65
+ </span>
66
+ )
67
+ else
68
+ %(
69
+ <span
70
+ class="badge #{kafka_state_badge(state)} time-title"
71
+ title="#{Time.now + state_ch_in_seconds}"
72
+ >
73
+ #{state}
74
+ </span>
75
+ )
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Web
5
+ module Ui
6
+ module Helpers
7
+ # Helper module for formatting Kafka topic and partition information
8
+ # in various contexts within the Karafka Web UI.
9
+ #
10
+ # This module provides consistent formatting for topic-partition assignments
11
+ # across different display contexts like inline text, labels, and identifiers.
12
+ #
13
+ # @see https://karafka.io/docs/Development-Naming-Conventions
14
+ module TopicsHelper
15
+ # Default limit for displaying partitions before truncation
16
+ DEFAULT_LIMIT = 5
17
+
18
+ # Formats topic and partitions for inline text display in views.
19
+ #
20
+ # This method is optimized for compact display in assignments, logs, and
21
+ # other inline contexts where space is limited.
22
+ #
23
+ # @param topic [String] the Kafka topic name
24
+ # @param partitions [Array<Integer>, Integer] partition number(s) to format
25
+ # @param limit [Integer] maximum number of partitions to display before truncation
26
+ # @return [String] formatted topic-partition string
27
+ #
28
+ # @example Single partition
29
+ # topics_assignment_text("user-events", 0)
30
+ # # => "user-events-[0]"
31
+ #
32
+ # @example Multiple consecutive partitions
33
+ # topics_assignment_text("user-events", [0, 1, 2, 3])
34
+ # # => "user-events-[0-3]"
35
+ #
36
+ # @example Multiple non-consecutive partitions
37
+ # topics_assignment_text("user-events", [0, 2, 4])
38
+ # # => "user-events-[0,2,4]"
39
+ #
40
+ # @example Truncated partitions list
41
+ # topics_assignment_text("user-events", [0, 1, 2, 3, 4, 5, 6], limit: 3)
42
+ # # => "user-events-[0,1,2...]"
43
+ #
44
+ # @example Empty partitions
45
+ # topics_assignment_text("user-events", [])
46
+ # # => "user-events"
47
+ def topics_assignment_text(topic, partitions, limit: DEFAULT_LIMIT)
48
+ partitions = Array(partitions)
49
+
50
+ if partitions.empty?
51
+ topic
52
+ elsif partitions.size == 1
53
+ "#{topic}-[#{partitions.first}]"
54
+ else
55
+ sorted_partitions = partitions.map(&:to_i).sort
56
+ # Check for consecutive first (best representation)
57
+ if topics_consecutive?(sorted_partitions) && sorted_partitions.size > 2
58
+ "#{topic}-[#{sorted_partitions.first}-#{sorted_partitions.last}]"
59
+ # Apply limit if specified and partitions exceed it
60
+ elsif limit && sorted_partitions.size > limit
61
+ displayed_partitions = sorted_partitions.first(limit)
62
+ "#{topic}-[#{displayed_partitions.join(',')}...]"
63
+ else
64
+ "#{topic}-[#{sorted_partitions.join(',')}]"
65
+ end
66
+ end
67
+ end
68
+
69
+ # Formats topic and partitions for human-readable labels and headers.
70
+ #
71
+ # This method provides more descriptive formatting suitable for page titles,
72
+ # section headers, and other contexts where additional context is helpful.
73
+ #
74
+ # @param topic [String] the Kafka topic name
75
+ # @param partitions [Array<Integer>, Integer] partition number(s) to format
76
+ # @param limit [Integer] maximum number of partitions to display before truncation
77
+ # @return [String] formatted topic-partition label with additional context
78
+ #
79
+ # @example Consecutive partitions with count
80
+ # topics_assignment_label("user-events", [0, 1, 2, 3])
81
+ # # => "user-events-[0-3] (4 partitions total)"
82
+ #
83
+ # @example Truncated with remaining count
84
+ # topics_assignment_label("user-events", [0, 1, 2, 3, 4, 5], limit: 3)
85
+ # # => "user-events-[0,1,2] (+3 more)"
86
+ #
87
+ # @example Non-consecutive partitions
88
+ # topics_assignment_label("user-events", [0, 2, 4])
89
+ # # => "user-events-[0,2,4]"
90
+ def topics_assignment_label(topic, partitions, limit: DEFAULT_LIMIT)
91
+ partitions = Array(partitions)
92
+
93
+ sorted_partitions = partitions.map(&:to_i).sort
94
+ if topics_consecutive?(sorted_partitions)
95
+ "#{topic}-[#{sorted_partitions.first}-#{sorted_partitions.last}] " \
96
+ "(#{partitions.size} partitions total)"
97
+ elsif sorted_partitions.size > limit
98
+ displayed = sorted_partitions.first(limit)
99
+ remaining = sorted_partitions.size - limit
100
+ "#{topic}-[#{displayed.join(',')}] (+#{remaining} more)"
101
+ else
102
+ "#{topic}-[#{sorted_partitions.join(',')}]"
103
+ end
104
+ end
105
+
106
+ # Creates a specific identifier for topic-partition combinations.
107
+ #
108
+ # This method generates consistent identifiers used in metrics collection,
109
+ # cache keys, and other contexts requiring unique topic-partition identification.
110
+ #
111
+ # @param topic [String] the Kafka topic name
112
+ # @param partition [Integer] the partition number
113
+ # @return [String] formatted topic-partition identifier
114
+ #
115
+ # @example Basic identifier
116
+ # topics_partition_identifier("user-events", 0)
117
+ # # => "user-events-0"
118
+ #
119
+ # @example Used for cache keys
120
+ # cache_key = topics_partition_identifier("orders", 3)
121
+ # Rails.cache.fetch(cache_key) { expensive_operation }
122
+ def topics_partition_identifier(topic, partition)
123
+ "#{topic}-#{partition}"
124
+ end
125
+
126
+ private
127
+
128
+ # Checks if an array of sorted integers contains consecutive numbers.
129
+ #
130
+ # This helper method determines whether partition numbers form a continuous
131
+ # sequence, which allows for more compact display formatting.
132
+ #
133
+ # @param sorted_array [Array<Integer>] array of sorted integers to check
134
+ # @return [Boolean] true if all numbers are consecutive, false otherwise
135
+ #
136
+ # @example Consecutive numbers
137
+ # topics_consecutive?([1, 2, 3, 4]) # => true
138
+ #
139
+ # @example Non-consecutive numbers
140
+ # topics_consecutive?([1, 3, 5, 7]) # => false
141
+ #
142
+ # @example Single element (not consecutive)
143
+ # topics_consecutive?([1]) # => false
144
+ #
145
+ # @example Empty array (not consecutive)
146
+ # topics_consecutive?([]) # => false
147
+ def topics_consecutive?(sorted_array)
148
+ return false if sorted_array.size < 2
149
+
150
+ sorted_array.each_cons(2).all? { |a, b| b == a + 1 }
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
@@ -17,7 +17,7 @@ module Karafka
17
17
  class << self
18
18
  extend Forwardable
19
19
 
20
- def_delegators ::Karafka::Admin, :read_watermark_offsets, :cluster_info
20
+ def_delegators ::Karafka::Admin, :read_watermark_offsets, :cluster_info, :topic_info
21
21
 
22
22
  # Allows us to read messages from the topic
23
23
  #
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Web
5
+ module Ui
6
+ module Lib
7
+ # Thread-safe in-memory cache with metadata tracking.
8
+ #
9
+ # This cache supports storing computed values, tracking the last update time,
10
+ # and computing a hash of the contents for change detection.
11
+ # It's designed for ephemeral, per-instance caching in Karafka Web controllers or libs.
12
+ #
13
+ # The cache ensures safe concurrent access via a mutex and provides utilities
14
+ # for cache invalidation based on external session state (timestamp + hash).
15
+ #
16
+ # @note All cache operations are mutex-synchronized for thread safety.
17
+ #
18
+ # @note We do not have granular level caching because our Web UI cache is fairly simple
19
+ # and we do not want to overcomplicate things.
20
+ class Cache
21
+ # Initializes an empty cache instance
22
+ # @param ttl_ms [Integer] time to live of the whole cache. After this time cache will be
23
+ # cleaned whether or not it is expired.
24
+ def initialize(ttl_ms)
25
+ @ttl_ms = ttl_ms
26
+ @values = {}
27
+ @timestamp = nil
28
+ @hash = nil
29
+ @mutex = Mutex.new
30
+ end
31
+
32
+ # Fetches or computes and stores a value under the given key.
33
+ #
34
+ # If the key already exists, returns the cached value.
35
+ # Otherwise, computes it via the provided block, stores it,
36
+ # and updates metadata (timestamp + hash).
37
+ #
38
+ # @param key [Object] key to retrieve
39
+ # @yield block to compute the value if key is not present
40
+ # @return [Object] cached or computed value
41
+ def fetch(key)
42
+ @mutex.synchronize do
43
+ return @values[key] if @values.key?(key)
44
+
45
+ @values[key] = yield
46
+ @hash = Digest::SHA256.hexdigest(
47
+ @values.sort.to_h.to_json
48
+ )
49
+ @timestamp = Time.now.to_f
50
+ @values[key]
51
+ end
52
+ end
53
+
54
+ # Clears the cache and resets metadata (timestamp and hash).
55
+ #
56
+ # If the mutex is already owned by the current thread, clears immediately.
57
+ # Otherwise, synchronizes first.
58
+ #
59
+ # @return [void]
60
+ def clear
61
+ cleaning = lambda do
62
+ @values.clear
63
+ @timestamp = nil
64
+ @hash = nil
65
+ end
66
+
67
+ return cleaning.call if @mutex.owned?
68
+
69
+ @mutex.synchronize do
70
+ cleaning.call
71
+ end
72
+ end
73
+
74
+ # Checks whether any values have been cached yet
75
+ #
76
+ # @return [Boolean] true if the cache has been written to
77
+ def exist?
78
+ !timestamp.nil?
79
+ end
80
+
81
+ # Returns the last update timestamp of the cache
82
+ #
83
+ # @return [Integer, nil] Unix timestamp or nil if never set
84
+ def timestamp
85
+ @mutex.synchronize { @timestamp }
86
+ end
87
+
88
+ # Returns the hash representing the current cached data state
89
+ #
90
+ # @return [String, nil] SHA256 hex digest or nil if never set
91
+ def hash
92
+ @mutex.synchronize { @hash }
93
+ end
94
+
95
+ # Clears the cache if the provided session hash and timestamp differ
96
+ #
97
+ # This is used to invalidate the cache if the external session data indicates
98
+ # a newer or inconsistent state.
99
+ #
100
+ # @param session_hash [String, nil] hash from the session or remote side
101
+ # @param session_timestamp [Integer, nil] timestamp from the session
102
+ # @return [Boolean] true if the cache was cleared, false otherwise
103
+ def clear_if_needed(session_hash, session_timestamp)
104
+ @mutex.synchronize do
105
+ return unless should_refresh?(session_hash, session_timestamp)
106
+
107
+ clear
108
+ end
109
+ end
110
+
111
+ private
112
+
113
+ # Determines whether the cache should be refreshed based on session data
114
+ #
115
+ # @param session_hash [String, nil]
116
+ # @param session_timestamp [Integer, nil]
117
+ # @return [Boolean] true if cache should be refreshed
118
+ def should_refresh?(session_hash, session_timestamp)
119
+ return true if @hash.nil? || @timestamp.nil?
120
+ return true if session_hash.nil? || session_timestamp.nil?
121
+
122
+ now = (Time.now.to_f * 1_000).to_i
123
+
124
+ return true if now - (@timestamp * 1_000) > @ttl_ms
125
+
126
+ return false if @hash == session_hash
127
+ return false if @timestamp > session_timestamp
128
+
129
+ true
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -9,8 +9,7 @@ module Karafka
9
9
  class << self
10
10
  # @return [Array<Broker>] all brokers in the cluster
11
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|
12
+ ClusterInfo.fetch.brokers.map do |broker|
14
13
  new(broker)
15
14
  end
16
15
  end
@@ -10,26 +10,18 @@ module Karafka
10
10
  class << self
11
11
  # Gets us all the cluster metadata info
12
12
  #
13
- # @param cached [Boolean] should we use cached data (true by default)
14
13
  # @return [Rdkafka::Metadata] cluster metadata info
15
- def fetch(cached: true)
16
- cache = ::Karafka::Web.config.ui.cache
17
-
18
- cluster_info = cache.read(:cluster_info)
19
-
20
- if cluster_info.nil? || !cached
21
- cluster_info = cache.write(:cluster_info, Lib::Admin.cluster_info)
14
+ def fetch
15
+ Karafka::Web.config.ui.cache.fetch(:cluster_info) do
16
+ Lib::Admin.cluster_info
22
17
  end
23
-
24
- cluster_info
25
18
  end
26
19
 
27
20
  # Returns us all the info about available topics from the cluster
28
21
  #
29
- # @param cached [Boolean] should we use cached data (true by default)
30
22
  # @return [Array<Ui::Models::Topic>] topics details
31
- def topics(cached: true)
32
- fetch(cached: cached)
23
+ def topics
24
+ fetch
33
25
  .topics
34
26
  .map { |topic| Topic.new(topic) }
35
27
  end
@@ -37,19 +29,21 @@ module Karafka
37
29
  # Fetches us details about particular topic
38
30
  #
39
31
  # @param topic_name [String] name of the topic we are looking for
40
- # @param cached [Boolean] should we use cached data (true by default)
41
32
  # @return [Ui::Models::Topic] topic details
42
- def topic(topic_name, cached: true)
43
- topics(cached: cached)
44
- .find { |topic_data| topic_data.topic_name == topic_name }
45
- .tap { |topic| topic || raise(Web::Errors::Ui::NotFoundError, topic_name) }
33
+ def topic(topic_name)
34
+ Lib::Admin
35
+ .topic_info(topic_name)
36
+ .then { |topic| Topic.new(topic) }
37
+ rescue Rdkafka::RdkafkaError => e
38
+ raise e unless e.code == :unknown_topic_or_part
39
+
40
+ raise(Web::Errors::Ui::NotFoundError, topic_name)
46
41
  end
47
42
 
48
43
  # @param topic_name [String] name of the topic we are looking for
49
- # @param cached [Boolean] should we use cached data (true by default)
50
44
  # @return [Integer] number of partitions in a given topic
51
- def partitions_count(topic_name, cached: true)
52
- topic(topic_name, cached: cached).partition_count
45
+ def partitions_count(topic_name)
46
+ topic(topic_name).partition_count
53
47
  end
54
48
  end
55
49
  end
@@ -33,7 +33,7 @@ module Karafka
33
33
  # @return [::Karafka::Messages::Message, nil] most recent state or nil if none
34
34
  def fetch
35
35
  Lib::Admin.read_topic(
36
- Karafka::Web.config.topics.consumers.metrics,
36
+ Karafka::Web.config.topics.consumers.metrics.name,
37
37
  0,
38
38
  # We need to take last two and not the last because in case of a transactional
39
39
  # producer, the last one will match the transaction commit message
@@ -42,7 +42,7 @@ module Karafka
42
42
  # @return [::Karafka::Messages::Message, nil] most recent state or nil if none
43
43
  def fetch
44
44
  Lib::Admin.read_topic(
45
- Karafka::Web.config.topics.consumers.states,
45
+ Karafka::Web.config.topics.consumers.states.name,
46
46
  0,
47
47
  # We need to take last two and not the last because in case of a transactional
48
48
  # producer, the last one will match the transaction commit message
@@ -32,7 +32,7 @@ module Karafka
32
32
  MAX_ERROR_PARTITIONS.times do |partition|
33
33
  begin
34
34
  offsets = Lib::Admin.read_watermark_offsets(
35
- ::Karafka::Web.config.topics.errors,
35
+ ::Karafka::Web.config.topics.errors.name,
36
36
  partition
37
37
  )
38
38
  # We estimate that way instead of using `#cluster_info` to get the partitions count