karafka-web 0.7.10 → 0.8.0.rc1
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 +18 -5
- data/.ruby-version +1 -1
- data/CHANGELOG.md +63 -0
- data/Gemfile.lock +22 -22
- data/docker-compose.yml +3 -1
- data/karafka-web.gemspec +2 -2
- data/lib/karafka/web/config.rb +16 -3
- data/lib/karafka/web/contracts/config.rb +7 -2
- data/lib/karafka/web/errors.rb +12 -0
- data/lib/karafka/web/inflector.rb +33 -0
- data/lib/karafka/web/installer.rb +20 -11
- data/lib/karafka/web/management/actions/base.rb +36 -0
- data/lib/karafka/web/management/actions/clean_boot_file.rb +33 -0
- data/lib/karafka/web/management/actions/create_initial_states.rb +77 -0
- data/lib/karafka/web/management/actions/create_topics.rb +139 -0
- data/lib/karafka/web/management/actions/delete_topics.rb +30 -0
- data/lib/karafka/web/management/actions/enable.rb +117 -0
- data/lib/karafka/web/management/actions/extend_boot_file.rb +39 -0
- data/lib/karafka/web/management/actions/migrate_states_data.rb +18 -0
- data/lib/karafka/web/management/migrations/0_base.rb +58 -0
- data/lib/karafka/web/management/migrations/0_set_initial_consumers_metrics.rb +36 -0
- data/lib/karafka/web/management/migrations/0_set_initial_consumers_state.rb +43 -0
- data/lib/karafka/web/management/migrations/1699543515_fill_missing_received_and_sent_bytes_in_consumers_metrics.rb +26 -0
- data/lib/karafka/web/management/migrations/1699543515_fill_missing_received_and_sent_bytes_in_consumers_state.rb +23 -0
- data/lib/karafka/web/management/migrations/1700234522_introduce_waiting_in_consumers_metrics.rb +24 -0
- data/lib/karafka/web/management/migrations/1700234522_introduce_waiting_in_consumers_state.rb +20 -0
- data/lib/karafka/web/management/migrations/1700234522_remove_processing_from_consumers_metrics.rb +24 -0
- data/lib/karafka/web/management/migrations/1700234522_remove_processing_from_consumers_state.rb +20 -0
- data/lib/karafka/web/management/migrations/1704722380_split_listeners_into_active_and_paused_in_metrics.rb +36 -0
- data/lib/karafka/web/management/migrations/1704722380_split_listeners_into_active_and_paused_in_states.rb +32 -0
- data/lib/karafka/web/management/migrator.rb +117 -0
- data/lib/karafka/web/processing/consumer.rb +39 -38
- data/lib/karafka/web/processing/consumers/aggregators/metrics.rb +2 -3
- data/lib/karafka/web/processing/consumers/aggregators/state.rb +8 -3
- data/lib/karafka/web/processing/consumers/contracts/aggregated_stats.rb +5 -1
- data/lib/karafka/web/processing/publisher.rb +59 -0
- data/lib/karafka/web/tracking/consumers/contracts/job.rb +3 -2
- data/lib/karafka/web/tracking/consumers/contracts/partition.rb +1 -0
- data/lib/karafka/web/tracking/consumers/contracts/report.rb +6 -1
- data/lib/karafka/web/tracking/consumers/contracts/subscription_group.rb +10 -1
- data/lib/karafka/web/tracking/consumers/listeners/connections.rb +49 -0
- data/lib/karafka/web/tracking/consumers/listeners/pausing.rb +7 -4
- data/lib/karafka/web/tracking/consumers/listeners/processing.rb +78 -70
- data/lib/karafka/web/tracking/consumers/listeners/statistics.rb +40 -13
- data/lib/karafka/web/tracking/consumers/sampler.rb +82 -25
- data/lib/karafka/web/tracking/helpers/ttls/array.rb +72 -0
- data/lib/karafka/web/tracking/helpers/ttls/hash.rb +34 -0
- data/lib/karafka/web/tracking/helpers/ttls/stats.rb +49 -0
- data/lib/karafka/web/tracking/helpers/ttls/windows.rb +32 -0
- data/lib/karafka/web/tracking/reporter.rb +1 -0
- data/lib/karafka/web/ui/app.rb +22 -4
- data/lib/karafka/web/ui/base.rb +18 -2
- data/lib/karafka/web/ui/controllers/base.rb +34 -4
- data/lib/karafka/web/ui/controllers/become_pro.rb +1 -1
- data/lib/karafka/web/ui/controllers/cluster.rb +33 -9
- data/lib/karafka/web/ui/controllers/consumers.rb +8 -2
- data/lib/karafka/web/ui/controllers/dashboard.rb +2 -2
- data/lib/karafka/web/ui/controllers/errors.rb +2 -2
- data/lib/karafka/web/ui/controllers/jobs.rb +55 -5
- data/lib/karafka/web/ui/controllers/requests/params.rb +5 -0
- data/lib/karafka/web/ui/controllers/responses/deny.rb +15 -0
- data/lib/karafka/web/ui/controllers/responses/file.rb +23 -0
- data/lib/karafka/web/ui/controllers/responses/{data.rb → render.rb} +3 -3
- data/lib/karafka/web/ui/controllers/routing.rb +11 -2
- data/lib/karafka/web/ui/controllers/status.rb +1 -1
- data/lib/karafka/web/ui/helpers/application_helper.rb +70 -0
- data/lib/karafka/web/ui/lib/hash_proxy.rb +29 -14
- data/lib/karafka/web/ui/lib/sorter.rb +170 -0
- data/lib/karafka/web/ui/models/counters.rb +6 -0
- data/lib/karafka/web/ui/models/health.rb +23 -2
- data/lib/karafka/web/ui/models/jobs.rb +48 -0
- data/lib/karafka/web/ui/models/metrics/charts/aggregated.rb +33 -0
- data/lib/karafka/web/ui/models/metrics/charts/topics.rb +1 -1
- data/lib/karafka/web/ui/models/process.rb +2 -1
- data/lib/karafka/web/ui/models/status.rb +23 -7
- data/lib/karafka/web/ui/models/topic.rb +3 -1
- data/lib/karafka/web/ui/models/visibility_filter.rb +16 -0
- data/lib/karafka/web/ui/pro/app.rb +44 -6
- data/lib/karafka/web/ui/pro/controllers/cluster.rb +1 -0
- data/lib/karafka/web/ui/pro/controllers/consumers.rb +52 -6
- data/lib/karafka/web/ui/pro/controllers/dashboard.rb +1 -1
- data/lib/karafka/web/ui/pro/controllers/dlq.rb +1 -1
- data/lib/karafka/web/ui/pro/controllers/errors.rb +3 -3
- data/lib/karafka/web/ui/pro/controllers/explorer.rb +8 -8
- data/lib/karafka/web/ui/pro/controllers/health.rb +34 -2
- data/lib/karafka/web/ui/pro/controllers/jobs.rb +11 -0
- data/lib/karafka/web/ui/pro/controllers/messages.rb +42 -0
- data/lib/karafka/web/ui/pro/controllers/routing.rb +11 -2
- data/lib/karafka/web/ui/pro/views/consumers/_breadcrumbs.erb +8 -2
- data/lib/karafka/web/ui/pro/views/consumers/_consumer.erb +14 -8
- data/lib/karafka/web/ui/pro/views/consumers/_counters.erb +8 -6
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_job.erb +4 -1
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_no_jobs.erb +1 -1
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_partition.erb +1 -3
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_subscription_group.erb +28 -11
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_tabs.erb +10 -3
- data/lib/karafka/web/ui/pro/views/consumers/index.erb +3 -3
- data/lib/karafka/web/ui/pro/views/consumers/pending_jobs.erb +43 -0
- data/lib/karafka/web/ui/pro/views/consumers/{jobs.erb → running_jobs.erb} +11 -10
- data/lib/karafka/web/ui/pro/views/dashboard/index.erb +7 -1
- data/lib/karafka/web/ui/pro/views/explorer/message/_message_actions.erb +18 -0
- data/lib/karafka/web/ui/pro/views/explorer/message/_metadata.erb +43 -0
- data/lib/karafka/web/ui/pro/views/explorer/message/_payload.erb +21 -0
- data/lib/karafka/web/ui/pro/views/explorer/message/_payload_actions.erb +19 -0
- data/lib/karafka/web/ui/pro/views/explorer/show.erb +9 -84
- data/lib/karafka/web/ui/pro/views/health/_breadcrumbs.erb +8 -0
- data/lib/karafka/web/ui/pro/views/health/_partition.erb +1 -3
- data/lib/karafka/web/ui/pro/views/health/_partition_offset.erb +4 -4
- data/lib/karafka/web/ui/pro/views/health/_partition_times.erb +32 -0
- data/lib/karafka/web/ui/pro/views/health/_tabs.erb +9 -0
- data/lib/karafka/web/ui/pro/views/health/changes.erb +66 -0
- data/lib/karafka/web/ui/pro/views/health/offsets.erb +14 -14
- data/lib/karafka/web/ui/pro/views/health/overview.erb +11 -11
- data/lib/karafka/web/ui/pro/views/jobs/_job.erb +1 -1
- data/lib/karafka/web/ui/pro/views/jobs/_no_jobs.erb +1 -1
- data/lib/karafka/web/ui/pro/views/jobs/pending.erb +39 -0
- data/lib/karafka/web/ui/pro/views/jobs/running.erb +39 -0
- data/lib/karafka/web/ui/pro/views/routing/_consumer_group.erb +2 -2
- data/lib/karafka/web/ui/pro/views/routing/_topic.erb +9 -0
- data/lib/karafka/web/ui/pro/views/routing/show.erb +12 -0
- data/lib/karafka/web/ui/pro/views/shared/_navigation.erb +1 -1
- data/lib/karafka/web/ui/public/javascripts/application.js +10 -0
- data/lib/karafka/web/ui/public/stylesheets/application.css +4 -0
- data/lib/karafka/web/ui/views/cluster/_breadcrumbs.erb +16 -0
- data/lib/karafka/web/ui/views/cluster/_tabs.erb +27 -0
- data/lib/karafka/web/ui/views/cluster/brokers.erb +27 -0
- data/lib/karafka/web/ui/views/cluster/topics.erb +35 -0
- data/lib/karafka/web/ui/views/consumers/_counters.erb +8 -6
- data/lib/karafka/web/ui/views/consumers/_summary.erb +2 -2
- data/lib/karafka/web/ui/views/consumers/index.erb +3 -3
- data/lib/karafka/web/ui/views/dashboard/_ranges_selector.erb +23 -7
- data/lib/karafka/web/ui/views/dashboard/index.erb +19 -8
- data/lib/karafka/web/ui/views/errors/show.erb +2 -23
- data/lib/karafka/web/ui/views/jobs/_breadcrumbs.erb +17 -1
- data/lib/karafka/web/ui/views/jobs/_job.erb +1 -1
- data/lib/karafka/web/ui/views/jobs/_no_jobs.erb +1 -1
- data/lib/karafka/web/ui/views/jobs/_tabs.erb +27 -0
- data/lib/karafka/web/ui/views/jobs/{index.erb → pending.erb} +9 -7
- data/lib/karafka/web/ui/{pro/views/jobs/index.erb → views/jobs/running.erb} +9 -11
- data/lib/karafka/web/ui/views/routing/_consumer_group.erb +14 -12
- data/lib/karafka/web/ui/views/shared/_navigation.erb +1 -1
- data/lib/karafka/web/ui/views/shared/_pagination.erb +1 -1
- data/lib/karafka/web/ui/views/shared/exceptions/not_allowed.erb +37 -0
- data/lib/karafka/web/ui/views/status/show.erb +17 -2
- data/lib/karafka/web/ui/views/status/warnings/_routing_topics_presence.erb +15 -0
- data/lib/karafka/web/version.rb +1 -1
- data/lib/karafka/web.rb +6 -2
- data.tar.gz.sig +0 -0
- metadata +61 -26
- metadata.gz.sig +0 -0
- data/lib/karafka/web/management/base.rb +0 -34
- data/lib/karafka/web/management/clean_boot_file.rb +0 -31
- data/lib/karafka/web/management/create_initial_states.rb +0 -101
- data/lib/karafka/web/management/create_topics.rb +0 -133
- data/lib/karafka/web/management/delete_topics.rb +0 -28
- data/lib/karafka/web/management/enable.rb +0 -102
- data/lib/karafka/web/management/extend_boot_file.rb +0 -37
- data/lib/karafka/web/tracking/ttl_array.rb +0 -59
- data/lib/karafka/web/tracking/ttl_hash.rb +0 -16
- data/lib/karafka/web/ui/pro/views/dashboard/_ranges_selector.erb +0 -39
- data/lib/karafka/web/ui/views/cluster/index.erb +0 -74
data/lib/karafka/web/ui/app.rb
CHANGED
@@ -48,9 +48,18 @@ module Karafka
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
-
r.
|
51
|
+
r.on 'jobs' do
|
52
52
|
controller = Controllers::Jobs.new(params)
|
53
|
-
|
53
|
+
|
54
|
+
r.get 'running' do
|
55
|
+
controller.running
|
56
|
+
end
|
57
|
+
|
58
|
+
r.get 'pending' do
|
59
|
+
controller.pending
|
60
|
+
end
|
61
|
+
|
62
|
+
r.redirect root_path('jobs/running')
|
54
63
|
end
|
55
64
|
|
56
65
|
r.on 'routing' do
|
@@ -65,9 +74,18 @@ module Karafka
|
|
65
74
|
end
|
66
75
|
end
|
67
76
|
|
68
|
-
r.
|
77
|
+
r.on 'cluster' do
|
69
78
|
controller = Controllers::Cluster.new(params)
|
70
|
-
|
79
|
+
|
80
|
+
r.get 'brokers' do
|
81
|
+
controller.brokers
|
82
|
+
end
|
83
|
+
|
84
|
+
r.get 'topics' do
|
85
|
+
controller.topics
|
86
|
+
end
|
87
|
+
|
88
|
+
r.redirect root_path('cluster/brokers')
|
71
89
|
end
|
72
90
|
|
73
91
|
r.on 'errors' do
|
data/lib/karafka/web/ui/base.rb
CHANGED
@@ -55,10 +55,16 @@ module Karafka
|
|
55
55
|
|
56
56
|
plugin :custom_block_results
|
57
57
|
|
58
|
-
handle_block_result Controllers::Responses::
|
58
|
+
handle_block_result Controllers::Responses::Render do |result|
|
59
59
|
render_response(result)
|
60
60
|
end
|
61
61
|
|
62
|
+
handle_block_result Controllers::Responses::Deny do
|
63
|
+
@error = true
|
64
|
+
response.status = 403
|
65
|
+
view 'shared/exceptions/not_allowed'
|
66
|
+
end
|
67
|
+
|
62
68
|
# Redirect either to referer back or to the desired path
|
63
69
|
handle_block_result Controllers::Responses::Redirect do |result|
|
64
70
|
# Map redirect flashes (if any) to Roda flash messages
|
@@ -67,6 +73,12 @@ module Karafka
|
|
67
73
|
response.redirect result.back? ? request.referer : root_path(result.path)
|
68
74
|
end
|
69
75
|
|
76
|
+
handle_block_result Controllers::Responses::File do |result|
|
77
|
+
response.headers['Content-Type'] = 'application/octet-stream'
|
78
|
+
response.headers['Content-Disposition'] = "attachment; filename=\"#{result.file_name}\""
|
79
|
+
response.write result.content
|
80
|
+
end
|
81
|
+
|
70
82
|
# Display appropriate error specific to a given error type
|
71
83
|
plugin :error_handler, classes: [
|
72
84
|
::Rdkafka::RdkafkaError,
|
@@ -111,10 +123,14 @@ module Karafka
|
|
111
123
|
raise Errors::Ui::NotFoundError
|
112
124
|
end
|
113
125
|
|
114
|
-
# Allows us to build current path with additional params
|
126
|
+
# Allows us to build current path with additional params + it merges existing params into
|
127
|
+
# the query data. Query data takes priority over request params.
|
115
128
|
# @param query_data [Hash] query params we want to add to the current path
|
116
129
|
path :current do |query_data = {}|
|
117
130
|
q = query_data
|
131
|
+
.transform_values(&:to_s)
|
132
|
+
.transform_keys(&:to_s)
|
133
|
+
.then { |candidates| request.params.merge(candidates) }
|
118
134
|
.select { |_, v| v }
|
119
135
|
.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }
|
120
136
|
.join('&')
|
@@ -7,6 +7,13 @@ module Karafka
|
|
7
7
|
module Controllers
|
8
8
|
# Base controller from which all the controllers should inherit.
|
9
9
|
class Base
|
10
|
+
class << self
|
11
|
+
# Attributes on which we can sort in a given controller
|
12
|
+
attr_accessor :sortable_attributes
|
13
|
+
end
|
14
|
+
|
15
|
+
self.sortable_attributes = []
|
16
|
+
|
10
17
|
# @param params [Karafka::Web::Ui::Controllers::Requests::Params] request parameters
|
11
18
|
def initialize(params)
|
12
19
|
@params = params
|
@@ -14,10 +21,10 @@ module Karafka
|
|
14
21
|
|
15
22
|
private
|
16
23
|
|
17
|
-
# Builds the
|
24
|
+
# Builds the render data object with assigned attributes based on instance variables.
|
18
25
|
#
|
19
|
-
# @return [Responses::
|
20
|
-
def
|
26
|
+
# @return [Responses::Render] data that should be used to render appropriate view
|
27
|
+
def render
|
21
28
|
attributes = {}
|
22
29
|
|
23
30
|
scope = self.class.to_s.split('::').last.gsub(/(.)([A-Z])/, '\1_\2').downcase
|
@@ -30,7 +37,7 @@ module Karafka
|
|
30
37
|
attributes[iv.to_s.delete('@').to_sym] = instance_variable_get(iv)
|
31
38
|
end
|
32
39
|
|
33
|
-
Responses::
|
40
|
+
Responses::Render.new(
|
34
41
|
"#{scope}/#{action}",
|
35
42
|
attributes
|
36
43
|
)
|
@@ -45,6 +52,29 @@ module Karafka
|
|
45
52
|
Responses::Redirect.new(path, flashes)
|
46
53
|
end
|
47
54
|
|
55
|
+
# Builds a file response object that will be used as a base to dispatch the file
|
56
|
+
#
|
57
|
+
# @param content [String] Payload we want to dispatch as a file
|
58
|
+
# @param file_name [String] name under which the browser is suppose to save the file
|
59
|
+
# @return [Responses::File] file response result
|
60
|
+
def file(content, file_name)
|
61
|
+
Responses::File.new(content, file_name)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Builds a halt 403 response
|
65
|
+
def deny
|
66
|
+
Responses::Deny.new
|
67
|
+
end
|
68
|
+
|
69
|
+
# @param resources [Hash, Array, Lib::HashProxy] object for sorting
|
70
|
+
# @return [Hash, Array, Lib::HashProxy] sorted results
|
71
|
+
def refine(resources)
|
72
|
+
Lib::Sorter.new(
|
73
|
+
@params.sort,
|
74
|
+
allowed_attributes: self.class.sortable_attributes
|
75
|
+
).call(resources)
|
76
|
+
end
|
77
|
+
|
48
78
|
# Initializes the expected pagination engine and assigns expected arguments
|
49
79
|
# @param args Any arguments accepted by the selected pagination engine
|
50
80
|
def paginate(*args)
|
@@ -6,31 +6,55 @@ module Karafka
|
|
6
6
|
module Controllers
|
7
7
|
# Selects cluster info and topics basic info
|
8
8
|
class Cluster < Base
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
self.sortable_attributes = %w[
|
10
|
+
broker_id
|
11
|
+
broker_name
|
12
|
+
broker_port
|
13
|
+
topic_name
|
14
|
+
partition_id
|
15
|
+
leader
|
16
|
+
replica_count
|
17
|
+
in_sync_replica_brokers
|
18
|
+
].freeze
|
13
19
|
|
20
|
+
# Lists available brokers in the cluster
|
21
|
+
def brokers
|
22
|
+
@brokers = refine(cluster_info.brokers)
|
23
|
+
|
24
|
+
render
|
25
|
+
end
|
26
|
+
|
27
|
+
# List topics and partitions with details
|
28
|
+
def topics
|
14
29
|
partitions_total = []
|
15
30
|
|
16
|
-
displayable_topics(
|
31
|
+
displayable_topics(cluster_info).each do |topic|
|
17
32
|
topic[:partitions].each do |partition|
|
18
|
-
partitions_total << partition.merge(
|
33
|
+
partitions_total << partition.merge(
|
34
|
+
topic: topic,
|
35
|
+
# Will allow sorting by name
|
36
|
+
topic_name: topic.fetch(:topic_name)
|
37
|
+
)
|
19
38
|
end
|
20
39
|
end
|
21
40
|
|
22
41
|
@partitions, last_page = Ui::Lib::Paginations::Paginators::Arrays.call(
|
23
|
-
partitions_total,
|
42
|
+
refine(partitions_total),
|
24
43
|
@params.current_page
|
25
44
|
)
|
26
45
|
|
27
46
|
paginate(@params.current_page, !last_page)
|
28
47
|
|
29
|
-
|
48
|
+
render
|
30
49
|
end
|
31
50
|
|
32
51
|
private
|
33
52
|
|
53
|
+
# Make sure, that for the cluster view we always get the most recent cluster state
|
54
|
+
def cluster_info
|
55
|
+
@cluster_info ||= Models::ClusterInfo.fetch(cached: false)
|
56
|
+
end
|
57
|
+
|
34
58
|
# @param cluster_info [Rdkafka::Metadata] cluster metadata
|
35
59
|
# @return [Array<Hash>] array with topics to be displayed sorted in an alphabetical
|
36
60
|
# order
|
@@ -39,7 +63,7 @@ module Karafka
|
|
39
63
|
.topics
|
40
64
|
.sort_by { |topic| topic[:topic_name] }
|
41
65
|
|
42
|
-
return all if ::Karafka::Web.config.ui.
|
66
|
+
return all if ::Karafka::Web.config.ui.visibility.internal_topics
|
43
67
|
|
44
68
|
all.reject { |topic| topic[:topic_name].start_with?('__') }
|
45
69
|
end
|
@@ -6,19 +6,25 @@ module Karafka
|
|
6
6
|
module Controllers
|
7
7
|
# Consumers (consuming processes - `karafka server`) processes display consumer
|
8
8
|
class Consumers < Base
|
9
|
+
self.sortable_attributes = %w[
|
10
|
+
name
|
11
|
+
started_at
|
12
|
+
lag_stored
|
13
|
+
].freeze
|
14
|
+
|
9
15
|
# List page with consumers
|
10
16
|
# @note For now we load all and paginate over the squashed data.
|
11
17
|
def index
|
12
18
|
@current_state = Models::ConsumersState.current!
|
13
19
|
@counters = Models::Counters.new(@current_state)
|
14
20
|
@processes, last_page = Ui::Lib::Paginations::Paginators::Arrays.call(
|
15
|
-
Models::Processes.active(@current_state),
|
21
|
+
refine(Models::Processes.active(@current_state)),
|
16
22
|
@params.current_page
|
17
23
|
)
|
18
24
|
|
19
25
|
paginate(@params.current_page, !last_page)
|
20
26
|
|
21
|
-
|
27
|
+
render
|
22
28
|
end
|
23
29
|
end
|
24
30
|
end
|
@@ -6,27 +6,77 @@ module Karafka
|
|
6
6
|
module Controllers
|
7
7
|
# Active jobs (work) reporting controller
|
8
8
|
class Jobs < Base
|
9
|
-
|
10
|
-
|
9
|
+
self.sortable_attributes = %w[
|
10
|
+
name
|
11
|
+
topic
|
12
|
+
consumer
|
13
|
+
type
|
14
|
+
updated_at
|
15
|
+
].freeze
|
16
|
+
|
17
|
+
# Lists running jobs
|
18
|
+
def running
|
11
19
|
current_state = Models::ConsumersState.current!
|
12
20
|
processes = Models::Processes.active(current_state)
|
13
21
|
|
22
|
+
@jobs_counters = count_jobs_types(processes)
|
23
|
+
|
14
24
|
# Aggregate jobs and inject the process info into them for better reporting
|
15
25
|
jobs_total = processes.flat_map do |process|
|
16
|
-
process.jobs.map do |job|
|
26
|
+
process.jobs.running.map do |job|
|
17
27
|
job.to_h[:process] = process
|
18
28
|
job
|
19
29
|
end
|
20
30
|
end
|
21
31
|
|
22
32
|
@jobs, last_page = Ui::Lib::Paginations::Paginators::Arrays.call(
|
23
|
-
jobs_total,
|
33
|
+
refine(jobs_total),
|
24
34
|
@params.current_page
|
25
35
|
)
|
26
36
|
|
27
37
|
paginate(@params.current_page, !last_page)
|
28
38
|
|
29
|
-
|
39
|
+
render
|
40
|
+
end
|
41
|
+
|
42
|
+
# Lists pending jobs
|
43
|
+
def pending
|
44
|
+
current_state = Models::ConsumersState.current!
|
45
|
+
processes = Models::Processes.active(current_state)
|
46
|
+
|
47
|
+
@jobs_counters = count_jobs_types(processes)
|
48
|
+
|
49
|
+
# Aggregate jobs and inject the process info into them for better reporting
|
50
|
+
jobs_total = processes.flat_map do |process|
|
51
|
+
process.jobs.pending.map do |job|
|
52
|
+
job.to_h[:process] = process
|
53
|
+
job
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
@jobs, last_page = Ui::Lib::Paginations::Paginators::Arrays.call(
|
58
|
+
refine(jobs_total),
|
59
|
+
@params.current_page
|
60
|
+
)
|
61
|
+
|
62
|
+
paginate(@params.current_page, !last_page)
|
63
|
+
|
64
|
+
render
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
# @param processes [Array<Models::Process>]
|
70
|
+
# @return [Lib::HashProxy] particular type jobs count
|
71
|
+
def count_jobs_types(processes)
|
72
|
+
counts = { running: 0, pending: 0 }
|
73
|
+
|
74
|
+
processes.flat_map do |process|
|
75
|
+
counts[:running] += process.jobs.running.size
|
76
|
+
counts[:pending] += process.jobs.pending.size
|
77
|
+
end
|
78
|
+
|
79
|
+
Lib::HashProxy.new(counts)
|
30
80
|
end
|
31
81
|
end
|
32
82
|
end
|
@@ -24,6 +24,11 @@ module Karafka
|
|
24
24
|
@request_params = request_params
|
25
25
|
end
|
26
26
|
|
27
|
+
# @return [String] sort query value
|
28
|
+
def sort
|
29
|
+
@sort ||= @request_params['sort'].to_s.downcase
|
30
|
+
end
|
31
|
+
|
27
32
|
# @return [Integer] current page for paginated views
|
28
33
|
# @note It does basic sanitization
|
29
34
|
def current_page
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Web
|
5
|
+
module Ui
|
6
|
+
module Controllers
|
7
|
+
module Responses
|
8
|
+
# Response that tells Roda to ship the content under a file name
|
9
|
+
class File
|
10
|
+
attr_reader :content, :file_name
|
11
|
+
|
12
|
+
# @param content [String] data we want to send
|
13
|
+
# @param file_name [String] name under which we want to send it
|
14
|
+
def initialize(content, file_name)
|
15
|
+
@content = content
|
16
|
+
@file_name = file_name
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -6,10 +6,10 @@ module Karafka
|
|
6
6
|
module Controllers
|
7
7
|
# Response related components
|
8
8
|
module Responses
|
9
|
-
# Response data object. It is used to transfer attributes assigned in controllers
|
10
|
-
# views
|
9
|
+
# Response render data object. It is used to transfer attributes assigned in controllers
|
10
|
+
# into views
|
11
11
|
# It acts as a simplification / transport layer for assigned attributes
|
12
|
-
class
|
12
|
+
class Render
|
13
13
|
attr_reader :path, :attributes
|
14
14
|
|
15
15
|
# @param path [String] render path
|
@@ -6,11 +6,20 @@ module Karafka
|
|
6
6
|
module Controllers
|
7
7
|
# Routing presentation controller
|
8
8
|
class Routing < Base
|
9
|
+
self.sortable_attributes = %w[
|
10
|
+
name
|
11
|
+
active?
|
12
|
+
].freeze
|
13
|
+
|
9
14
|
# Routing list
|
10
15
|
def index
|
11
16
|
@routes = Karafka::App.routes
|
12
17
|
|
13
|
-
|
18
|
+
@routes.each do |consumer_group|
|
19
|
+
refine(consumer_group.topics)
|
20
|
+
end
|
21
|
+
|
22
|
+
render
|
14
23
|
end
|
15
24
|
|
16
25
|
# Given route details
|
@@ -21,7 +30,7 @@ module Karafka
|
|
21
30
|
|
22
31
|
@topic || raise(::Karafka::Web::Errors::Ui::NotFoundError, topic_id)
|
23
32
|
|
24
|
-
|
33
|
+
render
|
25
34
|
end
|
26
35
|
end
|
27
36
|
end
|
@@ -126,6 +126,40 @@ module Karafka
|
|
126
126
|
%(<span title="#{stamp}">#{time}</span>)
|
127
127
|
end
|
128
128
|
|
129
|
+
# @param state [String] poll state
|
130
|
+
# @param state_ch [Integer] time until next change of the poll state
|
131
|
+
# (from paused to active)
|
132
|
+
# @return [String] span tag with label and title with change time if present
|
133
|
+
def poll_state_with_change_time_label(state, state_ch)
|
134
|
+
year_in_seconds = 131_556_926
|
135
|
+
state_ch_in_seconds = state_ch / 1_000.0
|
136
|
+
|
137
|
+
# If state is active, there is no date of change
|
138
|
+
if state == 'active'
|
139
|
+
%(
|
140
|
+
<span class="badge #{kafka_state_bg(state)} mt-1 mb-1">#{state}</span>
|
141
|
+
)
|
142
|
+
elsif state_ch_in_seconds > year_in_seconds
|
143
|
+
%(
|
144
|
+
<span
|
145
|
+
class="badge #{kafka_state_bg(state)} mt-1 mb-1"
|
146
|
+
title="until manual resume"
|
147
|
+
>
|
148
|
+
#{state}
|
149
|
+
</span>
|
150
|
+
)
|
151
|
+
else
|
152
|
+
%(
|
153
|
+
<span
|
154
|
+
class="badge #{kafka_state_bg(state)} time-title mt-1 mb-1"
|
155
|
+
title="#{Time.now + state_ch_in_seconds}"
|
156
|
+
>
|
157
|
+
#{state}
|
158
|
+
</span>
|
159
|
+
)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
129
163
|
# @param lag [Integer] lag
|
130
164
|
# @return [String] lag if correct or `N/A` with labeled explanation
|
131
165
|
# @see #offset_with_label
|
@@ -212,6 +246,42 @@ module Karafka
|
|
212
246
|
|
213
247
|
result
|
214
248
|
end
|
249
|
+
|
250
|
+
# @param name [String] link value
|
251
|
+
# @param attribute [Symbol, nil] sorting attribute or nil if we provide only symbol name
|
252
|
+
# @param rev [Boolean] when set to true, arrows will be in the reverse position. This is
|
253
|
+
# used when the description in the link is reverse to data we sort. For example we have
|
254
|
+
# order on when processes were started and we display "x hours" ago but we sort on
|
255
|
+
# their age, meaning that it looks like it is the other way around. This flag allows
|
256
|
+
# us to reverse just he arrow making it look consistent with the presented data order
|
257
|
+
# @return [String] html link for sorting with arrow when attribute sort enabled
|
258
|
+
def sort_link(name, attribute = nil, rev: false)
|
259
|
+
unless attribute
|
260
|
+
attribute = name
|
261
|
+
name = attribute.to_s.tr('_', ' ').capitalize
|
262
|
+
end
|
263
|
+
|
264
|
+
arrow_both = '⇕'
|
265
|
+
arrow_down = '▾'
|
266
|
+
arrow_up = '▴'
|
267
|
+
|
268
|
+
desc = "#{attribute} desc"
|
269
|
+
asc = "#{attribute} asc"
|
270
|
+
path = current_path(sort: desc)
|
271
|
+
full_name = "#{name} #{arrow_both}"
|
272
|
+
|
273
|
+
if params.sort == desc
|
274
|
+
path = current_path(sort: asc)
|
275
|
+
full_name = "#{name} #{rev ? arrow_up : arrow_down}"
|
276
|
+
end
|
277
|
+
|
278
|
+
if params.sort == asc
|
279
|
+
path = current_path(sort: desc)
|
280
|
+
full_name = "#{name} #{rev ? arrow_down : arrow_up}"
|
281
|
+
end
|
282
|
+
|
283
|
+
"<a class=\"sort\" href=\"#{path}\">#{full_name}</a>"
|
284
|
+
end
|
215
285
|
end
|
216
286
|
end
|
217
287
|
end
|
@@ -17,12 +17,17 @@ module Karafka
|
|
17
17
|
class HashProxy
|
18
18
|
extend Forwardable
|
19
19
|
|
20
|
-
def_delegators :@hash, :[], :[]=, :key?, :each, :find
|
20
|
+
def_delegators :@hash, :[], :[]=, :key?, :each, :find, :values, :keys, :select
|
21
21
|
|
22
22
|
# @param hash [Hash] hash we want to convert to a proxy
|
23
23
|
def initialize(hash)
|
24
24
|
@hash = hash
|
25
|
-
|
25
|
+
# Nodes we already visited in the context of a given attribute lookup
|
26
|
+
# We cache them not to look for them over and over again if they are used more than
|
27
|
+
# once
|
28
|
+
@visited = Hash.new { |h, k| h[k] = {} }
|
29
|
+
# Methods invocations cache
|
30
|
+
@results = {}
|
26
31
|
end
|
27
32
|
|
28
33
|
# @return [Original hash]
|
@@ -34,22 +39,32 @@ module Karafka
|
|
34
39
|
# @param args [Object] all the args of the method
|
35
40
|
# @param block [Proc] block for the method
|
36
41
|
def method_missing(method_name, *args, &block)
|
37
|
-
|
42
|
+
method_name = method_name.to_sym
|
38
43
|
|
39
|
-
|
44
|
+
return super unless args.empty? && block.nil?
|
45
|
+
return @results[method_name] if @results.key?(method_name)
|
40
46
|
|
41
|
-
result = deep_find(@hash, method_name
|
47
|
+
result = deep_find(@hash, method_name)
|
42
48
|
|
43
|
-
|
49
|
+
return super if result.nil?
|
44
50
|
|
45
|
-
|
51
|
+
@results[method_name] = result
|
46
52
|
end
|
47
53
|
|
48
54
|
# @param method_name [String] method name
|
49
55
|
# @param include_private [Boolean]
|
50
56
|
def respond_to_missing?(method_name, include_private = false)
|
51
|
-
|
52
|
-
|
57
|
+
method_name = method_name.to_sym
|
58
|
+
|
59
|
+
return true if @results.key?(method_name)
|
60
|
+
|
61
|
+
result = deep_find(@hash, method_name)
|
62
|
+
|
63
|
+
return super if result.nil?
|
64
|
+
|
65
|
+
@results[method_name] = result
|
66
|
+
|
67
|
+
true
|
53
68
|
end
|
54
69
|
|
55
70
|
private
|
@@ -59,16 +74,16 @@ module Karafka
|
|
59
74
|
def deep_find(obj, key)
|
60
75
|
# Prevent circular dependency lookups by making sure we do not check the same object
|
61
76
|
# multiple times
|
62
|
-
return nil if @visited.
|
77
|
+
return nil if @visited[key].key?(obj)
|
63
78
|
|
64
|
-
@visited
|
79
|
+
@visited[key][obj] = nil
|
65
80
|
|
66
81
|
if obj.respond_to?(:key?) && obj.key?(key)
|
67
82
|
obj[key]
|
68
83
|
elsif obj.respond_to?(:each)
|
69
|
-
|
70
|
-
obj.find { |*a|
|
71
|
-
|
84
|
+
result = nil
|
85
|
+
obj.find { |*a| result = deep_find(a.last, key) }
|
86
|
+
result
|
72
87
|
end
|
73
88
|
end
|
74
89
|
end
|