karafka-web 0.10.0 → 0.10.2

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 (69) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.ruby-version +1 -1
  4. data/CHANGELOG.md +17 -0
  5. data/Gemfile +1 -0
  6. data/Gemfile.lock +12 -4
  7. data/certs/cert.pem +26 -0
  8. data/karafka-web.gemspec +2 -2
  9. data/lib/karafka/web/pro/ui/app.rb +75 -0
  10. data/lib/karafka/web/pro/ui/controllers/commanding_controller.rb +4 -4
  11. data/lib/karafka/web/pro/ui/controllers/recurring_tasks_controller.rb +131 -0
  12. data/lib/karafka/web/pro/ui/controllers/scheduled_messages/base_controller.rb +29 -0
  13. data/lib/karafka/web/pro/ui/controllers/scheduled_messages/explorer_controller.rb +65 -0
  14. data/lib/karafka/web/pro/ui/controllers/scheduled_messages/schedules_controller.rb +84 -0
  15. data/lib/karafka/web/pro/ui/views/consumers/_consumer_controls.erb +13 -12
  16. data/lib/karafka/web/pro/ui/views/explorer/_partition_option.erb +10 -2
  17. data/lib/karafka/web/pro/ui/views/recurring_tasks/_actions.erb +58 -0
  18. data/lib/karafka/web/pro/ui/views/recurring_tasks/_batch_actions.erb +45 -0
  19. data/lib/karafka/web/pro/ui/views/recurring_tasks/_breadcrumbs.erb +22 -0
  20. data/lib/karafka/web/pro/ui/views/recurring_tasks/_log.erb +26 -0
  21. data/lib/karafka/web/pro/ui/views/recurring_tasks/_not_active.erb +12 -0
  22. data/lib/karafka/web/pro/ui/views/recurring_tasks/_tabs.erb +17 -0
  23. data/lib/karafka/web/pro/ui/views/recurring_tasks/_task.erb +46 -0
  24. data/lib/karafka/web/pro/ui/views/recurring_tasks/logs.erb +34 -0
  25. data/lib/karafka/web/pro/ui/views/recurring_tasks/schedule.erb +43 -0
  26. data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/_breadcrumbs.erb +30 -0
  27. data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/_message.erb +84 -0
  28. data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/_messages.erb +28 -0
  29. data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/partition.erb +72 -0
  30. data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/topic.erb +33 -0
  31. data/lib/karafka/web/pro/ui/views/scheduled_messages/schedules/_breadcrumbs.erb +21 -0
  32. data/lib/karafka/web/pro/ui/views/scheduled_messages/schedules/_no_groups.erb +11 -0
  33. data/lib/karafka/web/pro/ui/views/scheduled_messages/schedules/index.erb +38 -0
  34. data/lib/karafka/web/pro/ui/views/scheduled_messages/schedules/show.erb +48 -0
  35. data/lib/karafka/web/pro/ui/views/search/_breadcrumbs.erb +1 -0
  36. data/lib/karafka/web/pro/ui/views/shared/_navigation.erb +20 -0
  37. data/lib/karafka/web/pro/ui/views/topics/replication.erb +1 -1
  38. data/lib/karafka/web/tracking/consumers/listeners/errors.rb +3 -1
  39. data/lib/karafka/web/ui/base.rb +1 -1
  40. data/lib/karafka/web/ui/controllers/base_controller.rb +18 -3
  41. data/lib/karafka/web/ui/helpers/application_helper.rb +29 -8
  42. data/lib/karafka/web/ui/helpers/paths_helper.rb +18 -0
  43. data/lib/karafka/web/ui/models/recurring_tasks/log.rb +15 -0
  44. data/lib/karafka/web/ui/models/recurring_tasks/schedule.rb +75 -0
  45. data/lib/karafka/web/ui/models/recurring_tasks/task.rb +19 -0
  46. data/lib/karafka/web/ui/public/javascripts/application.js +1 -1
  47. data/lib/karafka/web/ui/public/javascripts/application.min.js +7 -7
  48. data/lib/karafka/web/ui/public/javascripts/application.min.js.br +0 -0
  49. data/lib/karafka/web/ui/public/javascripts/application.min.js.gz +0 -0
  50. data/lib/karafka/web/ui/public/javascripts/components/live_poll.js +51 -0
  51. data/lib/karafka/web/ui/public/stylesheets/application.css +5 -1
  52. data/lib/karafka/web/ui/public/stylesheets/application.min.css +2 -2
  53. data/lib/karafka/web/ui/public/stylesheets/application.min.css.br +0 -0
  54. data/lib/karafka/web/ui/public/stylesheets/application.min.css.gz +0 -0
  55. data/lib/karafka/web/ui/public/stylesheets/libs/tailwind.css +18 -6
  56. data/lib/karafka/web/ui/views/dashboard/_ranges_selector.erb +1 -1
  57. data/lib/karafka/web/ui/views/shared/_breadcrumbs.erb +1 -1
  58. data/lib/karafka/web/ui/views/shared/_navigation.erb +21 -0
  59. data/lib/karafka/web/ui/views/shared/icons/_calendar.erb +3 -0
  60. data/lib/karafka/web/ui/views/shared/icons/_check.erb +3 -0
  61. data/lib/karafka/web/ui/views/shared/icons/_play.erb +3 -0
  62. data/lib/karafka/web/ui/views/status/info/_components.erb +48 -11
  63. data/lib/karafka/web/ui/views/ux/_data_table.erb +34 -4
  64. data/lib/karafka/web/ui/views/ux/_status_rows.erb +12 -0
  65. data/lib/karafka/web/version.rb +1 -1
  66. data.tar.gz.sig +0 -0
  67. metadata +56 -27
  68. metadata.gz.sig +0 -0
  69. data/certs/cert_chain.pem +0 -26
@@ -0,0 +1,48 @@
1
+ <% view_title @schedule_name %>
2
+
3
+ <% @states.each do |partition, details| %>
4
+ <h3 class="h3">
5
+ Partition <%= partition %>
6
+ </h3>
7
+
8
+ <% if details %>
9
+ <div class="data-table-wrapper">
10
+ <table class="data-table">
11
+ <thead>
12
+ <tr>
13
+ <th>Date</th>
14
+ <th>For Dispatch</th>
15
+ </tr>
16
+ </thead>
17
+ <tbody>
18
+ <% details[:daily].each do |date, for_dispatch| %>
19
+ <tr>
20
+ <td><%= date %></td>
21
+ <td><%= for_dispatch %></td>
22
+ </tr>
23
+ <% end %>
24
+ </tbody>
25
+ </table>
26
+
27
+ <p class="table_metadata">
28
+ Metadata:
29
+ <span class="badge">
30
+ Schema version: <%= details[:schema_version] %>
31
+ </span>
32
+
33
+ <span class="badge">
34
+ Reported at:&nbsp;
35
+ <%== relative_time(details[:dispatched_at]) %>
36
+ </span>
37
+
38
+ <span class="badge badge-<%= details[:state] == 'loaded' ? 'success' : 'warning' %>">
39
+ State: <%= details[:state] %>
40
+ </span>
41
+ </p>
42
+ </div>
43
+ <% else %>
44
+ <div class="mb-4">
45
+ <%== alert_warning 'No state information for this partition is available.' %>
46
+ </div>
47
+ <% end %>
48
+ <% end %>
@@ -0,0 +1 @@
1
+ <%== partial 'explorer/breadcrumbs' %>
@@ -62,6 +62,26 @@
62
62
  </a>
63
63
  </li>
64
64
 
65
+ <li>
66
+ <a
67
+ class="sidebar-nav-item <%= nav_class(start_with: '/recurring_tasks') %>"
68
+ href="<%= root_path('recurring_tasks') %>"
69
+ >
70
+ <%== icon(:arrow_path_rounded) %>
71
+ Cron
72
+ </a>
73
+ </li>
74
+
75
+ <li>
76
+ <a
77
+ class="sidebar-nav-item <%= nav_class(start_with: '/scheduled_messages') %>"
78
+ href="<%= root_path('scheduled_messages') %>"
79
+ >
80
+ <%== icon(:calendar) %>
81
+ Schedules
82
+ </a>
83
+ </li>
84
+
65
85
  <li>
66
86
  <a
67
87
  class="sidebar-nav-item <%= nav_class(start_with: '/errors') %>"
@@ -9,7 +9,7 @@
9
9
  <th><%== sort_link(:partition_id) %></th>
10
10
  <th><%== sort_link(:leader) %></th>
11
11
  <th><%== sort_link(:replica_count) %></th>
12
- <th><%== sort_link('In sync brokers', :in_sync_replica_brokers) %></th>
12
+ <th><%== sort_link('In Sync Brokers', :in_sync_replica_brokers) %></th>
13
13
  </tr>
14
14
  </thead>
15
15
  <tbody>
@@ -73,7 +73,9 @@ module Karafka
73
73
  partition: consumer.partition,
74
74
  first_offset: consumer.messages.metadata.first_offset,
75
75
  last_offset: consumer.messages.metadata.last_offset,
76
- committed_offset: consumer.coordinator.seek_offset - 1,
76
+ # We set it to -1000 if non-existent because after subtracting one, we will end up
77
+ # with -1001, which is "N/A" offset position for all the offsets here
78
+ committed_offset: (consumer.coordinator.seek_offset || -1_000) - 1,
77
79
  consumer: consumer.class.to_s,
78
80
  tags: consumer.tags
79
81
  }
@@ -133,7 +133,7 @@ module Karafka
133
133
  # @param query_data [Hash] query params we want to add to the current path
134
134
  path :current do |query_data = {}|
135
135
  # Merge existing request parameters with new query data
136
- merged_params = request.params.deep_merge(query_data)
136
+ merged_params = deep_merge(request.params, query_data)
137
137
 
138
138
  # Flatten the merged parameters
139
139
  flattened_params = flatten_params('', merged_params)
@@ -28,13 +28,28 @@ module Karafka
28
28
 
29
29
  # Builds the render data object with assigned attributes based on instance variables.
30
30
  #
31
+ # @param attributes [Hash] attributes coming from the outside (in case of rebind)
31
32
  # @return [Responses::Render] data that should be used to render appropriate view
32
- def render
33
- attributes = {}
33
+ def render(attributes: {})
34
+ attributes = attributes.dup
35
+
36
+ full_parts = self.class.to_s.split('::')
37
+ separator = full_parts.index('Controllers')
38
+ base = full_parts[(separator + 1)..]
39
+
40
+ base.map!.with_index do |path_part, index|
41
+ if index == (base.size - 1)
42
+ path_part.gsub(/(.)([A-Z])/, '\1_\2').downcase.gsub('_controller', '')
43
+ else
44
+ path_part.gsub(/(.)([A-Z])/, '\1_\2').downcase
45
+ end
46
+ end
34
47
 
35
- scope = self.class.to_s.split('::').last.gsub(/(.)([A-Z])/, '\1_\2').downcase.gsub('_controller', '')
48
+ scope = base.join('/')
36
49
  action = caller_locations(1, 1)[0].label.split('#').last
37
50
 
51
+ attributes[:breadcrums_scope] = scope
52
+
38
53
  instance_variables.each do |iv|
39
54
  next if iv.to_s.start_with?('@_')
40
55
  next if iv.to_s.start_with?('@params')
@@ -50,13 +50,6 @@ module Karafka
50
50
  object.to_s.include?('#<') ? object.class.to_s : object.to_s
51
51
  end
52
52
 
53
- # Renders per scope breadcrumbs
54
- def render_breadcrumbs
55
- scope = request.path.delete_prefix(root_path).split('/')[0]
56
-
57
- render "#{scope}/_breadcrumbs"
58
- end
59
-
60
53
  # Takes a status and recommends background style color
61
54
  #
62
55
  # @param status [String] status
@@ -309,7 +302,14 @@ module Karafka
309
302
  def sort_link(name, attribute = nil, rev: false)
310
303
  unless attribute
311
304
  attribute = name
312
- name = SORT_NAMES[attribute] || attribute.to_s.tr('_', ' ').tr('?', '').capitalize
305
+
306
+ if SORT_NAMES[attribute]
307
+ name = SORT_NAMES[attribute]
308
+ else
309
+ name = attribute.to_s.tr('_', ' ').tr('?', '')
310
+ # Always capitalize the name
311
+ name = name.split(' ').map(&:capitalize).join(' ')
312
+ end
313
313
  end
314
314
 
315
315
  arrow_both = '&#x21D5;'
@@ -369,6 +369,27 @@ module Karafka
369
369
  def icon(name)
370
370
  render "shared/icons/_#{name}"
371
371
  end
372
+
373
+ # Merges two hashes deeply, combining nested hashes recursively.
374
+ #
375
+ # @param hash1 [Hash] The first hash to merge.
376
+ # @param hash2 [Hash] The second hash to merge.
377
+ # @return [Hash] A new hash that is the result of a deep merge of the two provided hashes.
378
+ def deep_merge(hash1, hash2)
379
+ merged_hash = hash1.dup
380
+
381
+ hash2.each_pair do |k, v|
382
+ tv = merged_hash[k]
383
+
384
+ merged_hash[k] = if tv.is_a?(Hash) && v.is_a?(Hash)
385
+ deep_merge(tv, v)
386
+ else
387
+ v
388
+ end
389
+ end
390
+
391
+ merged_hash
392
+ end
372
393
  end
373
394
  end
374
395
  end
@@ -65,6 +65,24 @@ module Karafka
65
65
  def explorer_path(topic_name = nil, partition_id = nil, offset = nil, action = nil)
66
66
  root_path(*['explorer', topic_name, partition_id, offset, action].compact)
67
67
  end
68
+
69
+ # Helps build scheduled messages paths.
70
+ # Similar to the explorer helper one
71
+ # @param topic_name [String]
72
+ # @param partition_id [Integer, nil]
73
+ # @param offset [Integer, nil]
74
+ # @param action [String, nil]
75
+ # @return [String] path to the expected location
76
+ def scheduled_messages_explorer_path(
77
+ topic_name = nil,
78
+ partition_id = nil,
79
+ offset = nil,
80
+ action = nil
81
+ )
82
+ root_path(
83
+ *['scheduled_messages', 'explorer', topic_name, partition_id, offset, action].compact
84
+ )
85
+ end
68
86
  end
69
87
  end
70
88
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Web
5
+ module Ui
6
+ module Models
7
+ module RecurringTasks
8
+ # Single log entry for recurring tasks execution
9
+ class Log < Lib::HashProxy
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Web
5
+ module Ui
6
+ module Models
7
+ # Namespace for models representing recurring tasks related components taken from Kafka
8
+ module RecurringTasks
9
+ # Karafka schedule representation
10
+ class Schedule < Web::Ui::Lib::HashProxy
11
+ # Rdkafka errors we expect and handle gracefully
12
+ EXPECTED_RDKAFKA_ERRORS = %i[
13
+ unknown_topic
14
+ unknown_partition
15
+ unknown_topic_or_part
16
+ ].freeze
17
+
18
+ private_constant :EXPECTED_RDKAFKA_ERRORS
19
+
20
+ class << self
21
+ # @return [Schedule, false] current schedule or false if it was not possible to
22
+ # get it because requested topic/partition does not exist or nothing was present
23
+ def current
24
+ messages = Karafka::Admin.read_topic(
25
+ config.topics.schedules,
26
+ 0,
27
+ # We work here with the assumption that users won't click so fast to load
28
+ # more than 20 commands prior to a state flush. If that happens, this will
29
+ # return false. This is a known and expected limitation.
30
+ 20
31
+ )
32
+
33
+ # Out of those messages we pick the most recent persisted schedule
34
+ candidate = messages
35
+ .reverse
36
+ .find { |message| message.key == 'state:schedule' }
37
+
38
+ # If there is a schedule message we use its data to build schedule, if not false
39
+ return false unless candidate
40
+
41
+ # If the deserializer is not our dedicated recurring tasks deserializer, it means
42
+ # that routing for recurring tasks was not loaded, so recurring tasks are not
43
+ # active
44
+ #
45
+ # User might have used recurring tasks previously and disabled them, but still may
46
+ # navigate to them and then we should not show anything because without the
47
+ # correct deserializer it will crash anyhow
48
+ return false unless candidate.metadata.deserializers.payload == config.deserializer
49
+
50
+ new(candidate.payload)
51
+ rescue Rdkafka::RdkafkaError => e
52
+ # If any of "topic missing" is raised, we return false but other errors we re-raise
53
+ raise(e) unless EXPECTED_RDKAFKA_ERRORS.any? { |code| e.code == code }
54
+
55
+ false
56
+ end
57
+
58
+ private
59
+
60
+ # @return [Karafka::Core::Configurable::Node]
61
+ def config
62
+ Karafka::App.config.recurring_tasks
63
+ end
64
+ end
65
+
66
+ # @return [Array<Task>] tasks of the current schedule
67
+ def tasks
68
+ @tasks ||= super.values.map { |task_hash| Task.new(task_hash) }
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Web
5
+ module Ui
6
+ module Models
7
+ module RecurringTasks
8
+ # Represents a single recurring task
9
+ class Task < Lib::HashProxy
10
+ # @return [Boolean] true if this task is enabled, otherwise false
11
+ def enabled?
12
+ enabled
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -119,4 +119,4 @@ function addListeners() {
119
119
 
120
120
  document.addEventListener('turbo:load', addListeners);
121
121
 
122
- Turbo.setProgressBarDelay(250)
122
+ Turbo.setProgressBarDelay(100)