karafka-web 0.6.2 → 0.6.3
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/CHANGELOG.md +2 -8
- data/Gemfile.lock +1 -1
- data/lib/karafka/web/config.rb +0 -2
- data/lib/karafka/web/ui/base.rb +2 -6
- data/lib/karafka/web/ui/controllers/base.rb +0 -17
- data/lib/karafka/web/ui/controllers/cluster.rb +2 -5
- data/lib/karafka/web/ui/controllers/consumers.rb +1 -3
- data/lib/karafka/web/ui/controllers/errors.rb +6 -19
- data/lib/karafka/web/ui/controllers/jobs.rb +1 -3
- data/lib/karafka/web/ui/controllers/requests/params.rb +0 -10
- data/lib/karafka/web/ui/lib/paginate_array.rb +38 -0
- data/lib/karafka/web/ui/models/message.rb +38 -114
- data/lib/karafka/web/ui/models/status.rb +1 -3
- data/lib/karafka/web/ui/pro/app.rb +3 -11
- data/lib/karafka/web/ui/pro/controllers/consumers.rb +1 -3
- data/lib/karafka/web/ui/pro/controllers/dlq.rb +2 -1
- data/lib/karafka/web/ui/pro/controllers/errors.rb +10 -43
- data/lib/karafka/web/ui/pro/controllers/explorer.rb +7 -52
- data/lib/karafka/web/ui/pro/views/errors/_breadcrumbs.erb +6 -8
- data/lib/karafka/web/ui/pro/views/errors/_error.erb +1 -1
- data/lib/karafka/web/ui/pro/views/errors/_partition_option.erb +1 -1
- data/lib/karafka/web/ui/pro/views/errors/index.erb +56 -9
- data/lib/karafka/web/ui/pro/views/explorer/_breadcrumbs.erb +1 -1
- data/lib/karafka/web/ui/pro/views/explorer/_message.erb +2 -8
- data/lib/karafka/web/ui/pro/views/explorer/_partition_option.erb +1 -1
- data/lib/karafka/web/ui/pro/views/explorer/_topic.erb +1 -1
- data/lib/karafka/web/ui/pro/views/explorer/partition/_messages.erb +0 -1
- data/lib/karafka/web/ui/pro/views/explorer/partition.erb +1 -1
- data/lib/karafka/web/ui/pro/views/shared/_navigation.erb +1 -1
- data/lib/karafka/web/ui/views/cluster/_partition.erb +1 -1
- data/lib/karafka/web/ui/views/errors/_error.erb +1 -1
- data/lib/karafka/web/ui/views/shared/_pagination.erb +12 -16
- data/lib/karafka/web/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +2 -17
- metadata.gz.sig +0 -0
- data/lib/karafka/web/ui/lib/paginations/base.rb +0 -61
- data/lib/karafka/web/ui/lib/paginations/offset_based.rb +0 -96
- data/lib/karafka/web/ui/lib/paginations/page_based.rb +0 -70
- data/lib/karafka/web/ui/lib/paginations/paginators/arrays.rb +0 -33
- data/lib/karafka/web/ui/lib/paginations/paginators/base.rb +0 -23
- data/lib/karafka/web/ui/lib/paginations/paginators/partitions.rb +0 -52
- data/lib/karafka/web/ui/lib/paginations/paginators/sets.rb +0 -85
- data/lib/karafka/web/ui/lib/ttl_cache.rb +0 -74
- data/lib/karafka/web/ui/models/cluster_info.rb +0 -59
- data/lib/karafka/web/ui/pro/views/errors/_table.erb +0 -21
- data/lib/karafka/web/ui/pro/views/errors/_title_with_select.erb +0 -31
- data/lib/karafka/web/ui/pro/views/errors/partition.erb +0 -17
- data/lib/karafka/web/ui/pro/views/explorer/topic/_empty.erb +0 -3
- data/lib/karafka/web/ui/pro/views/explorer/topic/_limited.erb +0 -4
- data/lib/karafka/web/ui/pro/views/explorer/topic/_partitions.erb +0 -11
- data/lib/karafka/web/ui/pro/views/explorer/topic.erb +0 -49
@@ -1,33 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Karafka
|
4
|
-
module Web
|
5
|
-
module Ui
|
6
|
-
module Lib
|
7
|
-
module Paginations
|
8
|
-
# Namespace for commands that build paginated resources based on the provided page
|
9
|
-
module Paginators
|
10
|
-
# A simple wrapper for paginating array related data structures
|
11
|
-
# We call this with plural (same with `Sets`) to avoid confusion with Ruby classes
|
12
|
-
class Arrays < Base
|
13
|
-
class << self
|
14
|
-
# @param array [Array] array we want to paginate
|
15
|
-
# @param current_page [Integer] page we want to be on
|
16
|
-
# @return [Array<Array, Boolean>] Array with two elements: first is the array with
|
17
|
-
# data of the given page and second is a boolean flag with info if the elements we got
|
18
|
-
# are from the last page
|
19
|
-
def call(array, current_page)
|
20
|
-
slices = array.each_slice(per_page).to_a
|
21
|
-
current_data = slices[current_page - 1] || []
|
22
|
-
last_page = !(slices.count >= current_page - 1 && current_data.size >= per_page)
|
23
|
-
|
24
|
-
[current_data, last_page]
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
@@ -1,23 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Karafka
|
4
|
-
module Web
|
5
|
-
module Ui
|
6
|
-
module Lib
|
7
|
-
module Paginations
|
8
|
-
module Paginators
|
9
|
-
# Base paginator
|
10
|
-
class Base
|
11
|
-
class << self
|
12
|
-
# @return [Integer] number of elements per page
|
13
|
-
def per_page
|
14
|
-
::Karafka::Web.config.ui.per_page
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
@@ -1,52 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Karafka
|
4
|
-
module Web
|
5
|
-
module Ui
|
6
|
-
module Lib
|
7
|
-
module Paginations
|
8
|
-
module Paginators
|
9
|
-
# Paginator for selecting proper range of partitions for each page
|
10
|
-
# For topics with a lot of partitions we cannot get all the data efficiently, that
|
11
|
-
# is why we limit number of partitions per page and reduce the operations
|
12
|
-
# that way. This allows us to effectively display more while not having to fetch
|
13
|
-
# more partitions then the number of messages per page.
|
14
|
-
# In cases like this we distribute partitions evenly part of partitions on each of
|
15
|
-
# the pages. This may become unreliable for partitions that are not evenly
|
16
|
-
# distributed but this allows us to display data for as many partitions as we want
|
17
|
-
# without overloading the system
|
18
|
-
class Partitions < Base
|
19
|
-
class << self
|
20
|
-
# Computers the partitions slice, materialized page and the limitations status
|
21
|
-
# for a given page
|
22
|
-
# @param partitions_count [Integer] number of partitions for a given topic
|
23
|
-
# @param current_page [Integer] current page
|
24
|
-
# @return [Array<Array<Integer>, Integer, Boolean>] list of partitions that should
|
25
|
-
# be active on a given page, materialized page for them and info if we had to
|
26
|
-
# limit the partitions number on a given page
|
27
|
-
def call(partitions_count, current_page)
|
28
|
-
# How many "chunks" of partitions we will have
|
29
|
-
slices_count = (partitions_count / per_page.to_f).ceil
|
30
|
-
# How many partitions in a single slice should we have
|
31
|
-
in_slice = (partitions_count / slices_count.to_f).ceil
|
32
|
-
# Which "chunked" page do we want to get
|
33
|
-
materialized_page = (current_page / slices_count.to_f).ceil
|
34
|
-
# Which slice is the one we are operating on
|
35
|
-
active_slice_index = (current_page - 1) % slices_count
|
36
|
-
# All available slices so we can pick one that is active
|
37
|
-
partitions_slices = (0...partitions_count).each_slice(in_slice).to_a
|
38
|
-
# Select active partitions only
|
39
|
-
active_partitions = partitions_slices[active_slice_index]
|
40
|
-
# Are we limiting ourselves because of partition count
|
41
|
-
limited = slices_count > 1
|
42
|
-
|
43
|
-
[active_partitions, materialized_page, limited]
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
@@ -1,85 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Karafka
|
4
|
-
module Web
|
5
|
-
module Ui
|
6
|
-
module Lib
|
7
|
-
module Paginations
|
8
|
-
module Paginators
|
9
|
-
# Paginator that allows us to take several lists/sets and iterate over them in a
|
10
|
-
# round-robin fashion.
|
11
|
-
#
|
12
|
-
# It does not have to iterate over all the elements from each set for higher pages
|
13
|
-
# making it much more effective than the naive implementation.
|
14
|
-
class Sets < Base
|
15
|
-
class << self
|
16
|
-
# @param counts [Array<Integer>] sets elements counts
|
17
|
-
# @param current_page [Integer] page number
|
18
|
-
# @return [Hash<Integer, Range>] hash with integer keys indicating the count
|
19
|
-
# location and the range needed to be taken of elements (counting backwards) for
|
20
|
-
# each partition
|
21
|
-
def call(counts, current_page)
|
22
|
-
return {} if current_page < 1
|
23
|
-
|
24
|
-
lists = counts.dup.map.with_index { |el, i| [i, el] }
|
25
|
-
|
26
|
-
curr_item_index = 0
|
27
|
-
curr_list_index = 0
|
28
|
-
items_to_skip_count = per_page * (current_page - 1)
|
29
|
-
|
30
|
-
loop do
|
31
|
-
lists_count = lists.length
|
32
|
-
return {} if lists_count.zero?
|
33
|
-
|
34
|
-
shortest_list_count = lists.map(&:last).min
|
35
|
-
mover = (shortest_list_count - curr_item_index)
|
36
|
-
items_we_are_considering_count = lists_count * mover
|
37
|
-
|
38
|
-
if items_we_are_considering_count >= items_to_skip_count
|
39
|
-
curr_item_index += items_to_skip_count / lists_count
|
40
|
-
curr_list_index = items_to_skip_count % lists_count
|
41
|
-
break
|
42
|
-
else
|
43
|
-
curr_item_index = shortest_list_count
|
44
|
-
lists.delete_if { |x| x.last == shortest_list_count }
|
45
|
-
items_to_skip_count -= items_we_are_considering_count
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
page_items = []
|
50
|
-
largest_list_count = lists.map(&:last).max
|
51
|
-
|
52
|
-
while page_items.length < per_page && curr_item_index < largest_list_count
|
53
|
-
curr_list = lists[curr_list_index]
|
54
|
-
|
55
|
-
if curr_item_index < curr_list.last
|
56
|
-
page_items << [curr_list.first, curr_item_index]
|
57
|
-
end
|
58
|
-
|
59
|
-
curr_list_index += 1
|
60
|
-
if curr_list_index == lists.length
|
61
|
-
curr_list_index = 0
|
62
|
-
curr_item_index += 1
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
hashed = Hash.new { |h, k| h[k] = [] }
|
67
|
-
|
68
|
-
page_items.each do |el|
|
69
|
-
hashed[el.first] << el.last
|
70
|
-
end
|
71
|
-
|
72
|
-
hashed.each do |key, value|
|
73
|
-
hashed[key] = (value.first..value.last)
|
74
|
-
end
|
75
|
-
|
76
|
-
hashed
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
@@ -1,74 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Karafka
|
4
|
-
module Web
|
5
|
-
module Ui
|
6
|
-
# Non info related extra components used in the UI
|
7
|
-
module Lib
|
8
|
-
# Ttl Cache for caching things in-memory
|
9
|
-
# @note It **is** thread-safe
|
10
|
-
class TtlCache
|
11
|
-
include ::Karafka::Core::Helpers::Time
|
12
|
-
|
13
|
-
# @param ttl [Integer] time in ms how long should this cache keep data
|
14
|
-
def initialize(ttl)
|
15
|
-
@ttl = ttl
|
16
|
-
@times = {}
|
17
|
-
@values = {}
|
18
|
-
@mutex = Mutex.new
|
19
|
-
end
|
20
|
-
|
21
|
-
# Reads data from the cache
|
22
|
-
#
|
23
|
-
# @param key [String, Symbol] key for the cache read
|
24
|
-
# @return [Object] anything that was cached
|
25
|
-
def read(key)
|
26
|
-
@mutex.synchronize do
|
27
|
-
evict
|
28
|
-
@values[key]
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
# Writes to the cache
|
33
|
-
#
|
34
|
-
# @param key [String, Symbol] key for the cache
|
35
|
-
# @param value [Object] value we want to cache
|
36
|
-
# @return [Object] value we have written
|
37
|
-
def write(key, value)
|
38
|
-
@mutex.synchronize do
|
39
|
-
@times[key] = monotonic_now + @ttl
|
40
|
-
@values[key] = value
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
# Reads from the cache and if value not present, will run the block and store its result
|
45
|
-
# in the cache
|
46
|
-
#
|
47
|
-
# @param key [String, Symbol] key for the cache read
|
48
|
-
# @return [Object] anything that was cached or yielded
|
49
|
-
def fetch(key)
|
50
|
-
@mutex.synchronize do
|
51
|
-
evict
|
52
|
-
|
53
|
-
return @values[key] if @values.key?(key)
|
54
|
-
|
55
|
-
@values[key] = yield
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
private
|
60
|
-
|
61
|
-
# Removes expired elements from the cache
|
62
|
-
def evict
|
63
|
-
@times.each do |key, time|
|
64
|
-
next if time >= monotonic_now
|
65
|
-
|
66
|
-
@times.delete(key)
|
67
|
-
@values.delete(key)
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
@@ -1,59 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Karafka
|
4
|
-
module Web
|
5
|
-
module Ui
|
6
|
-
module Models
|
7
|
-
# Wraps around the `Karafka::Admin#cluster_info` with caching and some additional aliases
|
8
|
-
# so we can reference relevant information easily
|
9
|
-
class ClusterInfo
|
10
|
-
class << self
|
11
|
-
# Gets us all the cluster metadata info
|
12
|
-
#
|
13
|
-
# @param cached [Boolean] should we use cached data (true by default)
|
14
|
-
# @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, Karafka::Admin.cluster_info)
|
22
|
-
end
|
23
|
-
|
24
|
-
cluster_info
|
25
|
-
end
|
26
|
-
|
27
|
-
# Returns us all the info about available topics from the cluster
|
28
|
-
#
|
29
|
-
# @param cached [Boolean] should we use cached data (true by default)
|
30
|
-
# @return [Array<Ui::Models::Topic>] topics details
|
31
|
-
def topics(cached: true)
|
32
|
-
fetch(cached: cached)
|
33
|
-
.topics
|
34
|
-
.map { |topic| Topic.new(topic) }
|
35
|
-
end
|
36
|
-
|
37
|
-
# Fetches us details about particular topic
|
38
|
-
#
|
39
|
-
# @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
|
-
# @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) }
|
46
|
-
end
|
47
|
-
|
48
|
-
# @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
|
-
# @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
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
<table class="processes bg-white table table-hover table-bordered table-striped mb-0 align-middle">
|
2
|
-
<thead>
|
3
|
-
<tr class="align-middle">
|
4
|
-
<th>Origin</th>
|
5
|
-
<th>Process name</th>
|
6
|
-
<th>Error</th>
|
7
|
-
<th>Occurred at</th>
|
8
|
-
<th></th>
|
9
|
-
</tr>
|
10
|
-
</thead>
|
11
|
-
|
12
|
-
<tbody>
|
13
|
-
<%==
|
14
|
-
each_partial(
|
15
|
-
@error_messages,
|
16
|
-
'errors/error',
|
17
|
-
local: :error_msg
|
18
|
-
)
|
19
|
-
%>
|
20
|
-
</tbody>
|
21
|
-
</table>
|
@@ -1,31 +0,0 @@
|
|
1
|
-
<div class="container mb-4">
|
2
|
-
<div class="row">
|
3
|
-
<div class="col">
|
4
|
-
<h3>
|
5
|
-
Errors
|
6
|
-
</h3>
|
7
|
-
</div>
|
8
|
-
|
9
|
-
<div class="col">
|
10
|
-
<div class="col-auto text-end">
|
11
|
-
<label class="col-form-label">Partition</label>
|
12
|
-
</div>
|
13
|
-
</div>
|
14
|
-
|
15
|
-
<div class="col pt-1 mb-0 pb-0">
|
16
|
-
<div class="col-auto">
|
17
|
-
<select class="form-select form-select-sm mb-0 form-control" id="current-partition">
|
18
|
-
<%==
|
19
|
-
each_partial(
|
20
|
-
[nil] + @partitions_count.times.to_a,
|
21
|
-
'errors/partition_option',
|
22
|
-
local: :partition
|
23
|
-
)
|
24
|
-
%>
|
25
|
-
</select>
|
26
|
-
</div>
|
27
|
-
</div>
|
28
|
-
</div>
|
29
|
-
|
30
|
-
<hr>
|
31
|
-
</div>
|
@@ -1,17 +0,0 @@
|
|
1
|
-
<%== partial('errors/title_with_select') %>
|
2
|
-
|
3
|
-
<div class="container">
|
4
|
-
<div class="row mb-5">
|
5
|
-
<div class="col-sm-12">
|
6
|
-
<%== partial('explorer/partition/watermark_offsets') %>
|
7
|
-
|
8
|
-
<% if @watermark_offsets.empty? %>
|
9
|
-
<%== partial 'errors/no_errors' %>
|
10
|
-
<% elsif @watermark_offsets.cleaned? %>
|
11
|
-
<%== partial 'errors/cleaned' %>
|
12
|
-
<% else %>
|
13
|
-
<%== partial 'errors/table' %>
|
14
|
-
<% end %>
|
15
|
-
</div>
|
16
|
-
</div>
|
17
|
-
</div>
|
@@ -1,11 +0,0 @@
|
|
1
|
-
<p class="text-end mb-4">
|
2
|
-
Partitions:
|
3
|
-
<span class="badge bg-secondary mt-1 mb-1">
|
4
|
-
total: <%= @partitions_count %>
|
5
|
-
</span>
|
6
|
-
|
7
|
-
<span class="badge bg-secondary mt-1 mb-1">
|
8
|
-
visible:
|
9
|
-
<%= [@active_partitions.first, @active_partitions.last].uniq.join(' to ') %>
|
10
|
-
</span>
|
11
|
-
</p>
|
@@ -1,49 +0,0 @@
|
|
1
|
-
<div class="container mb-4">
|
2
|
-
<div class="row">
|
3
|
-
<div class="col">
|
4
|
-
<h3>
|
5
|
-
<%= @topic_id %>
|
6
|
-
</h3>
|
7
|
-
</div>
|
8
|
-
|
9
|
-
<div class="col">
|
10
|
-
<div class="col-auto text-end">
|
11
|
-
<label class="col-form-label">Partition</label>
|
12
|
-
</div>
|
13
|
-
</div>
|
14
|
-
|
15
|
-
<div class="col pt-1 mb-0 pb-0">
|
16
|
-
<div class="col-auto">
|
17
|
-
<select class="form-select form-select-sm mb-0 form-control" id="current-partition">
|
18
|
-
<%==
|
19
|
-
each_partial(
|
20
|
-
[nil] + @partitions_count.times.to_a,
|
21
|
-
'explorer/partition_option',
|
22
|
-
local: :partition
|
23
|
-
)
|
24
|
-
%>
|
25
|
-
</select>
|
26
|
-
</div>
|
27
|
-
</div>
|
28
|
-
</div>
|
29
|
-
|
30
|
-
<hr>
|
31
|
-
</div>
|
32
|
-
|
33
|
-
<div class="container">
|
34
|
-
<div class="row mb-5">
|
35
|
-
<div class="col-sm-12">
|
36
|
-
<%== partial('explorer/topic/partitions') %>
|
37
|
-
|
38
|
-
<% if @limited %>
|
39
|
-
<%== partial('explorer/topic/limited') %>
|
40
|
-
<% end %>
|
41
|
-
|
42
|
-
<% if @messages.empty? && params.current_page == 1 %>
|
43
|
-
<%== partial 'explorer/topic/empty' %>
|
44
|
-
<% else %>
|
45
|
-
<%== partial('explorer/partition/messages') %>
|
46
|
-
<% end %>
|
47
|
-
</div>
|
48
|
-
</div>
|
49
|
-
</div>
|