karafka-web 0.6.3 → 0.7.0
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/.github/workflows/ci.yml +13 -4
- data/CHANGELOG.md +119 -5
- data/Gemfile +1 -0
- data/Gemfile.lock +27 -24
- data/README.md +2 -0
- data/bin/rspecs +6 -0
- data/certs/cert_chain.pem +21 -21
- data/docker-compose.yml +22 -0
- data/karafka-web.gemspec +3 -3
- data/lib/karafka/web/app.rb +6 -2
- data/lib/karafka/web/cli.rb +51 -47
- data/lib/karafka/web/config.rb +33 -9
- data/lib/karafka/web/contracts/base.rb +32 -0
- data/lib/karafka/web/contracts/config.rb +63 -0
- data/lib/karafka/web/deserializer.rb +10 -1
- data/lib/karafka/web/errors.rb +29 -7
- data/lib/karafka/web/installer.rb +58 -148
- data/lib/karafka/web/management/base.rb +34 -0
- data/lib/karafka/web/management/clean_boot_file.rb +31 -0
- data/lib/karafka/web/management/create_initial_states.rb +101 -0
- data/lib/karafka/web/management/create_topics.rb +127 -0
- data/lib/karafka/web/management/delete_topics.rb +28 -0
- data/lib/karafka/web/management/enable.rb +82 -0
- data/lib/karafka/web/management/extend_boot_file.rb +37 -0
- data/lib/karafka/web/processing/consumer.rb +73 -17
- data/lib/karafka/web/processing/consumers/aggregators/base.rb +56 -0
- data/lib/karafka/web/processing/consumers/aggregators/metrics.rb +154 -0
- data/lib/karafka/web/processing/consumers/aggregators/state.rb +180 -0
- data/lib/karafka/web/processing/consumers/contracts/aggregated_stats.rb +32 -0
- data/lib/karafka/web/processing/consumers/contracts/metrics.rb +53 -0
- data/lib/karafka/web/processing/consumers/contracts/process.rb +19 -0
- data/lib/karafka/web/processing/consumers/contracts/state.rb +49 -0
- data/lib/karafka/web/processing/consumers/contracts/topic_stats.rb +21 -0
- data/lib/karafka/web/processing/consumers/metrics.rb +29 -0
- data/lib/karafka/web/processing/consumers/schema_manager.rb +56 -0
- data/lib/karafka/web/processing/consumers/state.rb +6 -9
- data/lib/karafka/web/processing/time_series_tracker.rb +130 -0
- data/lib/karafka/web/tracking/consumers/contracts/consumer_group.rb +2 -2
- data/lib/karafka/web/tracking/consumers/contracts/job.rb +2 -1
- data/lib/karafka/web/tracking/consumers/contracts/partition.rb +14 -1
- data/lib/karafka/web/tracking/consumers/contracts/report.rb +10 -8
- data/lib/karafka/web/tracking/consumers/contracts/subscription_group.rb +2 -2
- data/lib/karafka/web/tracking/consumers/contracts/topic.rb +2 -2
- data/lib/karafka/web/tracking/consumers/listeners/processing.rb +6 -2
- data/lib/karafka/web/tracking/consumers/listeners/statistics.rb +15 -1
- data/lib/karafka/web/tracking/consumers/reporter.rb +14 -6
- data/lib/karafka/web/tracking/consumers/sampler.rb +80 -39
- data/lib/karafka/web/tracking/contracts/error.rb +2 -1
- data/lib/karafka/web/ui/app.rb +20 -10
- data/lib/karafka/web/ui/base.rb +56 -6
- data/lib/karafka/web/ui/controllers/base.rb +28 -0
- data/lib/karafka/web/ui/controllers/become_pro.rb +1 -1
- data/lib/karafka/web/ui/controllers/cluster.rb +12 -6
- data/lib/karafka/web/ui/controllers/consumers.rb +4 -2
- data/lib/karafka/web/ui/controllers/dashboard.rb +32 -0
- data/lib/karafka/web/ui/controllers/errors.rb +19 -6
- data/lib/karafka/web/ui/controllers/jobs.rb +4 -2
- data/lib/karafka/web/ui/controllers/requests/params.rb +28 -0
- data/lib/karafka/web/ui/controllers/responses/redirect.rb +29 -0
- data/lib/karafka/web/ui/helpers/application_helper.rb +57 -14
- data/lib/karafka/web/ui/helpers/paths_helper.rb +48 -0
- data/lib/karafka/web/ui/lib/hash_proxy.rb +18 -6
- data/lib/karafka/web/ui/lib/paginations/base.rb +61 -0
- data/lib/karafka/web/ui/lib/paginations/offset_based.rb +96 -0
- data/lib/karafka/web/ui/lib/paginations/page_based.rb +70 -0
- data/lib/karafka/web/ui/lib/paginations/paginators/arrays.rb +33 -0
- data/lib/karafka/web/ui/lib/paginations/paginators/base.rb +23 -0
- data/lib/karafka/web/ui/lib/paginations/paginators/partitions.rb +52 -0
- data/lib/karafka/web/ui/lib/paginations/paginators/sets.rb +85 -0
- data/lib/karafka/web/ui/lib/paginations/watermark_offsets_based.rb +75 -0
- data/lib/karafka/web/ui/lib/ttl_cache.rb +82 -0
- data/lib/karafka/web/ui/models/cluster_info.rb +59 -0
- data/lib/karafka/web/ui/models/consumers_metrics.rb +46 -0
- data/lib/karafka/web/ui/models/{state.rb → consumers_state.rb} +6 -2
- data/lib/karafka/web/ui/models/health.rb +37 -7
- data/lib/karafka/web/ui/models/message.rb +123 -39
- data/lib/karafka/web/ui/models/metrics/aggregated.rb +196 -0
- data/lib/karafka/web/ui/models/metrics/charts/aggregated.rb +50 -0
- data/lib/karafka/web/ui/models/metrics/charts/topics.rb +109 -0
- data/lib/karafka/web/ui/models/metrics/topics.rb +101 -0
- data/lib/karafka/web/ui/models/partition.rb +27 -0
- data/lib/karafka/web/ui/models/process.rb +12 -1
- data/lib/karafka/web/ui/models/status.rb +110 -22
- data/lib/karafka/web/ui/models/visibility_filter.rb +33 -0
- data/lib/karafka/web/ui/pro/app.rb +87 -19
- data/lib/karafka/web/ui/pro/controllers/cluster.rb +11 -0
- data/lib/karafka/web/ui/pro/controllers/consumers.rb +13 -7
- data/lib/karafka/web/ui/pro/controllers/dashboard.rb +54 -0
- data/lib/karafka/web/ui/pro/controllers/dlq.rb +1 -2
- data/lib/karafka/web/ui/pro/controllers/errors.rb +46 -10
- data/lib/karafka/web/ui/pro/controllers/explorer.rb +145 -15
- data/lib/karafka/web/ui/pro/controllers/health.rb +10 -2
- data/lib/karafka/web/ui/pro/controllers/messages.rb +62 -0
- data/lib/karafka/web/ui/pro/controllers/routing.rb +44 -0
- data/lib/karafka/web/ui/pro/views/consumers/_breadcrumbs.erb +7 -1
- data/lib/karafka/web/ui/pro/views/consumers/_consumer.erb +1 -1
- data/lib/karafka/web/ui/pro/views/consumers/_counters.erb +7 -5
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_job.erb +3 -3
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_metrics.erb +5 -4
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_partition.erb +13 -4
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_subscription_group.erb +3 -2
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_tabs.erb +7 -0
- data/lib/karafka/web/ui/pro/views/consumers/details.erb +21 -0
- data/lib/karafka/web/ui/pro/views/consumers/index.erb +4 -2
- data/lib/karafka/web/ui/pro/views/dashboard/_ranges_selector.erb +39 -0
- data/lib/karafka/web/ui/pro/views/dashboard/index.erb +82 -0
- data/lib/karafka/web/ui/pro/views/dlq/_topic.erb +1 -1
- data/lib/karafka/web/ui/pro/views/errors/_breadcrumbs.erb +8 -6
- data/lib/karafka/web/ui/pro/views/errors/_error.erb +2 -2
- data/lib/karafka/web/ui/pro/views/errors/_partition_option.erb +1 -1
- data/lib/karafka/web/ui/pro/views/errors/_table.erb +21 -0
- data/lib/karafka/web/ui/pro/views/errors/_title_with_select.erb +31 -0
- data/lib/karafka/web/ui/pro/views/errors/index.erb +9 -56
- data/lib/karafka/web/ui/pro/views/errors/partition.erb +17 -0
- data/lib/karafka/web/ui/pro/views/errors/show.erb +1 -1
- data/lib/karafka/web/ui/pro/views/explorer/_breadcrumbs.erb +6 -4
- data/lib/karafka/web/ui/pro/views/explorer/_filtered.erb +16 -0
- data/lib/karafka/web/ui/pro/views/explorer/_message.erb +14 -4
- data/lib/karafka/web/ui/pro/views/explorer/_no_topics.erb +7 -0
- data/lib/karafka/web/ui/pro/views/explorer/_partition_option.erb +3 -3
- data/lib/karafka/web/ui/pro/views/explorer/_topic.erb +1 -1
- data/lib/karafka/web/ui/pro/views/explorer/index.erb +12 -8
- data/lib/karafka/web/ui/pro/views/explorer/messages/_headers.erb +15 -0
- data/lib/karafka/web/ui/pro/views/explorer/messages/_key.erb +12 -0
- data/lib/karafka/web/ui/pro/views/explorer/partition/_details.erb +35 -0
- data/lib/karafka/web/ui/pro/views/explorer/partition/_messages.erb +1 -0
- data/lib/karafka/web/ui/pro/views/explorer/partition.erb +6 -4
- data/lib/karafka/web/ui/pro/views/explorer/show.erb +48 -5
- data/lib/karafka/web/ui/pro/views/explorer/topic/_details.erb +23 -0
- data/lib/karafka/web/ui/pro/views/explorer/topic/_empty.erb +3 -0
- data/lib/karafka/web/ui/pro/views/explorer/topic/_limited.erb +4 -0
- data/lib/karafka/web/ui/pro/views/explorer/topic.erb +51 -0
- data/lib/karafka/web/ui/pro/views/health/_breadcrumbs.erb +16 -0
- data/lib/karafka/web/ui/pro/views/health/_no_data.erb +9 -0
- data/lib/karafka/web/ui/pro/views/health/_partition.erb +17 -15
- data/lib/karafka/web/ui/pro/views/health/_partition_offset.erb +40 -0
- data/lib/karafka/web/ui/pro/views/health/_tabs.erb +27 -0
- data/lib/karafka/web/ui/pro/views/health/offsets.erb +71 -0
- data/lib/karafka/web/ui/pro/views/health/overview.erb +68 -0
- data/lib/karafka/web/ui/pro/views/jobs/_job.erb +6 -3
- data/lib/karafka/web/ui/pro/views/jobs/index.erb +4 -1
- data/lib/karafka/web/ui/pro/views/routing/_consumer_group.erb +37 -0
- data/lib/karafka/web/ui/pro/views/routing/_detail.erb +25 -0
- data/lib/karafka/web/ui/pro/views/routing/_topic.erb +23 -0
- data/lib/karafka/web/ui/pro/views/routing/index.erb +10 -0
- data/lib/karafka/web/ui/pro/views/routing/show.erb +26 -0
- data/lib/karafka/web/ui/pro/views/shared/_navigation.erb +7 -10
- data/lib/karafka/web/ui/public/images/logo-gray.svg +28 -0
- data/lib/karafka/web/ui/public/javascripts/application.js +30 -0
- data/lib/karafka/web/ui/public/javascripts/chart.min.js +14 -0
- data/lib/karafka/web/ui/public/javascripts/charts.js +330 -0
- data/lib/karafka/web/ui/public/javascripts/datepicker.js +6 -0
- data/lib/karafka/web/ui/public/javascripts/live_poll.js +39 -12
- data/lib/karafka/web/ui/public/javascripts/offset_datetime.js +74 -0
- data/lib/karafka/web/ui/public/javascripts/tabs.js +59 -0
- data/lib/karafka/web/ui/public/stylesheets/application.css +11 -0
- data/lib/karafka/web/ui/public/stylesheets/datepicker.min.css +12 -0
- data/lib/karafka/web/ui/views/cluster/_no_partitions.erb +3 -0
- data/lib/karafka/web/ui/views/cluster/_partition.erb +20 -22
- data/lib/karafka/web/ui/views/cluster/index.erb +6 -1
- data/lib/karafka/web/ui/views/consumers/_consumer.erb +1 -1
- data/lib/karafka/web/ui/views/consumers/_counters.erb +6 -4
- data/lib/karafka/web/ui/views/consumers/_summary.erb +3 -3
- data/lib/karafka/web/ui/views/consumers/index.erb +3 -1
- data/lib/karafka/web/ui/views/dashboard/_feature_pro.erb +3 -0
- data/lib/karafka/web/ui/views/dashboard/_not_enough_data.erb +15 -0
- data/lib/karafka/web/ui/views/dashboard/_ranges_selector.erb +23 -0
- data/lib/karafka/web/ui/views/dashboard/index.erb +95 -0
- data/lib/karafka/web/ui/views/errors/_detail.erb +12 -0
- data/lib/karafka/web/ui/views/errors/_error.erb +2 -2
- data/lib/karafka/web/ui/views/errors/show.erb +1 -1
- data/lib/karafka/web/ui/views/jobs/index.erb +3 -1
- data/lib/karafka/web/ui/views/layout.erb +10 -3
- data/lib/karafka/web/ui/views/routing/_consumer_group.erb +8 -6
- data/lib/karafka/web/ui/views/routing/_detail.erb +2 -2
- data/lib/karafka/web/ui/views/routing/_topic.erb +1 -1
- data/lib/karafka/web/ui/views/routing/show.erb +1 -1
- data/lib/karafka/web/ui/views/shared/_brand.erb +2 -2
- data/lib/karafka/web/ui/views/shared/_chart.erb +14 -0
- data/lib/karafka/web/ui/views/shared/_content.erb +2 -2
- data/lib/karafka/web/ui/views/shared/_feature_pro.erb +1 -1
- data/lib/karafka/web/ui/views/shared/_flashes.erb +9 -0
- data/lib/karafka/web/ui/views/shared/_footer.erb +22 -0
- data/lib/karafka/web/ui/views/shared/_header.erb +15 -9
- data/lib/karafka/web/ui/views/shared/_live_poll.erb +7 -0
- data/lib/karafka/web/ui/views/shared/_navigation.erb +5 -8
- data/lib/karafka/web/ui/views/shared/_no_paginated_data.erb +9 -0
- data/lib/karafka/web/ui/views/shared/_pagination.erb +17 -13
- data/lib/karafka/web/ui/views/shared/_tab_nav.erb +7 -0
- data/lib/karafka/web/ui/views/shared/exceptions/not_found.erb +34 -32
- data/lib/karafka/web/ui/views/shared/exceptions/pro_only.erb +45 -43
- data/lib/karafka/web/ui/views/status/failures/_consumers_reports_schema_state.erb +15 -0
- data/lib/karafka/web/ui/views/status/failures/_enabled.erb +8 -0
- data/lib/karafka/web/ui/views/status/failures/_initial_consumers_metrics.erb +11 -0
- data/lib/karafka/web/ui/views/status/failures/{_initial_state.erb → _initial_consumers_state.erb} +3 -3
- data/lib/karafka/web/ui/views/status/failures/_partitions.erb +14 -6
- data/lib/karafka/web/ui/views/status/info/_components.erb +21 -1
- data/lib/karafka/web/ui/views/status/show.erb +62 -5
- data/lib/karafka/web/ui/views/status/successes/_enabled.erb +1 -0
- data/lib/karafka/web/ui/views/status/warnings/_replication.erb +19 -0
- data/lib/karafka/web/version.rb +1 -1
- data/lib/karafka/web.rb +11 -0
- data.tar.gz.sig +0 -0
- metadata +124 -39
- metadata.gz.sig +0 -0
- data/lib/karafka/web/processing/consumers/aggregator.rb +0 -130
- data/lib/karafka/web/tracking/contracts/base.rb +0 -34
- data/lib/karafka/web/ui/lib/paginate_array.rb +0 -38
- data/lib/karafka/web/ui/pro/views/explorer/_encryption_enabled.erb +0 -18
- data/lib/karafka/web/ui/pro/views/explorer/partition/_watermark_offsets.erb +0 -10
- data/lib/karafka/web/ui/pro/views/health/index.erb +0 -60
- /data/lib/karafka/web/ui/pro/views/explorer/{_detail.erb → messages/_detail.erb} +0 -0
@@ -8,7 +8,8 @@ module Karafka
|
|
8
8
|
class Cluster < Base
|
9
9
|
# List cluster info data
|
10
10
|
def index
|
11
|
-
|
11
|
+
# Make sure, that for the cluster view we always get the most recent cluster state
|
12
|
+
@cluster_info = Models::ClusterInfo.fetch(cached: false)
|
12
13
|
|
13
14
|
partitions_total = []
|
14
15
|
|
@@ -18,11 +19,13 @@ module Karafka
|
|
18
19
|
end
|
19
20
|
end
|
20
21
|
|
21
|
-
@partitions,
|
22
|
+
@partitions, last_page = Ui::Lib::Paginations::Paginators::Arrays.call(
|
22
23
|
partitions_total,
|
23
24
|
@params.current_page
|
24
25
|
)
|
25
26
|
|
27
|
+
paginate(@params.current_page, !last_page)
|
28
|
+
|
26
29
|
respond
|
27
30
|
end
|
28
31
|
|
@@ -32,10 +35,13 @@ module Karafka
|
|
32
35
|
# @return [Array<Hash>] array with topics to be displayed sorted in an alphabetical
|
33
36
|
# order
|
34
37
|
def displayable_topics(cluster_info)
|
35
|
-
cluster_info
|
36
|
-
|
37
|
-
|
38
|
-
|
38
|
+
all = cluster_info
|
39
|
+
.topics
|
40
|
+
.sort_by { |topic| topic[:topic_name] }
|
41
|
+
|
42
|
+
return all if ::Karafka::Web.config.ui.show_internal_topics
|
43
|
+
|
44
|
+
all.reject { |topic| topic[:topic_name].start_with?('__') }
|
39
45
|
end
|
40
46
|
end
|
41
47
|
end
|
@@ -9,13 +9,15 @@ module Karafka
|
|
9
9
|
# List page with consumers
|
10
10
|
# @note For now we load all and paginate over the squashed data.
|
11
11
|
def index
|
12
|
-
@current_state = Models::
|
12
|
+
@current_state = Models::ConsumersState.current!
|
13
13
|
@counters = Models::Counters.new(@current_state)
|
14
|
-
@processes,
|
14
|
+
@processes, last_page = Ui::Lib::Paginations::Paginators::Arrays.call(
|
15
15
|
Models::Processes.active(@current_state),
|
16
16
|
@params.current_page
|
17
17
|
)
|
18
18
|
|
19
|
+
paginate(@params.current_page, !last_page)
|
20
|
+
|
19
21
|
respond
|
20
22
|
end
|
21
23
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Web
|
5
|
+
module Ui
|
6
|
+
module Controllers
|
7
|
+
# Main Karafka Pro Web-Ui dashboard controller
|
8
|
+
class Dashboard < Ui::Controllers::Base
|
9
|
+
# View with statistics dashboard details
|
10
|
+
def index
|
11
|
+
@current_state = Models::ConsumersState.current!
|
12
|
+
@counters = Models::Counters.new(@current_state)
|
13
|
+
|
14
|
+
current_metrics = Models::ConsumersMetrics.current!
|
15
|
+
|
16
|
+
# Build the charts data using the aggregated metrics
|
17
|
+
@aggregated = Models::Metrics::Aggregated.new(
|
18
|
+
current_metrics.to_h.fetch(:aggregated)
|
19
|
+
)
|
20
|
+
|
21
|
+
# Load only historicals for the selected range
|
22
|
+
@aggregated_charts = Models::Metrics::Charts::Aggregated.new(
|
23
|
+
@aggregated, :seconds
|
24
|
+
)
|
25
|
+
|
26
|
+
respond
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -10,13 +10,15 @@ module Karafka
|
|
10
10
|
class Errors < Base
|
11
11
|
# Lists first page of the errors
|
12
12
|
def index
|
13
|
-
@previous_page, @error_messages, @next_page, = Models::Message.page(
|
14
|
-
errors_topic,
|
15
|
-
0,
|
16
|
-
@params.current_page
|
17
|
-
)
|
18
|
-
|
19
13
|
@watermark_offsets = Ui::Models::WatermarkOffsets.find(errors_topic, 0)
|
14
|
+
previous_offset, @error_messages, next_offset, = current_page_data
|
15
|
+
|
16
|
+
paginate(
|
17
|
+
previous_offset,
|
18
|
+
@params.current_offset,
|
19
|
+
next_offset,
|
20
|
+
@error_messages.map(&:offset)
|
21
|
+
)
|
20
22
|
|
21
23
|
respond
|
22
24
|
end
|
@@ -34,6 +36,17 @@ module Karafka
|
|
34
36
|
|
35
37
|
private
|
36
38
|
|
39
|
+
# @return [Array] Array with requested messages as well as pagination details and other
|
40
|
+
# obtained metadata
|
41
|
+
def current_page_data
|
42
|
+
Models::Message.offset_page(
|
43
|
+
errors_topic,
|
44
|
+
0,
|
45
|
+
@params.current_offset,
|
46
|
+
@watermark_offsets
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
37
50
|
# @return [String] errors topic
|
38
51
|
def errors_topic
|
39
52
|
::Karafka::Web.config.topics.errors
|
@@ -8,7 +8,7 @@ module Karafka
|
|
8
8
|
class Jobs < Base
|
9
9
|
# Lists jobs
|
10
10
|
def index
|
11
|
-
current_state = Models::
|
11
|
+
current_state = Models::ConsumersState.current!
|
12
12
|
processes = Models::Processes.active(current_state)
|
13
13
|
|
14
14
|
# Aggregate jobs and inject the process info into them for better reporting
|
@@ -19,11 +19,13 @@ module Karafka
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
@jobs,
|
22
|
+
@jobs, last_page = Ui::Lib::Paginations::Paginators::Arrays.call(
|
23
23
|
jobs_total,
|
24
24
|
@params.current_page
|
25
25
|
)
|
26
26
|
|
27
|
+
paginate(@params.current_page, !last_page)
|
28
|
+
|
27
29
|
respond
|
28
30
|
end
|
29
31
|
end
|
@@ -8,6 +8,17 @@ module Karafka
|
|
8
8
|
module Requests
|
9
9
|
# Internal representation of params with sane sanitization
|
10
10
|
class Params
|
11
|
+
# What ranges we support for charts
|
12
|
+
# Anything else will be rejected
|
13
|
+
ALLOWED_RANGES = %w[
|
14
|
+
seconds
|
15
|
+
minutes
|
16
|
+
hours
|
17
|
+
days
|
18
|
+
].freeze
|
19
|
+
|
20
|
+
private_constant :ALLOWED_RANGES
|
21
|
+
|
11
22
|
# @param request_params [Hash] raw hash with params
|
12
23
|
def initialize(request_params)
|
13
24
|
@request_params = request_params
|
@@ -22,6 +33,23 @@ module Karafka
|
|
22
33
|
page.positive? ? page : 1
|
23
34
|
end
|
24
35
|
end
|
36
|
+
|
37
|
+
# @return [String] Range type for charts we want to fetch
|
38
|
+
def current_range
|
39
|
+
candidate = @request_params.fetch('range', 'seconds')
|
40
|
+
candidate = ALLOWED_RANGES.first unless ALLOWED_RANGES.include?(candidate)
|
41
|
+
candidate.to_sym
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [Integer] offset from which we want to start. `-1` indicates, that we want
|
45
|
+
# to show the first page discovered based on the high watermark offset. If no offset
|
46
|
+
# is provided, we go with the high offset first page approach
|
47
|
+
def current_offset
|
48
|
+
@current_offset ||= begin
|
49
|
+
offset = @request_params.fetch('offset', -1).to_i
|
50
|
+
offset < -1 ? -1 : offset
|
51
|
+
end
|
52
|
+
end
|
25
53
|
end
|
26
54
|
end
|
27
55
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Web
|
5
|
+
module Ui
|
6
|
+
module Controllers
|
7
|
+
module Responses
|
8
|
+
# Representation of a redirect response with optional flash messages
|
9
|
+
class Redirect
|
10
|
+
attr_reader :path, :flashes
|
11
|
+
|
12
|
+
# @param path [String, Symbol] relative (without root path) path where we want to be
|
13
|
+
# redirected or `:back` to use referer back
|
14
|
+
# @param flashes [Hash] hash where key is the flash type and value is the message
|
15
|
+
def initialize(path = :back, flashes = {})
|
16
|
+
@path = path
|
17
|
+
@flashes = flashes
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Boolean] are we going back via referer and not explicit path
|
21
|
+
def back?
|
22
|
+
@path == :back
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -8,18 +8,6 @@ module Karafka
|
|
8
8
|
module Helpers
|
9
9
|
# Main application helper
|
10
10
|
module ApplicationHelper
|
11
|
-
# Generates a full path with the root path out of the provided arguments
|
12
|
-
#
|
13
|
-
# @param args [Array<String, Numeric>] arguments that will make the path
|
14
|
-
# @return [String] path from the root
|
15
|
-
#
|
16
|
-
# @note This needs to be done that way with the `#root_path` because the web UI can be
|
17
|
-
# mounted in a sub-path and we need to make sure our all paths are relative to "our"
|
18
|
-
# root, not the root of the app in which it was mounted.
|
19
|
-
def root_path(*args)
|
20
|
-
"#{env.fetch('SCRIPT_NAME')}/#{args.join('/')}"
|
21
|
-
end
|
22
|
-
|
23
11
|
# Adds active class to the current location in the nav if needed
|
24
12
|
# @param location [Hash]
|
25
13
|
def nav_class(location)
|
@@ -41,7 +29,7 @@ module Karafka
|
|
41
29
|
|
42
30
|
# Renders per scope breadcrumbs
|
43
31
|
def render_breadcrumbs
|
44
|
-
scope = request.path.
|
32
|
+
scope = request.path.delete_prefix(root_path).split('/')[0]
|
45
33
|
|
46
34
|
render "#{scope}/_breadcrumbs"
|
47
35
|
end
|
@@ -137,19 +125,53 @@ module Karafka
|
|
137
125
|
%(<span title="#{stamp}">#{time}</span>)
|
138
126
|
end
|
139
127
|
|
128
|
+
# @param lag [Integer] lag
|
129
|
+
# @return [String] lag if correct or `N/A` with labeled explanation
|
130
|
+
# @see #offset_with_label
|
131
|
+
def lag_with_label(lag)
|
132
|
+
if lag.negative?
|
133
|
+
title = 'Not available until first offset commit'
|
134
|
+
%(<span class="badge bg-secondary" title="#{title}">N/A</span>)
|
135
|
+
else
|
136
|
+
lag.to_s
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# @param topic_name [String] name of the topic for explorer path
|
141
|
+
# @param partition_id [Integer] partition for the explorer path
|
140
142
|
# @param offset [Integer] offset
|
143
|
+
# @param explore [Boolean] should we generate (when allowed) a link to message explorer
|
141
144
|
# @return [String] offset if correct or `N/A` with labeled explanation for offsets
|
142
145
|
# that are less than 0. Offset with less than 0 indicates, that the offset was not
|
143
146
|
# yet committed and there is no value we know of
|
144
|
-
def offset_with_label(offset)
|
147
|
+
def offset_with_label(topic_name, partition_id, offset, explore: false)
|
145
148
|
if offset.negative?
|
146
149
|
title = 'Not available until first offset commit'
|
147
150
|
%(<span class="badge bg-secondary" title="#{title}">N/A</span>)
|
151
|
+
elsif explore
|
152
|
+
path = explorer_path(topic_name, partition_id, offset)
|
153
|
+
%(<a href="#{path}">#{offset}</a>)
|
148
154
|
else
|
149
155
|
offset.to_s
|
150
156
|
end
|
151
157
|
end
|
152
158
|
|
159
|
+
# @param details [::Karafka::Web::Ui::Models::Partition] partition information with
|
160
|
+
# lso risk state info
|
161
|
+
# @return [String] background classes for row marking
|
162
|
+
def lso_risk_state_bg(details)
|
163
|
+
case details.lso_risk_state
|
164
|
+
when :active
|
165
|
+
''
|
166
|
+
when :at_risk
|
167
|
+
'bg-warning bg-opacity-25'
|
168
|
+
when :stopped
|
169
|
+
'bg-danger bg-opacity-25'
|
170
|
+
else
|
171
|
+
raise ::Karafka::Errors::UnsupportedCaseError
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
153
175
|
# Returns the view title html code
|
154
176
|
#
|
155
177
|
# @param title [String] page title
|
@@ -168,6 +190,27 @@ module Karafka
|
|
168
190
|
</div>
|
169
191
|
HTML
|
170
192
|
end
|
193
|
+
|
194
|
+
# @param hash [Hash] we want to flatten
|
195
|
+
# @param parent_key [String] key for recursion
|
196
|
+
# @param result [Hash] result for recursion
|
197
|
+
# @return [Hash]
|
198
|
+
def flat_hash(hash, parent_key = nil, result = {})
|
199
|
+
hash.each do |key, value|
|
200
|
+
current_key = parent_key ? "#{parent_key}.#{key}" : key.to_s
|
201
|
+
if value.is_a?(Hash)
|
202
|
+
flat_hash(value, current_key, result)
|
203
|
+
elsif value.is_a?(Array)
|
204
|
+
value.each_with_index do |item, index|
|
205
|
+
flat_hash({ index => item }, current_key, result)
|
206
|
+
end
|
207
|
+
else
|
208
|
+
result[current_key] = value
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
result
|
213
|
+
end
|
171
214
|
end
|
172
215
|
end
|
173
216
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Web
|
5
|
+
module Ui
|
6
|
+
module Helpers
|
7
|
+
# Helper for web ui paths builders
|
8
|
+
module PathsHelper
|
9
|
+
# Generates a full path with the root path out of the provided arguments
|
10
|
+
#
|
11
|
+
# @param args [Array<String, Numeric>] arguments that will make the path
|
12
|
+
# @return [String] path from the root
|
13
|
+
#
|
14
|
+
# @note This needs to be done that way with the `#root_path` because the web UI can be
|
15
|
+
# mounted in a sub-path and we need to make sure our all paths are relative to "our"
|
16
|
+
# root, not the root of the app in which it was mounted.
|
17
|
+
def root_path(*args)
|
18
|
+
"#{env.fetch('SCRIPT_NAME')}/#{args.join('/')}"
|
19
|
+
end
|
20
|
+
|
21
|
+
# Generates a full path to any asset with our web-ui version. We ship all assets with
|
22
|
+
# the version in the url to prevent those assets from being used after update. After
|
23
|
+
# each web-ui update, assets are going to be re-fetched as the url will change
|
24
|
+
#
|
25
|
+
# @param local_path [String] local path to the asset
|
26
|
+
# @return [String] full path to the asst including correct root path
|
27
|
+
def asset_path(local_path)
|
28
|
+
root_path("assets/#{Karafka::Web::VERSION}/#{local_path}")
|
29
|
+
end
|
30
|
+
|
31
|
+
# Helps build explorer paths. We often link offsets to proper messages, etc so this
|
32
|
+
# allows us to short-track this
|
33
|
+
# @param topic_name [String, nil] name of the topic where we want to go within the
|
34
|
+
# explorer or nil if we want to just go to the explorer root
|
35
|
+
# @param partition_id [Integer, nil] partition we want to display in the explorer or nil
|
36
|
+
# if we want to go to the topic root
|
37
|
+
# @param offset [Integer, nil] offset of particular message or nil of we want to just go
|
38
|
+
# to the partition root
|
39
|
+
# @param action [String, nil] specific routed action or nil
|
40
|
+
# @return [String] path to the expected location
|
41
|
+
def explorer_path(topic_name = nil, partition_id = nil, offset = nil, action = nil)
|
42
|
+
root_path(*['explorer', topic_name, partition_id, offset, action].compact)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -12,16 +12,17 @@ module Karafka
|
|
12
12
|
# It is mostly used for flat hashes.
|
13
13
|
#
|
14
14
|
# It is in a way similar to openstruct but has abilities to dive deep into objects
|
15
|
+
#
|
16
|
+
# It is not super fast but it is enough for the UI and how deep structures we have.
|
15
17
|
class HashProxy
|
18
|
+
extend Forwardable
|
19
|
+
|
20
|
+
def_delegators :@hash, :[], :[]=, :key?, :each, :find
|
21
|
+
|
16
22
|
# @param hash [Hash] hash we want to convert to a proxy
|
17
23
|
def initialize(hash)
|
18
24
|
@hash = hash
|
19
|
-
|
20
|
-
|
21
|
-
# @param key [Object] hash key
|
22
|
-
# @return [Object] key content or nil if missing
|
23
|
-
def [](key)
|
24
|
-
@hash[key]
|
25
|
+
@visited = []
|
25
26
|
end
|
26
27
|
|
27
28
|
# @return [Original hash]
|
@@ -35,7 +36,12 @@ module Karafka
|
|
35
36
|
def method_missing(method_name, *args, &block)
|
36
37
|
return super unless args.empty? && block.nil?
|
37
38
|
|
39
|
+
@visited.clear
|
40
|
+
|
38
41
|
result = deep_find(@hash, method_name.to_sym)
|
42
|
+
|
43
|
+
@visited.clear
|
44
|
+
|
39
45
|
result.nil? ? super : result
|
40
46
|
end
|
41
47
|
|
@@ -51,6 +57,12 @@ module Karafka
|
|
51
57
|
# @param obj [Object] local scope of iterating
|
52
58
|
# @param key [Symbol, String] key we are looking for
|
53
59
|
def deep_find(obj, key)
|
60
|
+
# Prevent circular dependency lookups by making sure we do not check the same object
|
61
|
+
# multiple times
|
62
|
+
return nil if @visited.include?(obj)
|
63
|
+
|
64
|
+
@visited << obj
|
65
|
+
|
54
66
|
if obj.respond_to?(:key?) && obj.key?(key)
|
55
67
|
obj[key]
|
56
68
|
elsif obj.respond_to?(:each)
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Web
|
5
|
+
module Ui
|
6
|
+
module Lib
|
7
|
+
# Namespace for all the types of pagination engines we want to support
|
8
|
+
module Paginations
|
9
|
+
# Abstraction on top of pagination, so we can alter pagination key and other things
|
10
|
+
# for non-standard pagination views (non page based, etc)
|
11
|
+
#
|
12
|
+
# @note We do not use `_page` explicitly to indicate, that the page scope may not operate
|
13
|
+
# on numerable pages (1,2,3,4) but can operate on offsets or times, etc. `_offset` is
|
14
|
+
# more general and may refer to many types of pagination.
|
15
|
+
class Base
|
16
|
+
attr_reader :previous_offset, :current_offset, :next_offset
|
17
|
+
|
18
|
+
# @return [Boolean] Should we show pagination at all
|
19
|
+
def paginate?
|
20
|
+
raise NotImplementedError, 'Implement in a subclass'
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [Boolean] Should first offset link be active. If false, the first offset link
|
24
|
+
# will be disabled
|
25
|
+
def first_offset?
|
26
|
+
raise NotImplementedError, 'Implement in a subclass'
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [String] first offset url value
|
30
|
+
def first_offset
|
31
|
+
raise NotImplementedError, 'Implement in a subclass'
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [Boolean] Should previous offset link be active. If false, the previous
|
35
|
+
# offset link will be disabled
|
36
|
+
def previous_offset?
|
37
|
+
raise NotImplementedError, 'Implement in a subclass'
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [Boolean] Should we show current offset. If false, the current offset link
|
41
|
+
# will not be visible at all. Useful for non-linear pagination.
|
42
|
+
def current_offset?
|
43
|
+
raise NotImplementedError, 'Implement in a subclass'
|
44
|
+
end
|
45
|
+
|
46
|
+
# @return [Boolean] Should we show next offset pagination. If false, next offset link
|
47
|
+
# will be disabled.
|
48
|
+
def next_offset?
|
49
|
+
raise NotImplementedError, 'Implement in a subclass'
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [String] the url offset key
|
53
|
+
def offset_key
|
54
|
+
raise NotImplementedError, 'Implement in a subclass'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Web
|
5
|
+
module Ui
|
6
|
+
module Lib
|
7
|
+
module Paginations
|
8
|
+
# Kafka offset based pagination backend
|
9
|
+
#
|
10
|
+
# Allows us to support paginating over offsets
|
11
|
+
class OffsetBased < Base
|
12
|
+
# @param previous_offset [Integer, false] previous offset or false if should not be
|
13
|
+
# presented
|
14
|
+
# @param current_offset [Integer] current offset
|
15
|
+
# @param next_offset [Integer, Boolean] should we show next offset page button. If
|
16
|
+
# false it will not be presented.
|
17
|
+
# @param visible_offsets [Array<Integer>] offsets that are visible in the paginated
|
18
|
+
# view. It is needed for the current page label
|
19
|
+
def initialize(
|
20
|
+
previous_offset,
|
21
|
+
current_offset,
|
22
|
+
next_offset,
|
23
|
+
visible_offsets
|
24
|
+
)
|
25
|
+
@previous_offset = previous_offset
|
26
|
+
@current_offset = current_offset
|
27
|
+
@next_offset = next_offset
|
28
|
+
@visible_offsets = visible_offsets
|
29
|
+
super()
|
30
|
+
end
|
31
|
+
|
32
|
+
# Show pagination only when there is more than one page of results to be presented
|
33
|
+
#
|
34
|
+
# @return [Boolean]
|
35
|
+
def paginate?
|
36
|
+
@current_offset && (!!@previous_offset || !!@next_offset)
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [Boolean] active only when we are not on the first page. First page is always
|
40
|
+
# indicated by the current offset being -1. If there is someone that sets up the
|
41
|
+
# current offset to a value equal to the last message in the topic partition, we do
|
42
|
+
# not consider it as a first page and we allow to "reset" to -1 via the first page
|
43
|
+
# button
|
44
|
+
def first_offset?
|
45
|
+
@current_offset != -1
|
46
|
+
end
|
47
|
+
|
48
|
+
# @return [Boolean] first page offset is always nothing because we use the default -1
|
49
|
+
# for the offset.
|
50
|
+
def first_offset
|
51
|
+
false
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [Boolean] Active previous page link when it is not the first page
|
55
|
+
def previous_offset?
|
56
|
+
!!@previous_offset
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [Boolean] We show current label with offsets that are present on the given
|
60
|
+
# page
|
61
|
+
def current_offset?
|
62
|
+
true
|
63
|
+
end
|
64
|
+
|
65
|
+
# @return [Boolean] move to the next page if not false. False indicates, that there is
|
66
|
+
# no next page to move to
|
67
|
+
def next_offset?
|
68
|
+
!!@next_offset
|
69
|
+
end
|
70
|
+
|
71
|
+
# If there is no next offset, we point to 0 as there should be no smaller offset than
|
72
|
+
# that in Kafka ever
|
73
|
+
# @return [Integer]
|
74
|
+
def next_offset
|
75
|
+
next_offset? ? @next_offset : 0
|
76
|
+
end
|
77
|
+
|
78
|
+
# @return [String] label of the current page. It is combined out of the first and
|
79
|
+
# the last offsets to show the range where we are. It will be empty if no offsets
|
80
|
+
# but this is not a problem as then we should not display pagination at all
|
81
|
+
def current_label
|
82
|
+
first = @visible_offsets.first
|
83
|
+
last = @visible_offsets.last
|
84
|
+
[first, last].compact.uniq.join(' - ').to_s
|
85
|
+
end
|
86
|
+
|
87
|
+
# @return [String] for offset based pagination we use the offset param name
|
88
|
+
def offset_key
|
89
|
+
'offset'
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Web
|
5
|
+
module Ui
|
6
|
+
module Lib
|
7
|
+
module Paginations
|
8
|
+
# Regular page-based pagination engine
|
9
|
+
class PageBased < Base
|
10
|
+
# @param current_offset [Integer] current page
|
11
|
+
# @param show_next_offset [Boolean] should we show next page
|
12
|
+
# (value is computed automatically)
|
13
|
+
def initialize(
|
14
|
+
current_offset,
|
15
|
+
show_next_offset
|
16
|
+
)
|
17
|
+
@previous_offset = current_offset - 1
|
18
|
+
@current_offset = current_offset
|
19
|
+
@next_offset = show_next_offset ? current_offset + 1 : false
|
20
|
+
super()
|
21
|
+
end
|
22
|
+
|
23
|
+
# Show pagination only when there is more than one page
|
24
|
+
# @return [Boolean]
|
25
|
+
def paginate?
|
26
|
+
@current_offset && (@current_offset > 1 || !!@next_offset)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Boolean] active the first page link when we are not on the first page
|
30
|
+
def first_offset?
|
31
|
+
@current_offset > 1
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [Boolean] first page for page based pagination is always empty as it moves us
|
35
|
+
# to the initial page so we do not include any page info
|
36
|
+
def first_offset
|
37
|
+
false
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [Boolean] Active previous page link when it is not the first page
|
41
|
+
def previous_offset?
|
42
|
+
@current_offset > 1
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [Boolean] always show current offset pagination value
|
46
|
+
def current_offset?
|
47
|
+
true
|
48
|
+
end
|
49
|
+
|
50
|
+
# @return [String] label of the current page
|
51
|
+
def current_label
|
52
|
+
@current_offset.to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
# @return [Boolean] move to the next page if not false. False indicates, that there is
|
56
|
+
# no next page to move to
|
57
|
+
def next_offset?
|
58
|
+
@next_offset
|
59
|
+
end
|
60
|
+
|
61
|
+
# @return [String] for page pages pagination, always use page as the url value
|
62
|
+
def offset_key
|
63
|
+
'page'
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|