karafka-web 0.10.0 → 0.10.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|