karafka-web 0.1.0
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 +7 -0
- checksums.yaml.gz.sig +3 -0
- data/.coditsu/ci.yml +3 -0
- data/.diffend.yml +3 -0
- data/.github/FUNDING.yml +1 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +50 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.github/workflows/ci.yml +49 -0
- data/.gitignore +69 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +9 -0
- data/CODE_OF_CONDUCT.md +46 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +52 -0
- data/LICENSE +17 -0
- data/README.md +29 -0
- data/bin/karafka-web +33 -0
- data/certs/cert_chain.pem +26 -0
- data/config/locales/errors.yml +9 -0
- data/karafka-web.gemspec +44 -0
- data/lib/karafka/web/app.rb +17 -0
- data/lib/karafka/web/config.rb +80 -0
- data/lib/karafka/web/deserializer.rb +20 -0
- data/lib/karafka/web/errors.rb +25 -0
- data/lib/karafka/web/installer.rb +124 -0
- data/lib/karafka/web/processing/consumer.rb +66 -0
- data/lib/karafka/web/processing/consumers/aggregator.rb +130 -0
- data/lib/karafka/web/processing/consumers/state.rb +32 -0
- data/lib/karafka/web/tracking/base_contract.rb +31 -0
- data/lib/karafka/web/tracking/consumers/contracts/consumer_group.rb +33 -0
- data/lib/karafka/web/tracking/consumers/contracts/job.rb +26 -0
- data/lib/karafka/web/tracking/consumers/contracts/partition.rb +22 -0
- data/lib/karafka/web/tracking/consumers/contracts/report.rb +95 -0
- data/lib/karafka/web/tracking/consumers/contracts/topic.rb +29 -0
- data/lib/karafka/web/tracking/consumers/listeners/base.rb +33 -0
- data/lib/karafka/web/tracking/consumers/listeners/errors.rb +107 -0
- data/lib/karafka/web/tracking/consumers/listeners/pausing.rb +45 -0
- data/lib/karafka/web/tracking/consumers/listeners/processing.rb +157 -0
- data/lib/karafka/web/tracking/consumers/listeners/statistics.rb +123 -0
- data/lib/karafka/web/tracking/consumers/listeners/status.rb +58 -0
- data/lib/karafka/web/tracking/consumers/sampler.rb +216 -0
- data/lib/karafka/web/tracking/memoized_shell.rb +48 -0
- data/lib/karafka/web/tracking/reporter.rb +144 -0
- data/lib/karafka/web/tracking/ttl_array.rb +59 -0
- data/lib/karafka/web/tracking/ttl_hash.rb +16 -0
- data/lib/karafka/web/ui/app.rb +78 -0
- data/lib/karafka/web/ui/base.rb +77 -0
- data/lib/karafka/web/ui/controllers/base.rb +40 -0
- data/lib/karafka/web/ui/controllers/become_pro.rb +17 -0
- data/lib/karafka/web/ui/controllers/cluster.rb +24 -0
- data/lib/karafka/web/ui/controllers/consumers.rb +27 -0
- data/lib/karafka/web/ui/controllers/errors.rb +43 -0
- data/lib/karafka/web/ui/controllers/jobs.rb +33 -0
- data/lib/karafka/web/ui/controllers/requests/params.rb +30 -0
- data/lib/karafka/web/ui/controllers/responses/data.rb +26 -0
- data/lib/karafka/web/ui/controllers/routing.rb +30 -0
- data/lib/karafka/web/ui/helpers/application_helper.rb +144 -0
- data/lib/karafka/web/ui/lib/hash_proxy.rb +66 -0
- data/lib/karafka/web/ui/lib/paginate_array.rb +38 -0
- data/lib/karafka/web/ui/models/consumer_group.rb +20 -0
- data/lib/karafka/web/ui/models/health.rb +44 -0
- data/lib/karafka/web/ui/models/job.rb +13 -0
- data/lib/karafka/web/ui/models/message.rb +99 -0
- data/lib/karafka/web/ui/models/partition.rb +13 -0
- data/lib/karafka/web/ui/models/process.rb +56 -0
- data/lib/karafka/web/ui/models/processes.rb +86 -0
- data/lib/karafka/web/ui/models/state.rb +67 -0
- data/lib/karafka/web/ui/models/topic.rb +19 -0
- data/lib/karafka/web/ui/pro/app.rb +120 -0
- data/lib/karafka/web/ui/pro/controllers/cluster.rb +16 -0
- data/lib/karafka/web/ui/pro/controllers/consumers.rb +54 -0
- data/lib/karafka/web/ui/pro/controllers/dlq.rb +44 -0
- data/lib/karafka/web/ui/pro/controllers/errors.rb +57 -0
- data/lib/karafka/web/ui/pro/controllers/explorer.rb +79 -0
- data/lib/karafka/web/ui/pro/controllers/health.rb +33 -0
- data/lib/karafka/web/ui/pro/controllers/jobs.rb +26 -0
- data/lib/karafka/web/ui/pro/controllers/routing.rb +26 -0
- data/lib/karafka/web/ui/pro/views/consumers/_breadcrumbs.erb +27 -0
- data/lib/karafka/web/ui/pro/views/consumers/_consumer.erb +60 -0
- data/lib/karafka/web/ui/pro/views/consumers/_counters.erb +50 -0
- data/lib/karafka/web/ui/pro/views/consumers/_summary.erb +81 -0
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_consumer_group.erb +109 -0
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_job.erb +26 -0
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_metrics.erb +126 -0
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_no_jobs.erb +9 -0
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_no_subscriptions.erb +9 -0
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_partition.erb +32 -0
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_stopped.erb +10 -0
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_tabs.erb +20 -0
- data/lib/karafka/web/ui/pro/views/consumers/index.erb +30 -0
- data/lib/karafka/web/ui/pro/views/consumers/jobs.erb +42 -0
- data/lib/karafka/web/ui/pro/views/consumers/subscriptions.erb +23 -0
- data/lib/karafka/web/ui/pro/views/dlq/_breadcrumbs.erb +5 -0
- data/lib/karafka/web/ui/pro/views/dlq/_no_topics.erb +9 -0
- data/lib/karafka/web/ui/pro/views/dlq/_topic.erb +12 -0
- data/lib/karafka/web/ui/pro/views/dlq/index.erb +16 -0
- data/lib/karafka/web/ui/pro/views/errors/_breadcrumbs.erb +25 -0
- data/lib/karafka/web/ui/pro/views/errors/_detail.erb +29 -0
- data/lib/karafka/web/ui/pro/views/errors/_error.erb +26 -0
- data/lib/karafka/web/ui/pro/views/errors/_partition_option.erb +7 -0
- data/lib/karafka/web/ui/pro/views/errors/index.erb +58 -0
- data/lib/karafka/web/ui/pro/views/errors/show.erb +56 -0
- data/lib/karafka/web/ui/pro/views/explorer/_breadcrumbs.erb +29 -0
- data/lib/karafka/web/ui/pro/views/explorer/_detail.erb +21 -0
- data/lib/karafka/web/ui/pro/views/explorer/_encryption_enabled.erb +18 -0
- data/lib/karafka/web/ui/pro/views/explorer/_failed_deserialization.erb +4 -0
- data/lib/karafka/web/ui/pro/views/explorer/_message.erb +16 -0
- data/lib/karafka/web/ui/pro/views/explorer/_partition_option.erb +7 -0
- data/lib/karafka/web/ui/pro/views/explorer/_topic.erb +12 -0
- data/lib/karafka/web/ui/pro/views/explorer/index.erb +17 -0
- data/lib/karafka/web/ui/pro/views/explorer/partition.erb +56 -0
- data/lib/karafka/web/ui/pro/views/explorer/show.erb +65 -0
- data/lib/karafka/web/ui/pro/views/health/_breadcrumbs.erb +5 -0
- data/lib/karafka/web/ui/pro/views/health/_partition.erb +35 -0
- data/lib/karafka/web/ui/pro/views/health/index.erb +60 -0
- data/lib/karafka/web/ui/pro/views/jobs/_breadcrumbs.erb +5 -0
- data/lib/karafka/web/ui/pro/views/jobs/_job.erb +31 -0
- data/lib/karafka/web/ui/pro/views/jobs/_no_jobs.erb +9 -0
- data/lib/karafka/web/ui/pro/views/jobs/index.erb +34 -0
- data/lib/karafka/web/ui/pro/views/shared/_navigation.erb +57 -0
- data/lib/karafka/web/ui/public/images/favicon.ico +0 -0
- data/lib/karafka/web/ui/public/images/logo.svg +28 -0
- data/lib/karafka/web/ui/public/javascripts/application.js +41 -0
- data/lib/karafka/web/ui/public/javascripts/bootstrap.min.js +7 -0
- data/lib/karafka/web/ui/public/javascripts/highlight.min.js +337 -0
- data/lib/karafka/web/ui/public/javascripts/live_poll.js +124 -0
- data/lib/karafka/web/ui/public/javascripts/timeago.min.js +1 -0
- data/lib/karafka/web/ui/public/stylesheets/application.css +106 -0
- data/lib/karafka/web/ui/public/stylesheets/bootstrap.min.css +7 -0
- data/lib/karafka/web/ui/public/stylesheets/bootstrap.min.css.map +1 -0
- data/lib/karafka/web/ui/public/stylesheets/highlight.min.css +10 -0
- data/lib/karafka/web/ui/views/cluster/_breadcrumbs.erb +5 -0
- data/lib/karafka/web/ui/views/cluster/_broker.erb +5 -0
- data/lib/karafka/web/ui/views/cluster/_partition.erb +22 -0
- data/lib/karafka/web/ui/views/cluster/index.erb +72 -0
- data/lib/karafka/web/ui/views/consumers/_breadcrumbs.erb +27 -0
- data/lib/karafka/web/ui/views/consumers/_consumer.erb +43 -0
- data/lib/karafka/web/ui/views/consumers/_counters.erb +44 -0
- data/lib/karafka/web/ui/views/consumers/_summary.erb +81 -0
- data/lib/karafka/web/ui/views/consumers/consumer/_consumer_group.erb +109 -0
- data/lib/karafka/web/ui/views/consumers/consumer/_job.erb +26 -0
- data/lib/karafka/web/ui/views/consumers/consumer/_metrics.erb +126 -0
- data/lib/karafka/web/ui/views/consumers/consumer/_no_jobs.erb +9 -0
- data/lib/karafka/web/ui/views/consumers/consumer/_no_subscriptions.erb +9 -0
- data/lib/karafka/web/ui/views/consumers/consumer/_partition.erb +32 -0
- data/lib/karafka/web/ui/views/consumers/consumer/_stopped.erb +10 -0
- data/lib/karafka/web/ui/views/consumers/consumer/_tabs.erb +20 -0
- data/lib/karafka/web/ui/views/consumers/index.erb +29 -0
- data/lib/karafka/web/ui/views/errors/_breadcrumbs.erb +19 -0
- data/lib/karafka/web/ui/views/errors/_detail.erb +29 -0
- data/lib/karafka/web/ui/views/errors/_error.erb +26 -0
- data/lib/karafka/web/ui/views/errors/index.erb +38 -0
- data/lib/karafka/web/ui/views/errors/show.erb +30 -0
- data/lib/karafka/web/ui/views/jobs/_breadcrumbs.erb +5 -0
- data/lib/karafka/web/ui/views/jobs/_job.erb +22 -0
- data/lib/karafka/web/ui/views/jobs/_no_jobs.erb +9 -0
- data/lib/karafka/web/ui/views/jobs/index.erb +31 -0
- data/lib/karafka/web/ui/views/layout.erb +23 -0
- data/lib/karafka/web/ui/views/routing/_breadcrumbs.erb +15 -0
- data/lib/karafka/web/ui/views/routing/_consumer_group.erb +34 -0
- data/lib/karafka/web/ui/views/routing/_detail.erb +25 -0
- data/lib/karafka/web/ui/views/routing/_topic.erb +18 -0
- data/lib/karafka/web/ui/views/routing/index.erb +10 -0
- data/lib/karafka/web/ui/views/routing/show.erb +26 -0
- data/lib/karafka/web/ui/views/shared/_become_pro.erb +13 -0
- data/lib/karafka/web/ui/views/shared/_brand.erb +3 -0
- data/lib/karafka/web/ui/views/shared/_content.erb +31 -0
- data/lib/karafka/web/ui/views/shared/_header.erb +20 -0
- data/lib/karafka/web/ui/views/shared/_navigation.erb +57 -0
- data/lib/karafka/web/ui/views/shared/_pagination.erb +21 -0
- data/lib/karafka/web/ui/views/shared/exceptions/not_found.erb +39 -0
- data/lib/karafka/web/ui/views/shared/exceptions/pro_only.erb +52 -0
- data/lib/karafka/web/version.rb +8 -0
- data/lib/karafka/web.rb +60 -0
- data.tar.gz.sig +0 -0
- metadata +328 -0
- metadata.gz.sig +0 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Web
|
|
5
|
+
module Ui
|
|
6
|
+
module Controllers
|
|
7
|
+
# Selects cluster info and topics basic info
|
|
8
|
+
class Cluster < Base
|
|
9
|
+
# List cluster info data
|
|
10
|
+
def index
|
|
11
|
+
@cluster_info = Karafka::Admin.cluster_info
|
|
12
|
+
|
|
13
|
+
@topics = @cluster_info
|
|
14
|
+
.topics
|
|
15
|
+
.reject { |topic| topic[:topic_name] == '__consumer_offsets' }
|
|
16
|
+
.sort_by { |topic| topic[:topic_name] }
|
|
17
|
+
|
|
18
|
+
respond
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Web
|
|
5
|
+
module Ui
|
|
6
|
+
module Controllers
|
|
7
|
+
# Consumers (consuming processes - `karafka server`) processes display consumer
|
|
8
|
+
class Consumers < Base
|
|
9
|
+
# List page with consumers
|
|
10
|
+
# @note For now we load all and paginate over the squashed data.
|
|
11
|
+
def index
|
|
12
|
+
@current_state = Models::State.current!
|
|
13
|
+
processes_total = Models::Processes.active(@current_state)
|
|
14
|
+
|
|
15
|
+
@counters = Lib::HashProxy.new(@current_state[:stats])
|
|
16
|
+
@processes, @next_page = Lib::PaginateArray.new.call(
|
|
17
|
+
processes_total,
|
|
18
|
+
@params.current_page
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
respond
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Web
|
|
5
|
+
module Ui
|
|
6
|
+
module Controllers
|
|
7
|
+
# Errors displaying controller
|
|
8
|
+
# It supports only scenarios with a single partition for errors
|
|
9
|
+
# If you have high load of errors, consider going Pro
|
|
10
|
+
class Errors < Base
|
|
11
|
+
# Lists first page of the errors
|
|
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
|
+
respond
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @param offset [Integer] given error message offset
|
|
23
|
+
def show(offset)
|
|
24
|
+
@error_message = Models::Message.find(
|
|
25
|
+
errors_topic,
|
|
26
|
+
0,
|
|
27
|
+
offset
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
respond
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
# @return [String] errors topic
|
|
36
|
+
def errors_topic
|
|
37
|
+
::Karafka::Web.config.topics.errors
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Web
|
|
5
|
+
module Ui
|
|
6
|
+
module Controllers
|
|
7
|
+
# Active jobs (work) reporting controller
|
|
8
|
+
class Jobs < Base
|
|
9
|
+
# Lists jobs
|
|
10
|
+
def index
|
|
11
|
+
current_state = Models::State.current!
|
|
12
|
+
processes = Models::Processes.active(current_state)
|
|
13
|
+
|
|
14
|
+
# Aggregate jobs and inject the process info into them for better reporting
|
|
15
|
+
jobs_total = processes.flat_map do |process|
|
|
16
|
+
process.jobs.map do |job|
|
|
17
|
+
job.to_h[:process] = process
|
|
18
|
+
job
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
@jobs, @next_page = Ui::Lib::PaginateArray.new.call(
|
|
23
|
+
jobs_total,
|
|
24
|
+
@params.current_page
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
respond
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Web
|
|
5
|
+
module Ui
|
|
6
|
+
module Controllers
|
|
7
|
+
# Namespace for request related components
|
|
8
|
+
module Requests
|
|
9
|
+
# Internal representation of params with sane sanitization
|
|
10
|
+
class Params
|
|
11
|
+
# @param request_params [Hash] raw hash with params
|
|
12
|
+
def initialize(request_params)
|
|
13
|
+
@request_params = request_params
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @return [Integer] current page for paginated views
|
|
17
|
+
# @note It does basic sanitization
|
|
18
|
+
def current_page
|
|
19
|
+
@current_page ||= begin
|
|
20
|
+
page = @request_params['page'].to_i
|
|
21
|
+
|
|
22
|
+
page.positive? ? page : 1
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Web
|
|
5
|
+
module Ui
|
|
6
|
+
module Controllers
|
|
7
|
+
# Response related components
|
|
8
|
+
module Responses
|
|
9
|
+
# Response data object. It is used to transfer attributes assigned in controllers into
|
|
10
|
+
# views
|
|
11
|
+
# It acts as a simplification / transport layer for assigned attributes
|
|
12
|
+
class Data
|
|
13
|
+
attr_reader :path, :attributes
|
|
14
|
+
|
|
15
|
+
# @param path [String] render path
|
|
16
|
+
# @param attributes [Hash] attributes assigned in the controller
|
|
17
|
+
def initialize(path, attributes)
|
|
18
|
+
@path = path
|
|
19
|
+
@attributes = attributes
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Web
|
|
5
|
+
module Ui
|
|
6
|
+
module Controllers
|
|
7
|
+
# Routing presentation controller
|
|
8
|
+
class Routing < Base
|
|
9
|
+
# Routing list
|
|
10
|
+
def index
|
|
11
|
+
@routes = Karafka::App.routes
|
|
12
|
+
|
|
13
|
+
respond
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Given route details
|
|
17
|
+
#
|
|
18
|
+
# @param topic_id [String] topic id
|
|
19
|
+
def show(topic_id)
|
|
20
|
+
@topic = Karafka::Routing::Router.find_by(id: topic_id)
|
|
21
|
+
|
|
22
|
+
@topic || raise(::Karafka::Web::Errors::Ui::NotFoundError, topic_id)
|
|
23
|
+
|
|
24
|
+
respond
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# @see https://github.com/mperham/sidekiq/blob/main/lib/sidekiq/web/helpers.rb
|
|
4
|
+
module Karafka
|
|
5
|
+
module Web
|
|
6
|
+
module Ui
|
|
7
|
+
# Namespace for helpers used by the Web UI
|
|
8
|
+
module Helpers
|
|
9
|
+
# Main application helper
|
|
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
|
+
# Adds active class to the current location in the nav if needed
|
|
24
|
+
# @param location [Hash]
|
|
25
|
+
def nav_class(location)
|
|
26
|
+
comparator, value = location.to_a.first
|
|
27
|
+
|
|
28
|
+
local_location = request.path.gsub(env.fetch('SCRIPT_NAME'), '')
|
|
29
|
+
local_location.public_send(:"#{comparator}?", value) ? 'active' : ''
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Converts object into a string and for objects that would anyhow return their
|
|
33
|
+
# stringified instance value, it replaces it with the class name instead.
|
|
34
|
+
# Useful for deserializers, etc presentation.
|
|
35
|
+
#
|
|
36
|
+
# @param object [Object]
|
|
37
|
+
# @return [String]
|
|
38
|
+
def object_value_to_s(object)
|
|
39
|
+
object.to_s.include?('#<') ? object.class.to_s : object.to_s
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Renders per scope breadcrumbs
|
|
43
|
+
def render_breadcrumbs
|
|
44
|
+
scope = request.path.gsub(root_path, '').split('/')[0]
|
|
45
|
+
|
|
46
|
+
render "#{scope}/_breadcrumbs"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Takes a status and recommends background style color
|
|
50
|
+
#
|
|
51
|
+
# @param status [String] status
|
|
52
|
+
# @return [String] background style
|
|
53
|
+
def status_bg(status)
|
|
54
|
+
case status
|
|
55
|
+
when 'initialized' then 'bg-success'
|
|
56
|
+
when 'running' then 'bg-success'
|
|
57
|
+
when 'quieting' then 'bg-warning'
|
|
58
|
+
when 'quiet' then 'bg-warning text-dark'
|
|
59
|
+
when 'stopping' then 'bg-warning text-dark'
|
|
60
|
+
when 'stopped' then 'bg-danger'
|
|
61
|
+
else
|
|
62
|
+
raise ::Karafka::Errors::UnsupportedCaseError, status
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Takes the lag trend and gives it appropriate background style color for badge
|
|
67
|
+
#
|
|
68
|
+
# @param trend [Numeric] lag trend
|
|
69
|
+
# @return [String] bg classes
|
|
70
|
+
def lag_trend_bg(trend)
|
|
71
|
+
bg = 'bg-success' if trend.negative?
|
|
72
|
+
bg ||= 'bg-warning text-dark' if trend.positive?
|
|
73
|
+
bg ||= 'bg-secondary'
|
|
74
|
+
bg
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Takes a kafka report state and recommends background style color
|
|
78
|
+
# @param state [String] state
|
|
79
|
+
# @return [String] background style
|
|
80
|
+
def kafka_state_bg(state)
|
|
81
|
+
case state
|
|
82
|
+
when 'up' then 'bg-success text-white'
|
|
83
|
+
when 'active' then 'bg-success text-white'
|
|
84
|
+
when 'steady' then 'bg-success text-white'
|
|
85
|
+
else
|
|
86
|
+
'bg-warning text-dark'
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# @param mem_kb [Integer] memory used in KB
|
|
91
|
+
# @return [String] formatted memory usage
|
|
92
|
+
def format_memory(mem_kb)
|
|
93
|
+
return '0' if !mem_kb || mem_kb.zero?
|
|
94
|
+
|
|
95
|
+
if mem_kb < 10_240
|
|
96
|
+
"#{number_with_delimiter(mem_kb)} KB"
|
|
97
|
+
elsif mem_kb < 1_000_000
|
|
98
|
+
"#{number_with_delimiter((mem_kb / 1024.0).to_i)} MB"
|
|
99
|
+
else
|
|
100
|
+
"#{number_with_delimiter((mem_kb / (1024.0 * 1024.0)).round(1))} GB"
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Converts number to a more friendly delimiter based version
|
|
105
|
+
# @param number [Numeric]
|
|
106
|
+
# @return [String] number with delimiter
|
|
107
|
+
def number_with_delimiter(number)
|
|
108
|
+
return '' unless number
|
|
109
|
+
|
|
110
|
+
parts = number.to_s.to_str.split('.')
|
|
111
|
+
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, '\1,')
|
|
112
|
+
parts.join('.')
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# @param time [Float] UTC time float
|
|
116
|
+
# @return [String] relative time tag for timeago.js
|
|
117
|
+
def relative_time(time)
|
|
118
|
+
stamp = Time.at(time).getutc.iso8601
|
|
119
|
+
%(<time class="ltr" dir="ltr" title="#{stamp}" datetime="#{stamp}">#{time}</time>)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Returns the view title html code
|
|
123
|
+
#
|
|
124
|
+
# @param title [String] page title
|
|
125
|
+
# @param hr [Boolean] should we add the hr tag at the end
|
|
126
|
+
# @return [String] title html
|
|
127
|
+
def view_title(title, hr: false)
|
|
128
|
+
<<-HTML
|
|
129
|
+
<div class="container mb-5">
|
|
130
|
+
<div class="row">
|
|
131
|
+
<h3>
|
|
132
|
+
#{title}
|
|
133
|
+
</h3>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
#{hr ? '<hr/>' : ''}
|
|
137
|
+
</div>
|
|
138
|
+
HTML
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
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
|
+
# Proxy for hashes we use across UI.
|
|
9
|
+
# Often we have nested values we want to extract or just values we want to reference and
|
|
10
|
+
# this object drastically simplifies that.
|
|
11
|
+
#
|
|
12
|
+
# It is mostly used for flat hashes.
|
|
13
|
+
#
|
|
14
|
+
# It is in a way similar to openstruct but has abilities to dive deep into objects
|
|
15
|
+
class HashProxy
|
|
16
|
+
# @param hash [Hash] hash we want to convert to a proxy
|
|
17
|
+
def initialize(hash)
|
|
18
|
+
@hash = hash
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @param key [Object] hash key
|
|
22
|
+
# @return [Object] key content or nil if missing
|
|
23
|
+
def [](key)
|
|
24
|
+
@hash[key]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @return [Original hash]
|
|
28
|
+
def to_h
|
|
29
|
+
@hash
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @param method_name [String] method name
|
|
33
|
+
# @param args [Object] all the args of the method
|
|
34
|
+
# @param block [Proc] block for the method
|
|
35
|
+
def method_missing(method_name, *args, &block)
|
|
36
|
+
return super unless args.empty? && block.nil?
|
|
37
|
+
|
|
38
|
+
result = deep_find(@hash, method_name.to_sym)
|
|
39
|
+
result.nil? ? super : result
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @param method_name [String] method name
|
|
43
|
+
# @param include_private [Boolean]
|
|
44
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
45
|
+
result = deep_find(@hash, method_name.to_sym)
|
|
46
|
+
result.nil? ? super : true
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
# @param obj [Object] local scope of iterating
|
|
52
|
+
# @param key [Symbol, String] key we are looking for
|
|
53
|
+
def deep_find(obj, key)
|
|
54
|
+
if obj.respond_to?(:key?) && obj.key?(key)
|
|
55
|
+
obj[key]
|
|
56
|
+
elsif obj.respond_to?(:each)
|
|
57
|
+
r = nil
|
|
58
|
+
obj.find { |*a| r = deep_find(a.last, key) }
|
|
59
|
+
r
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Web
|
|
5
|
+
module Ui
|
|
6
|
+
module Lib
|
|
7
|
+
# A simple wrapper for paginating array related data structures
|
|
8
|
+
class PaginateArray
|
|
9
|
+
# @param array [Array] array we want to paginate
|
|
10
|
+
# @param current_page [Integer] page we want to be on
|
|
11
|
+
# @return [Array<Array, <Integer, nil>>] Array with two elements: first is the array with
|
|
12
|
+
# data of the given page and second is the next page number of nil in case there is
|
|
13
|
+
# no next page (end of data)
|
|
14
|
+
def call(array, current_page)
|
|
15
|
+
slices = array.each_slice(per_page).to_a
|
|
16
|
+
|
|
17
|
+
current_data = slices[current_page - 1] || []
|
|
18
|
+
|
|
19
|
+
if slices.count >= current_page - 1 && current_data.size >= per_page
|
|
20
|
+
next_page = current_page + 1
|
|
21
|
+
else
|
|
22
|
+
next_page = nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
[current_data, next_page]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
# @return [Integer] how many elements should we display in the UI
|
|
31
|
+
def per_page
|
|
32
|
+
::Karafka::Web.config.ui.per_page
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Web
|
|
5
|
+
module Ui
|
|
6
|
+
# Namespace for models representing pieces of data about Karafka setup
|
|
7
|
+
module Models
|
|
8
|
+
# Representation of data of a Karafka consumer group
|
|
9
|
+
class ConsumerGroup < Lib::HashProxy
|
|
10
|
+
# @return [Array<Topic>] Data of topics belonging to this consumer group
|
|
11
|
+
def topics
|
|
12
|
+
super.values.map do |topic_hash|
|
|
13
|
+
Topic.new(topic_hash)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Web
|
|
5
|
+
module Ui
|
|
6
|
+
module Models
|
|
7
|
+
# Aggregated health data statistics representation
|
|
8
|
+
class Health
|
|
9
|
+
class << self
|
|
10
|
+
# @param state [State] current system state
|
|
11
|
+
# @return [Hash] has with aggregated statistics
|
|
12
|
+
def current(state)
|
|
13
|
+
stats = {}
|
|
14
|
+
|
|
15
|
+
processes = Processes.active(state)
|
|
16
|
+
|
|
17
|
+
processes.each do |process|
|
|
18
|
+
process.consumer_groups.each do |details|
|
|
19
|
+
name = details.id
|
|
20
|
+
|
|
21
|
+
stats[name] ||= {}
|
|
22
|
+
|
|
23
|
+
details.topics.each do |details2|
|
|
24
|
+
t_name = details2.name
|
|
25
|
+
|
|
26
|
+
stats[name][t_name] ||= {}
|
|
27
|
+
details2.partitions.each do |partition|
|
|
28
|
+
partition_id = partition.id
|
|
29
|
+
stats[name][t_name] ||= {}
|
|
30
|
+
stats[name][t_name][partition_id] = partition.to_h
|
|
31
|
+
stats[name][t_name][partition_id][:process] = process
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
stats
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Web
|
|
5
|
+
module Ui
|
|
6
|
+
module Models
|
|
7
|
+
# A proxy between `::Karafka::Messages::Message` and web UI
|
|
8
|
+
# We work with the Karafka messages but use this model to wrap the work needed.
|
|
9
|
+
class Message
|
|
10
|
+
class << self
|
|
11
|
+
# Looks for a message from a given topic partition
|
|
12
|
+
#
|
|
13
|
+
# @param topic_id [String]
|
|
14
|
+
# @param partition_id [Integer]
|
|
15
|
+
# @param offset [Integer]
|
|
16
|
+
# @raise [::Karafka::Web::Errors::Ui::NotFoundError] when not found
|
|
17
|
+
def find(topic_id, partition_id, offset)
|
|
18
|
+
message = Karafka::Admin.read_topic(
|
|
19
|
+
topic_id,
|
|
20
|
+
partition_id,
|
|
21
|
+
1,
|
|
22
|
+
offset
|
|
23
|
+
).first
|
|
24
|
+
|
|
25
|
+
return message if message
|
|
26
|
+
|
|
27
|
+
raise(
|
|
28
|
+
::Karafka::Web::Errors::Ui::NotFoundError,
|
|
29
|
+
[topic_id, partition_id, offset].join(', ')
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Fetches requested page of Kafka messages.
|
|
34
|
+
#
|
|
35
|
+
# @param topic_id [String]
|
|
36
|
+
# @param partition_id [Integer]
|
|
37
|
+
# @param page [Integer]
|
|
38
|
+
# @return [Array] We return both page data as well as all the details needed to build
|
|
39
|
+
# the pagination details.
|
|
40
|
+
def page(topic_id, partition_id, page)
|
|
41
|
+
# Establish the leading offset
|
|
42
|
+
lead = Karafka::Admin.read_topic(topic_id, partition_id, 1).first
|
|
43
|
+
|
|
44
|
+
partitions_count = fetch_partition_count(topic_id)
|
|
45
|
+
|
|
46
|
+
# If there is not even one message, we need to early exit
|
|
47
|
+
return [false, [], false, partitions_count] unless lead
|
|
48
|
+
|
|
49
|
+
# We add plus one because we compute previous offset from which we want to start and
|
|
50
|
+
# not previous page leading offset
|
|
51
|
+
previous_offset = lead.offset - (per_page * page) + 1
|
|
52
|
+
|
|
53
|
+
if previous_offset.negative?
|
|
54
|
+
count = per_page + previous_offset
|
|
55
|
+
previous_page = page < 2 ? false : page - 1
|
|
56
|
+
next_page = false
|
|
57
|
+
previous_offset = 0
|
|
58
|
+
else
|
|
59
|
+
previous_page = page < 2 ? false : page - 1
|
|
60
|
+
next_page = page + 1
|
|
61
|
+
count = per_page
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
[
|
|
65
|
+
previous_page,
|
|
66
|
+
read_topic(topic_id, partition_id, count, previous_offset).reverse,
|
|
67
|
+
next_page,
|
|
68
|
+
partitions_count
|
|
69
|
+
]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
# @param args [Object] anything required by the admin `#read_topic`
|
|
75
|
+
# @return [Array<Karafka::Messages::Message>] topic partition messages
|
|
76
|
+
def read_topic(*args)
|
|
77
|
+
::Karafka::Admin.read_topic(*args)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @param topic_id [String] id of the topic
|
|
81
|
+
# @return [Integer] number of partitions this topic has
|
|
82
|
+
def fetch_partition_count(topic_id)
|
|
83
|
+
::Karafka::Admin
|
|
84
|
+
.cluster_info
|
|
85
|
+
.topics
|
|
86
|
+
.find { |topic| topic[:topic_name] == topic_id }
|
|
87
|
+
.fetch(:partition_count)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# @return [Integer] elements per page
|
|
91
|
+
def per_page
|
|
92
|
+
::Karafka::Web.config.ui.per_page
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|