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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +17 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +12 -4
- data/certs/cert.pem +26 -0
- data/karafka-web.gemspec +2 -2
- data/lib/karafka/web/pro/ui/app.rb +75 -0
- data/lib/karafka/web/pro/ui/controllers/commanding_controller.rb +4 -4
- data/lib/karafka/web/pro/ui/controllers/recurring_tasks_controller.rb +131 -0
- data/lib/karafka/web/pro/ui/controllers/scheduled_messages/base_controller.rb +29 -0
- data/lib/karafka/web/pro/ui/controllers/scheduled_messages/explorer_controller.rb +65 -0
- data/lib/karafka/web/pro/ui/controllers/scheduled_messages/schedules_controller.rb +84 -0
- data/lib/karafka/web/pro/ui/views/consumers/_consumer_controls.erb +13 -12
- data/lib/karafka/web/pro/ui/views/explorer/_partition_option.erb +10 -2
- data/lib/karafka/web/pro/ui/views/recurring_tasks/_actions.erb +58 -0
- data/lib/karafka/web/pro/ui/views/recurring_tasks/_batch_actions.erb +45 -0
- data/lib/karafka/web/pro/ui/views/recurring_tasks/_breadcrumbs.erb +22 -0
- data/lib/karafka/web/pro/ui/views/recurring_tasks/_log.erb +26 -0
- data/lib/karafka/web/pro/ui/views/recurring_tasks/_not_active.erb +12 -0
- data/lib/karafka/web/pro/ui/views/recurring_tasks/_tabs.erb +17 -0
- data/lib/karafka/web/pro/ui/views/recurring_tasks/_task.erb +46 -0
- data/lib/karafka/web/pro/ui/views/recurring_tasks/logs.erb +34 -0
- data/lib/karafka/web/pro/ui/views/recurring_tasks/schedule.erb +43 -0
- data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/_breadcrumbs.erb +30 -0
- data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/_message.erb +84 -0
- data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/_messages.erb +28 -0
- data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/partition.erb +72 -0
- data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/topic.erb +33 -0
- data/lib/karafka/web/pro/ui/views/scheduled_messages/schedules/_breadcrumbs.erb +21 -0
- data/lib/karafka/web/pro/ui/views/scheduled_messages/schedules/_no_groups.erb +11 -0
- data/lib/karafka/web/pro/ui/views/scheduled_messages/schedules/index.erb +38 -0
- data/lib/karafka/web/pro/ui/views/scheduled_messages/schedules/show.erb +48 -0
- data/lib/karafka/web/pro/ui/views/search/_breadcrumbs.erb +1 -0
- data/lib/karafka/web/pro/ui/views/shared/_navigation.erb +20 -0
- data/lib/karafka/web/pro/ui/views/topics/replication.erb +1 -1
- data/lib/karafka/web/tracking/consumers/listeners/errors.rb +3 -1
- data/lib/karafka/web/ui/base.rb +1 -1
- data/lib/karafka/web/ui/controllers/base_controller.rb +18 -3
- data/lib/karafka/web/ui/helpers/application_helper.rb +29 -8
- data/lib/karafka/web/ui/helpers/paths_helper.rb +18 -0
- data/lib/karafka/web/ui/models/recurring_tasks/log.rb +15 -0
- data/lib/karafka/web/ui/models/recurring_tasks/schedule.rb +75 -0
- data/lib/karafka/web/ui/models/recurring_tasks/task.rb +19 -0
- data/lib/karafka/web/ui/public/javascripts/application.js +1 -1
- data/lib/karafka/web/ui/public/javascripts/application.min.js +7 -7
- data/lib/karafka/web/ui/public/javascripts/application.min.js.br +0 -0
- data/lib/karafka/web/ui/public/javascripts/application.min.js.gz +0 -0
- data/lib/karafka/web/ui/public/javascripts/components/live_poll.js +51 -0
- data/lib/karafka/web/ui/public/stylesheets/application.css +5 -1
- data/lib/karafka/web/ui/public/stylesheets/application.min.css +2 -2
- data/lib/karafka/web/ui/public/stylesheets/application.min.css.br +0 -0
- data/lib/karafka/web/ui/public/stylesheets/application.min.css.gz +0 -0
- data/lib/karafka/web/ui/public/stylesheets/libs/tailwind.css +18 -6
- data/lib/karafka/web/ui/views/dashboard/_ranges_selector.erb +1 -1
- data/lib/karafka/web/ui/views/shared/_breadcrumbs.erb +1 -1
- data/lib/karafka/web/ui/views/shared/_navigation.erb +21 -0
- data/lib/karafka/web/ui/views/shared/icons/_calendar.erb +3 -0
- data/lib/karafka/web/ui/views/shared/icons/_check.erb +3 -0
- data/lib/karafka/web/ui/views/shared/icons/_play.erb +3 -0
- data/lib/karafka/web/ui/views/status/info/_components.erb +48 -11
- data/lib/karafka/web/ui/views/ux/_data_table.erb +34 -4
- data/lib/karafka/web/ui/views/ux/_status_rows.erb +12 -0
- data/lib/karafka/web/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +56 -27
- metadata.gz.sig +0 -0
- 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:
|
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
|
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
|
-
|
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
|
}
|
data/lib/karafka/web/ui/base.rb
CHANGED
@@ -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
|
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 =
|
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
|
-
|
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 = '⇕'
|
@@ -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,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
|