karafka-web 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +17 -1
  4. data/Gemfile +1 -0
  5. data/Gemfile.lock +23 -11
  6. data/bin/karafka-web +1 -26
  7. data/karafka-web.gemspec +2 -2
  8. data/lib/karafka/web/cli.rb +80 -0
  9. data/lib/karafka/web/config.rb +5 -0
  10. data/lib/karafka/web/installer.rb +65 -29
  11. data/lib/karafka/web/processing/consumer.rb +2 -1
  12. data/lib/karafka/web/tracking/consumers/contracts/job.rb +1 -0
  13. data/lib/karafka/web/tracking/consumers/contracts/report.rb +1 -0
  14. data/lib/karafka/web/tracking/consumers/listeners/errors.rb +1 -1
  15. data/lib/karafka/web/tracking/consumers/listeners/processing.rb +2 -1
  16. data/lib/karafka/web/tracking/consumers/sampler.rb +9 -3
  17. data/lib/karafka/web/tracking/reporter.rb +2 -1
  18. data/lib/karafka/web/ui/app.rb +5 -0
  19. data/lib/karafka/web/ui/controllers/status.rb +23 -0
  20. data/lib/karafka/web/ui/models/process.rb +7 -0
  21. data/lib/karafka/web/ui/models/status.rb +169 -0
  22. data/lib/karafka/web/ui/pro/app.rb +5 -0
  23. data/lib/karafka/web/ui/pro/controllers/status.rb +26 -0
  24. data/lib/karafka/web/ui/pro/views/consumers/_consumer.erb +15 -7
  25. data/lib/karafka/web/ui/pro/views/consumers/consumer/_job.erb +4 -0
  26. data/lib/karafka/web/ui/pro/views/consumers/consumer/_metrics.erb +10 -0
  27. data/lib/karafka/web/ui/pro/views/jobs/_job.erb +4 -0
  28. data/lib/karafka/web/ui/pro/views/shared/_navigation.erb +5 -0
  29. data/lib/karafka/web/ui/views/consumers/_consumer.erb +15 -7
  30. data/lib/karafka/web/ui/views/jobs/_job.erb +4 -0
  31. data/lib/karafka/web/ui/views/shared/_navigation.erb +5 -0
  32. data/lib/karafka/web/ui/views/shared/exceptions/not_found.erb +2 -0
  33. data/lib/karafka/web/ui/views/status/_breadcrumbs.erb +5 -0
  34. data/lib/karafka/web/ui/views/status/_failure.erb +14 -0
  35. data/lib/karafka/web/ui/views/status/_halted.erb +11 -0
  36. data/lib/karafka/web/ui/views/status/_success.erb +11 -0
  37. data/lib/karafka/web/ui/views/status/failures/_connection.erb +7 -0
  38. data/lib/karafka/web/ui/views/status/failures/_initial_state.erb +20 -0
  39. data/lib/karafka/web/ui/views/status/failures/_live_reporting.erb +7 -0
  40. data/lib/karafka/web/ui/views/status/failures/_partitions.erb +19 -0
  41. data/lib/karafka/web/ui/views/status/failures/_state_calculation.erb +8 -0
  42. data/lib/karafka/web/ui/views/status/failures/_topics.erb +20 -0
  43. data/lib/karafka/web/ui/views/status/show.erb +94 -0
  44. data/lib/karafka/web/version.rb +1 -1
  45. data/lib/karafka/web.rb +0 -6
  46. data.tar.gz.sig +0 -0
  47. metadata +21 -6
  48. metadata.gz.sig +0 -0
@@ -0,0 +1,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Web
5
+ module Ui
6
+ module Models
7
+ # Model that represents the general status of the Web UI.
8
+ # We use this data to display a status page that helps with debugging on what is missing
9
+ # in the overall setup of the Web UI.
10
+ #
11
+ # People have various problems like too many partitions, not created topics, etc. and this
12
+ # data and view aims to help them with understanding the current status of the setup
13
+ class Status
14
+ # Status of a single step of setup
15
+ Step = Struct.new(:status, :details) do
16
+ # @return [Boolean] is the given step successfully configured and working
17
+ def success?
18
+ status == :success
19
+ end
20
+
21
+ # @return [String] stringified status
22
+ def to_s
23
+ status.to_s
24
+ end
25
+ end
26
+
27
+ # Initializes the status object and tries to connect to Kafka
28
+ def initialize
29
+ connect
30
+ end
31
+
32
+ # @return [Status::Step] were we able to connect to Kafka or not
33
+ def connection
34
+ Step.new(
35
+ @connected ? :success : :failure,
36
+ nil
37
+ )
38
+ end
39
+
40
+ # @return [Status::Step] do all the needed topics exist
41
+ def topics
42
+ if connection.success?
43
+ details = topics_details
44
+ status = details.all? { |_, detail| detail[:present] } ? :success : :failure
45
+ else
46
+ status = :halted
47
+ details = {}
48
+ end
49
+
50
+ Step.new(
51
+ status,
52
+ details
53
+ )
54
+ end
55
+
56
+ # @return [Status::Step] do we have all topics with expected number of partitions
57
+ def partitions
58
+ if topics.success?
59
+ status = :success
60
+ status = :failure if topics_details[topics_consumers_states][:partitions] != 1
61
+ status = :failure if topics_details[topics_consumers_reports][:partitions] != 1
62
+ details = topics_details
63
+ else
64
+ status = :halted
65
+ details = {}
66
+ end
67
+
68
+ Step.new(
69
+ status,
70
+ details
71
+ )
72
+ end
73
+
74
+ # @return [Status::Step] Is the initial state present in the setup or not
75
+ def initial_state
76
+ if partitions.success?
77
+ @current_state ||= Models::State.current
78
+ status = @current_state ? :success : :failure
79
+ else
80
+ status = :halted
81
+ end
82
+
83
+ Step.new(
84
+ status,
85
+ nil
86
+ )
87
+ end
88
+
89
+ # @return [Status::Step] Is there at least one active karafka server reporting to the
90
+ # Web UI
91
+ def live_reporting
92
+ if initial_state.success?
93
+ @processes ||= Models::Processes.active(@current_state)
94
+ status = @processes.empty? ? :failure : :success
95
+ else
96
+ status = :halted
97
+ end
98
+
99
+ Step.new(
100
+ status,
101
+ nil
102
+ )
103
+ end
104
+
105
+ # @return [Status::Step] is there a subscription to our reports topic that is being
106
+ # consumed actively.
107
+ def state_calculation
108
+ if live_reporting.success?
109
+ @subscriptions ||= Models::Health.current(@current_state).values.flat_map(&:keys)
110
+ status = @subscriptions.include?(topics_consumers_reports) ? :success : :failure
111
+ else
112
+ status = :halted
113
+ end
114
+
115
+ Step.new(
116
+ status,
117
+ nil
118
+ )
119
+ end
120
+
121
+ private
122
+
123
+ # @return [String] consumers states topic name
124
+ def topics_consumers_states
125
+ ::Karafka::Web.config.topics.consumers.states.to_s
126
+ end
127
+
128
+ # @return [String] consumers reports topic name
129
+ def topics_consumers_reports
130
+ ::Karafka::Web.config.topics.consumers.reports.to_s
131
+ end
132
+
133
+ # @return [String] errors topic name
134
+ def topics_errors
135
+ ::Karafka::Web.config.topics.errors
136
+ end
137
+
138
+ # @return [Hash] hash with topics with which we work details (even if don't exist)
139
+ def topics_details
140
+ topics = {
141
+ topics_consumers_states => { present: false, partitions: 0 },
142
+ topics_consumers_reports => { present: false, partitions: 0 },
143
+ topics_errors => { present: false, partitions: 0 }
144
+ }
145
+
146
+ @cluster_info.topics.each do |topic|
147
+ name = topic[:topic_name]
148
+
149
+ next unless topics.key?(name)
150
+
151
+ topics[name][:present] = true
152
+ topics[name][:partitions] = topic[:partition_count]
153
+ end
154
+
155
+ topics
156
+ end
157
+
158
+ # Tries connecting with the cluster and sets the connection state
159
+ def connect
160
+ @cluster_info = ::Karafka::Admin.cluster_info
161
+ @connected = true
162
+ rescue ::Rdkafka::RdkafkaError
163
+ @connected = false
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
@@ -112,6 +112,11 @@ module Karafka
112
112
  controller = Controllers::Dlq.new(params)
113
113
  render_response controller.index
114
114
  end
115
+
116
+ r.get 'status' do
117
+ controller = Controllers::Status.new(params)
118
+ render_response controller.show
119
+ end
115
120
  end
116
121
  end
117
122
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Web
16
+ module Ui
17
+ module Pro
18
+ module Controllers
19
+ # Status details - same as in OSS
20
+ class Status < Ui::Controllers::Status
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -8,16 +8,24 @@
8
8
  <%= process.name %>
9
9
  </a>
10
10
 
11
- <br/>
11
+ <p class="mt-0 mb-1">
12
+ <% process.consumer_groups.each do |consumer_group| %>
13
+ <% consumer_group.topics.each do |topic| %>
14
+ <span class="badge bg-secondary badge-topic" title="Consumer group: <%= consumer_group.id %>">
15
+ <%= topic.name %>:
16
+ <%= topic.partitions.map(&:id).join(',') %>
17
+ </span>
18
+ <% end %>
19
+ <% end %>
20
+ </p>
12
21
 
13
- <% process.consumer_groups.each do |consumer_group| %>
14
- <% consumer_group.topics.each do |topic| %>
15
- <span class="badge bg-secondary badge-topic" title="Consumer group: <%= consumer_group.id %>">
16
- <%= topic.name %>:
17
- <%= topic.partitions.map(&:id).join(',') %>
22
+ <p class="mt-0 mb-1">
23
+ <% process.tags.each do |tag| %>
24
+ <span class="badge bg-info badge-topic">
25
+ <%= tag %>
18
26
  </span>
19
27
  <% end %>
20
- <% end %>
28
+ </p>
21
29
  </td>
22
30
 
23
31
  <td>
@@ -1,6 +1,10 @@
1
1
  <tr>
2
2
  <td>
3
3
  <code><%= job.consumer %></code>
4
+
5
+ <% job.tags.each do |tag| %>
6
+ <span class="badge bg-info"><%= tag %></span>
7
+ <% end %>
4
8
  </td>
5
9
  <td>
6
10
  <span class="badge bg-secondary badge-topic" title="Consumer group: <%= job.consumer_group %>">
@@ -27,6 +27,16 @@
27
27
  </li>
28
28
  </ul>
29
29
  </p>
30
+
31
+ <% unless @process.tags.empty? %>
32
+ <p class="mb-0 text-end">
33
+ <% @process.tags.each do |tag| %>
34
+ <span class="badge bg-info badge-topic">
35
+ <%= tag %>
36
+ </span>
37
+ <% end %>
38
+ </p>
39
+ <% end %>
30
40
  </div>
31
41
  </div>
32
42
  <div class="card">
@@ -12,6 +12,10 @@
12
12
  </td>
13
13
  <td>
14
14
  <code><%= job.consumer %></code>
15
+
16
+ <% job.tags.each do |tag| %>
17
+ <span class="badge bg-info"><%= tag %></span>
18
+ <% end %>
15
19
  </td>
16
20
  <td>
17
21
  <code>#<%= job.type %></code>
@@ -45,6 +45,11 @@
45
45
  Cluster
46
46
  </a>
47
47
  </li>
48
+ <li class="nav-item ms-3">
49
+ <a class="nav-link <%= nav_class(start_with: '/status') %>" href="<%= root_path('Status') %>">
50
+ Status
51
+ </a>
52
+ </li>
48
53
  </ul>
49
54
  </div>
50
55
 
@@ -8,16 +8,24 @@
8
8
  <%= process.name %>
9
9
  </a>
10
10
 
11
- <br/>
11
+ <p class="mt-0 mb-1">
12
+ <% process.consumer_groups.each do |consumer_group| %>
13
+ <% consumer_group.topics.each do |topic| %>
14
+ <span class="badge bg-secondary badge-topic" title="Consumer group: <%= consumer_group.id %>">
15
+ <%= topic.name %>:
16
+ <%= topic.partitions.map(&:id).join(',') %>
17
+ </span>
18
+ <% end %>
19
+ <% end %>
20
+ </p>
12
21
 
13
- <% process.consumer_groups.each do |consumer_group| %>
14
- <% consumer_group.topics.each do |topic| %>
15
- <span class="badge bg-secondary badge-topic" title="Consumer group: <%= consumer_group.id %>">
16
- <%= topic.name %>:
17
- <%= topic.partitions.map(&:id).join(',') %>
22
+ <p class="mt-0 mb-1">
23
+ <% process.tags.each do |tag| %>
24
+ <span class="badge bg-info badge-topic">
25
+ <%= tag %>
18
26
  </span>
19
27
  <% end %>
20
- <% end %>
28
+ </p>
21
29
  </td>
22
30
 
23
31
  <td>
@@ -12,6 +12,10 @@
12
12
  </td>
13
13
  <td>
14
14
  <code><%= job.consumer %></code>
15
+
16
+ <% job.tags.each do |tag| %>
17
+ <span class="badge bg-info"><%= tag %></span>
18
+ <% end %>
15
19
  </td>
16
20
  <td>
17
21
  <code>#<%= job.type %></code>
@@ -45,6 +45,11 @@
45
45
  Cluster
46
46
  </a>
47
47
  </li>
48
+ <li class="nav-item ms-3">
49
+ <a class="nav-link <%= nav_class(start_with: '/status') %>" href="<%= root_path('status') %>">
50
+ Status
51
+ </a>
52
+ </li>
48
53
  </ul>
49
54
  </div>
50
55
 
@@ -20,6 +20,7 @@
20
20
  </p>
21
21
 
22
22
  <ul class="mb-5 text-start">
23
+ <li>You have visited the <a href="<%= root_path('status') %>">Status</a> page to troubleshoot any potential issues</li>
23
24
  <li>All the topics required by Karafka Web exist</li>
24
25
  <li>You have used <code>bundle exec karafka-web install</code> to initialize the Web UI</li>
25
26
  <li>You have a working connection with your Kafka cluster</li>
@@ -32,6 +33,7 @@
32
33
 
33
34
  <p>
34
35
  <a href="<%= root_path %>" class="btn btn-primary">Go Home</a>
36
+ <a href="<%= root_path('status') %>" class="btn btn-success">Status page</a>
35
37
  </p>
36
38
  </div>
37
39
  </div>
@@ -0,0 +1,5 @@
1
+ <li class="breadcrumb-item">
2
+ <a href="<%= root_path('status') %>">
3
+ Web UI status details
4
+ </a>
5
+ </li>
@@ -0,0 +1,14 @@
1
+ <div class="card border-danger mb-3">
2
+ <div class="card-header text-bg-danger">
3
+ <span>
4
+ <%= title %>
5
+ </span>
6
+
7
+ <span class="float-end">
8
+ <span class="badge text-bg-light">Failure</span>
9
+ </span>
10
+ </div>
11
+ <div class="card-body">
12
+ <%== description %>
13
+ </div>
14
+ </div>
@@ -0,0 +1,11 @@
1
+ <div class="card text-bg-secondary mb-3">
2
+ <div class="card-header">
3
+ <span>
4
+ <%= title %>
5
+ </span>
6
+
7
+ <span class="float-end">
8
+ <span class="badge text-bg-light">Halted</span>
9
+ </span>
10
+ </div>
11
+ </div>
@@ -0,0 +1,11 @@
1
+ <div class="card text-bg-success mb-3">
2
+ <div class="card-header">
3
+ <span>
4
+ <%= title %>
5
+ </span>
6
+
7
+ <span class="float-end">
8
+ <span class="badge text-bg-light">Successful</span>
9
+ </span>
10
+ </div>
11
+ </div>
@@ -0,0 +1,7 @@
1
+ <p>
2
+ Web UI was not able to establish a connection with the Kafka cluster.
3
+ </p>
4
+
5
+ <p class="mb-0">
6
+ Please make sure that Web UI can reach Kafka.
7
+ </p>
@@ -0,0 +1,20 @@
1
+ <p>
2
+ The initial state for the Web UI was not created.
3
+ </p>
4
+
5
+ <p>
6
+ It means that the <code>bundle exec karafka-web install</code> was not executed or failed.
7
+ </p>
8
+
9
+ <p>
10
+ To fix this, you can either:
11
+ </p>
12
+
13
+ <ul>
14
+ <li>Run <code>bundle exec karafka-web install</code></li>
15
+ <li>Run at least one Karafka consumer process</li>
16
+ </ul>
17
+
18
+ <p class="mb-0">
19
+ Any of the above, when successful, will bootstrap the initial state.
20
+ </p>
@@ -0,0 +1,7 @@
1
+ <p>
2
+ There are no Karafka consumer processes actively reporting to the Web UI.
3
+ </p>
4
+
5
+ <p class="mb-0">
6
+ If you are sure you are running at least one <code>karafka server</code> instance, please make sure that it can report to the <code><%= Karafka::Web.config.topics.consumers.reports %></code> topic.
7
+ </p>
@@ -0,0 +1,19 @@
1
+ <p>
2
+ Both
3
+ <code><%= Karafka::Web.config.topics.consumers.states %></code>
4
+ and
5
+ <code><%= Karafka::Web.config.topics.consumers.reports %></code>
6
+ topics need to be configured with <strong>exactly</strong> one partition.
7
+ </p>
8
+
9
+ <p>
10
+ Your current setup contains the following:
11
+ </p>
12
+
13
+ <ul class="mb-0">
14
+ <% details.each do |name, details| %>
15
+ <li>
16
+ <code><%= name %> </code> with <code><%= details[:partitions] %> </code> partitions.
17
+ </li>
18
+ <% end %>
19
+ </ul>
@@ -0,0 +1,8 @@
1
+ <p>
2
+ None of the <code>karafka server</code> processes is subscribed to handle the
3
+ <code><%= Karafka::Web.config.topics.consumers.reports %></code> topic data aggregations.
4
+ </p>
5
+
6
+ <p class="mb-0">
7
+ If you are limiting the topics you consume using the <a href="https://karafka.io/docs/CLI/#limiting-consumer-groups-used-per-process" target="_blank">limiting API</a>, please include the Karafka Web consumer group and the <code><%= Karafka::Web.config.topics.consumers.reports %></code> topic.
8
+ </p>
@@ -0,0 +1,20 @@
1
+ <p>
2
+ Web UI requires the following topics to exist:
3
+ </p>
4
+
5
+ <ul>
6
+ <% details.each do |name, details| %>
7
+ <li>
8
+ <code><%= name %></code> - <%= details[:present] ? 'Exists' : 'Missing' %>
9
+ </li>
10
+ <% end %>
11
+ </ul>
12
+
13
+ <p>
14
+ Please ensure all those topics exist and are accessible to your Karafka user.
15
+ </p>
16
+
17
+ <p class="mb-0">
18
+ You can read more about their setup and configuration
19
+ <a href="https://karafka.io/docs/Web-UI-Getting-Started/" target="_blank">here</a>.
20
+ </p>
@@ -0,0 +1,94 @@
1
+ <%== view_title('Web UI status details') %>
2
+
3
+ <div class="container mb-5">
4
+ <div class="row">
5
+ <div class="col-lg-10 offset-md-1">
6
+
7
+ <%==
8
+ partial(
9
+ "status/#{@status.connection.to_s}",
10
+ locals: {
11
+ title: 'Connection to Kafka',
12
+ description: partial('status/failures/connection')
13
+ }
14
+ )
15
+ %>
16
+
17
+ <%==
18
+ partial(
19
+ "status/#{@status.topics.to_s}",
20
+ locals: {
21
+ title: 'Topics presence',
22
+ description: partial(
23
+ 'status/failures/topics',
24
+ locals: {
25
+ details: @status.topics.details
26
+ }
27
+ )
28
+ }
29
+ )
30
+ %>
31
+
32
+ <%==
33
+ partial(
34
+ "status/#{@status.partitions.to_s}",
35
+ locals: {
36
+ title: 'Partitions count',
37
+ description: partial(
38
+ 'status/failures/partitions',
39
+ locals: {
40
+ details: @status.partitions.details
41
+ }
42
+ )
43
+ }
44
+ )
45
+ %>
46
+
47
+ <%==
48
+ partial(
49
+ "status/#{@status.initial_state.to_s}",
50
+ locals: {
51
+ title: 'Initial state presence',
52
+ description: partial(
53
+ 'status/failures/initial_state',
54
+ locals: {
55
+ details: @status.initial_state.details
56
+ }
57
+ )
58
+ }
59
+ )
60
+ %>
61
+
62
+ <%==
63
+ partial(
64
+ "status/#{@status.live_reporting.to_s}",
65
+ locals: {
66
+ title: 'Live reporting',
67
+ description: partial(
68
+ 'status/failures/live_reporting',
69
+ locals: {
70
+ details: @status.live_reporting.details
71
+ }
72
+ )
73
+ }
74
+ )
75
+ %>
76
+
77
+ <%==
78
+ partial(
79
+ "status/#{@status.state_calculation.to_s}",
80
+ locals: {
81
+ title: 'State calculation subscription',
82
+ description: partial(
83
+ 'status/failures/state_calculation',
84
+ locals: {
85
+ details: @status.state_calculation.details
86
+ }
87
+ )
88
+ }
89
+ )
90
+ %>
91
+
92
+ </div>
93
+ </div>
94
+ </div>
@@ -3,6 +3,6 @@
3
3
  module Karafka
4
4
  module Web
5
5
  # Current gem version
6
- VERSION = '0.1.2'
6
+ VERSION = '0.2.0'
7
7
  end
8
8
  end
data/lib/karafka/web.rb CHANGED
@@ -27,12 +27,6 @@ module Karafka
27
27
  Config.config
28
28
  end
29
29
 
30
- # Creates all the needed topics for the admin UI to work and populates initial (empty) set
31
- # of data, so the UI will work even when no Karafka servers are started
32
- def bootstrap!
33
- Installer.new.bootstrap!
34
- end
35
-
36
30
  # Activates all the needed routing and sets up listener, etc
37
31
  # This needs to run **after** the optional configuration of the web component
38
32
  def enable!
data.tar.gz.sig CHANGED
Binary file