kafka_command 0.0.1

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.
Files changed (159) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +179 -0
  3. data/.env +1 -0
  4. data/.env.test +1 -0
  5. data/.gitignore +41 -0
  6. data/.rspec +1 -0
  7. data/.rubocop.yml +12 -0
  8. data/.ruby-version +1 -0
  9. data/Gemfile +17 -0
  10. data/Gemfile.lock +194 -0
  11. data/LICENSE +21 -0
  12. data/README.md +138 -0
  13. data/Rakefile +34 -0
  14. data/app/assets/config/manifest.js +3 -0
  15. data/app/assets/images/.keep +0 -0
  16. data/app/assets/images/kafka_command/cluster_view.png +0 -0
  17. data/app/assets/images/kafka_command/kafka.png +0 -0
  18. data/app/assets/images/kafka_command/topic_view.png +0 -0
  19. data/app/assets/javascripts/kafka_command/application.js +14 -0
  20. data/app/assets/stylesheets/kafka_command/application.css +27 -0
  21. data/app/assets/stylesheets/kafka_command/clusters.css +8 -0
  22. data/app/assets/stylesheets/kafka_command/topics.css +3 -0
  23. data/app/channels/application_cable/channel.rb +6 -0
  24. data/app/channels/application_cable/connection.rb +6 -0
  25. data/app/controllers/kafka_command/application_controller.rb +96 -0
  26. data/app/controllers/kafka_command/brokers_controller.rb +26 -0
  27. data/app/controllers/kafka_command/clusters_controller.rb +46 -0
  28. data/app/controllers/kafka_command/consumer_groups_controller.rb +44 -0
  29. data/app/controllers/kafka_command/topics_controller.rb +187 -0
  30. data/app/helpers/kafka_command/application_helper.rb +29 -0
  31. data/app/helpers/kafka_command/consumer_group_helper.rb +13 -0
  32. data/app/jobs/application_job.rb +6 -0
  33. data/app/mailers/application_mailer.rb +8 -0
  34. data/app/models/kafka_command/broker.rb +47 -0
  35. data/app/models/kafka_command/client.rb +102 -0
  36. data/app/models/kafka_command/cluster.rb +172 -0
  37. data/app/models/kafka_command/consumer_group.rb +142 -0
  38. data/app/models/kafka_command/consumer_group_partition.rb +23 -0
  39. data/app/models/kafka_command/group_member.rb +18 -0
  40. data/app/models/kafka_command/partition.rb +36 -0
  41. data/app/models/kafka_command/topic.rb +153 -0
  42. data/app/views/kafka_command/brokers/index.html.erb +38 -0
  43. data/app/views/kafka_command/clusters/_tabs.html.erb +9 -0
  44. data/app/views/kafka_command/clusters/index.html.erb +54 -0
  45. data/app/views/kafka_command/clusters/new.html.erb +115 -0
  46. data/app/views/kafka_command/configuration_error.html.erb +1 -0
  47. data/app/views/kafka_command/consumer_groups/index.html.erb +32 -0
  48. data/app/views/kafka_command/consumer_groups/show.html.erb +115 -0
  49. data/app/views/kafka_command/shared/_alert.html.erb +13 -0
  50. data/app/views/kafka_command/shared/_search_bar.html.erb +31 -0
  51. data/app/views/kafka_command/shared/_title.html.erb +6 -0
  52. data/app/views/kafka_command/topics/_form_fields.html.erb +49 -0
  53. data/app/views/kafka_command/topics/edit.html.erb +17 -0
  54. data/app/views/kafka_command/topics/index.html.erb +46 -0
  55. data/app/views/kafka_command/topics/new.html.erb +36 -0
  56. data/app/views/kafka_command/topics/show.html.erb +126 -0
  57. data/app/views/layouts/kafka_command/application.html.erb +50 -0
  58. data/bin/rails +16 -0
  59. data/config/initializers/kafka.rb +13 -0
  60. data/config/initializers/kafka_command.rb +11 -0
  61. data/config/routes.rb +11 -0
  62. data/docker-compose.yml +18 -0
  63. data/kafka_command.gemspec +27 -0
  64. data/lib/assets/.keep +0 -0
  65. data/lib/core_extensions/kafka/broker/attr_readers.rb +11 -0
  66. data/lib/core_extensions/kafka/broker_pool/attr_readers.rb +11 -0
  67. data/lib/core_extensions/kafka/client/attr_readers.rb +11 -0
  68. data/lib/core_extensions/kafka/cluster/attr_readers.rb +11 -0
  69. data/lib/core_extensions/kafka/protocol/metadata_response/partition_metadata/attr_readers.rb +15 -0
  70. data/lib/kafka_command/configuration.rb +150 -0
  71. data/lib/kafka_command/engine.rb +11 -0
  72. data/lib/kafka_command/errors.rb +6 -0
  73. data/lib/kafka_command/version.rb +5 -0
  74. data/lib/kafka_command.rb +13 -0
  75. data/lib/tasks/.keep +0 -0
  76. data/spec/dummy/Rakefile +6 -0
  77. data/spec/dummy/app/assets/config/manifest.js +4 -0
  78. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  79. data/spec/dummy/app/assets/javascripts/cable.js +13 -0
  80. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  81. data/spec/dummy/app/channels/application_cable/channel.rb +4 -0
  82. data/spec/dummy/app/channels/application_cable/connection.rb +4 -0
  83. data/spec/dummy/app/controllers/application_controller.rb +2 -0
  84. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  85. data/spec/dummy/app/jobs/application_job.rb +2 -0
  86. data/spec/dummy/app/mailers/application_mailer.rb +4 -0
  87. data/spec/dummy/app/models/application_record.rb +3 -0
  88. data/spec/dummy/app/views/layouts/application.html.erb +15 -0
  89. data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
  90. data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
  91. data/spec/dummy/bin/bundle +3 -0
  92. data/spec/dummy/bin/rails +4 -0
  93. data/spec/dummy/bin/rake +4 -0
  94. data/spec/dummy/bin/setup +36 -0
  95. data/spec/dummy/bin/update +31 -0
  96. data/spec/dummy/bin/yarn +11 -0
  97. data/spec/dummy/config/application.rb +19 -0
  98. data/spec/dummy/config/boot.rb +5 -0
  99. data/spec/dummy/config/cable.yml +10 -0
  100. data/spec/dummy/config/database.yml +25 -0
  101. data/spec/dummy/config/environment.rb +5 -0
  102. data/spec/dummy/config/environments/development.rb +61 -0
  103. data/spec/dummy/config/environments/production.rb +94 -0
  104. data/spec/dummy/config/environments/test.rb +46 -0
  105. data/spec/dummy/config/initializers/application_controller_renderer.rb +8 -0
  106. data/spec/dummy/config/initializers/assets.rb +14 -0
  107. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  108. data/spec/dummy/config/initializers/content_security_policy.rb +25 -0
  109. data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
  110. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  111. data/spec/dummy/config/initializers/inflections.rb +16 -0
  112. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  113. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  114. data/spec/dummy/config/kafka_command.yml +18 -0
  115. data/spec/dummy/config/locales/en.yml +33 -0
  116. data/spec/dummy/config/puma.rb +34 -0
  117. data/spec/dummy/config/routes.rb +3 -0
  118. data/spec/dummy/config/spring.rb +6 -0
  119. data/spec/dummy/config/ssl/test_ca_cert +1 -0
  120. data/spec/dummy/config/ssl/test_client_cert +1 -0
  121. data/spec/dummy/config/ssl/test_client_cert_key +1 -0
  122. data/spec/dummy/config/storage.yml +34 -0
  123. data/spec/dummy/config.ru +5 -0
  124. data/spec/dummy/db/schema.rb +42 -0
  125. data/spec/dummy/db/test.sqlite3 +0 -0
  126. data/spec/dummy/log/development.log +0 -0
  127. data/spec/dummy/log/hey.log +0 -0
  128. data/spec/dummy/log/test.log +2227 -0
  129. data/spec/dummy/package.json +5 -0
  130. data/spec/dummy/public/404.html +67 -0
  131. data/spec/dummy/public/422.html +67 -0
  132. data/spec/dummy/public/500.html +66 -0
  133. data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
  134. data/spec/dummy/public/apple-touch-icon.png +0 -0
  135. data/spec/dummy/public/favicon.ico +0 -0
  136. data/spec/examples.txt +165 -0
  137. data/spec/fast_helper.rb +20 -0
  138. data/spec/fixtures/files/kafka_command_sasl.yml +10 -0
  139. data/spec/fixtures/files/kafka_command_ssl.yml +10 -0
  140. data/spec/fixtures/files/kafka_command_ssl_file_paths.yml +11 -0
  141. data/spec/fixtures/files/kafka_command_staging.yml +8 -0
  142. data/spec/lib/kafka_command/configuration_spec.rb +311 -0
  143. data/spec/models/kafka_command/broker_spec.rb +83 -0
  144. data/spec/models/kafka_command/client_spec.rb +306 -0
  145. data/spec/models/kafka_command/cluster_spec.rb +163 -0
  146. data/spec/models/kafka_command/consumer_group_partition_spec.rb +43 -0
  147. data/spec/models/kafka_command/consumer_group_spec.rb +236 -0
  148. data/spec/models/kafka_command/partition_spec.rb +95 -0
  149. data/spec/models/kafka_command/topic_spec.rb +311 -0
  150. data/spec/rails_helper.rb +63 -0
  151. data/spec/requests/json/brokers_spec.rb +50 -0
  152. data/spec/requests/json/clusters_spec.rb +58 -0
  153. data/spec/requests/json/consumer_groups_spec.rb +139 -0
  154. data/spec/requests/json/topics_spec.rb +274 -0
  155. data/spec/spec_helper.rb +109 -0
  156. data/spec/support/factory_bot.rb +5 -0
  157. data/spec/support/json_helper.rb +13 -0
  158. data/spec/support/kafka_helper.rb +93 -0
  159. metadata +326 -0
@@ -0,0 +1,115 @@
1
+ <%= render partial: 'kafka_command/shared/title', locals: { title: 'Clusters', subtitle: 'Add a cluster' } %>
2
+
3
+ <div class="column is-one-third">
4
+ <%= form_for @cluster, url: { action: 'create' } do |f| %>
5
+ <div class="field">
6
+ <%= label_tag :name, 'Name', class: 'label' %>
7
+ <div class="control">
8
+ <input class="input" type="text" name="name" placeholder="Production">
9
+ </div>
10
+ </div>
11
+
12
+ <div class="field">
13
+ <%= label_tag :description, 'Description', class: 'label' %>
14
+ <div class="control">
15
+ <input class="input" type="text" name="description" placeholder="Production">
16
+ </div>
17
+ </div>
18
+
19
+ <div class="field">
20
+ <%= label_tag :hosts, 'Hosts', class: 'label' %>
21
+ <div class="control">
22
+ <input class="input" type="text" name="hosts" placeholder="10.0.0.1:9092,10.0.0.2:9092,10.0.0.3:9092">
23
+ </div>
24
+ </div>
25
+
26
+ <div class="field">
27
+ <%= label_tag :protocol, 'Protocol', class: 'label' %>
28
+ <div class="control">
29
+ <div class="select">
30
+ <select id="protocol-selection">
31
+ <option>PLAINTEXT</option>
32
+ <option>SSL</option>
33
+ <option>SASL/SCRAM</option>
34
+ </select>
35
+ </div>
36
+ </div>
37
+ </div>
38
+
39
+ <div id="sasl-scram-username" class="field" style="display:none">
40
+ <%= label_tag :sasl_scram_username, 'SASL/SCRAM Username', class: 'label' %>
41
+ <div class="control">
42
+ <input type="text" name="sasl_scram_username" class="input" placeholder="SASL/SCRAM username">
43
+ </div>
44
+ </div>
45
+
46
+ <div id="sasl-scram-password" class="field" style="display:none">
47
+ <%= label_tag :sasl_scram_password, 'SASL/SCRAM Password', class: 'label' %>
48
+ <div class="control">
49
+ <input type="password" class="input" name="sasl_scram_password" placeholder="SASL/SCRAM password">
50
+ </div>
51
+ </div>
52
+
53
+ <div id="ca-cert-textarea" class="field" style="display:none">
54
+ <%= label_tag :ssl_ca_cert, 'SSL CA Certificate', class: 'label' %>
55
+ <div class="control">
56
+ <textarea type="textarea" name="ssl_ca_cert" class="textarea"></textarea>
57
+ </div>
58
+ </div>
59
+
60
+ <div id="client-cert-textarea" class="field" style="display:none">
61
+ <%= label_tag :ssl_client_cert, 'SSL Client Certificate', class: 'label' %>
62
+ <div class="control">
63
+ <textarea name="ssl_client_cert" class="textarea"></textarea>
64
+ </div>
65
+ </div>
66
+
67
+ <div id="client-cert-key-textarea" class="field" style="display:none">
68
+ <%= label_tag :ssl_client_cert_key, 'SSL Client Certificate Key', class: 'label' %>
69
+ <div class="control">
70
+ <textarea type="textarea" name="ssl_client_cert_key" class="textarea"></textarea>
71
+ </div>
72
+ </div>
73
+
74
+ <div class="field">
75
+ <div class="control">
76
+ <%= f.submit 'Add', class: 'button is-primary' %>
77
+ </div>
78
+ </div>
79
+
80
+ <% end %>
81
+ </div>
82
+
83
+ <script type="text/javascript">
84
+ var protocolSelectTag = document.getElementById("protocol-selection");
85
+
86
+ protocolSelectTag.onchange = function() {
87
+ var saslUsername = document.getElementById("sasl-scram-username");
88
+ var saslPassword = document.getElementById("sasl-scram-password");
89
+ var sslCaCertText = document.getElementById("ca-cert-textarea");
90
+ var sslClientText = document.getElementById("client-cert-textarea");
91
+ var sslClientCertKeyText = document.getElementById("client-cert-key-textarea");
92
+
93
+ saslUsername.getElementsByTagName('input')[0].value = "";
94
+ saslPassword.getElementsByTagName('input')[0].value = "";
95
+ sslCaCertText.getElementsByTagName('textarea')[0].value = "";
96
+ sslClientText.getElementsByTagName('textarea')[0].value = "";
97
+ sslClientCertKeyText.getElementsByTagName('textarea')[0].value = "";
98
+
99
+ saslUsername.style.display = "none";
100
+ saslPassword.style.display = "none";
101
+ sslCaCertText.style.display = "none";
102
+ sslClientText.style.display = "none";
103
+ sslClientCertKeyText.style.display = "none";
104
+
105
+ if(this.value == "SSL") {
106
+ sslCaCertText.style.display = "block";
107
+ sslClientText.style.display = "block";
108
+ sslClientCertKeyText.style.display = "block";
109
+ } else if(this.value == "SASL/SCRAM") {
110
+ sslCaCertText.style.display = "block";
111
+ saslUsername.style.display = "block";
112
+ saslPassword.style.display = "block";
113
+ }
114
+ };
115
+ </script>
@@ -0,0 +1 @@
1
+ <%= render partial: 'kafka_command/shared/title', locals: { title: 'Configuration Error', subtitle: 'Kafka Command is improperly configured' } %>
@@ -0,0 +1,32 @@
1
+ <%= render partial: 'kafka_command/shared/title', locals: { title: @cluster.to_human, subtitle: 'Consumers' } %>
2
+
3
+ <div class="level"a>
4
+ <%=
5
+ render(
6
+ partial: 'kafka_command/shared/search_bar',
7
+ locals: {
8
+ resources: @groups,
9
+ resource_name: 'consumer',
10
+ resource_path: cluster_consumer_groups_path,
11
+ filter_property: :group_id
12
+ }
13
+ )
14
+ %>
15
+ </div>
16
+
17
+ <div class="columns">
18
+ <div class="column is-5">
19
+ <table class="table is-striped is-bordered is-fullwidth">
20
+ <thead>
21
+ <th>Group Id</th>
22
+ </thead>
23
+ <tbody>
24
+ <% @groups.each do |group| %>
25
+ <tr>
26
+ <td><%= link_to group.group_id, consumer_groups_path(group) %>
27
+ </tr>
28
+ <% end %>
29
+ </tbody>
30
+ </table>
31
+ </div>
32
+ </div>
@@ -0,0 +1,115 @@
1
+ <%= render partial: 'kafka_command/shared/title', locals: { title: @cluster.to_human, subtitle: @group.group_id } %>
2
+
3
+ <div class="columns">
4
+ <div class="column is-three-quarters">
5
+ <nav class="level">
6
+ <div class="level-item is-narrow has-text-centered">
7
+ <div> <p class="heading">State</p>
8
+ <p class="title"><%= @group.state.downcase.capitalize %></p>
9
+ </div>
10
+ </div>
11
+ <div class="level-item is-narrow has-text-centered">
12
+ <div>
13
+ <p class="heading">Members</p>
14
+ <p class="title"><%= @group.members.count %></p>
15
+ </div>
16
+ </div>
17
+ <div class="level-item is-narrow has-text-centered">
18
+ <div>
19
+ <p class="heading">Coordinator</p>
20
+ <p class="title"><%= @group.coordinator.node_id %></p>
21
+ </div>
22
+ </div>
23
+ <div class="level-item is-narrow has-text-centered">
24
+ <div>
25
+ <p class="heading">Total Lag</p>
26
+ <% if @current_topic %>
27
+ <p class="title"><%= @group.total_lag_for(@current_topic.name) %></p>
28
+ <% else %>
29
+ <p class="title">N/A</p>
30
+ <% end %>
31
+ </div>
32
+ </div>
33
+ </nav>
34
+ </div>
35
+
36
+ <% if @group.consumed_topics.any? %>
37
+ <div class="column is-one-quarter has-text-right">
38
+ <div class="box is-shadowless">
39
+ <div id="select-topic-dropdown" class="dropdown is-hoverable">
40
+ <div class="dropdown-trigger">
41
+ <button class="button" aria-haspopup="true" aria-controls="dropdown-menu">
42
+ <span><%= trim_name(@current_topic.name) %></span>
43
+ <span class="icon is-small">
44
+ <i class="fas fa-angle-down" aria-hidden="true"></i>
45
+ </span>
46
+ </button>
47
+ </div>
48
+ <div class="dropdown-menu" id="dropdown-menu" role="menu">
49
+ <div class="dropdown-content">
50
+ <% @group.consumed_topics.each do |topic| %>
51
+ <%= link_to trim_name(topic.name), "#{consumer_groups_path(@group)}?topic=#{topic.name}", class: 'dropdown-item' %>
52
+ <% end %>
53
+ </div>
54
+ </div>
55
+ </div>
56
+ </div>
57
+ </div>
58
+ <% end %>
59
+ </div>
60
+
61
+ <div class="columns">
62
+ <div class="column is-three-quarters">
63
+ <table class="table is-striped is-bordered is-fullwidth">
64
+ <thead class="has-background-primary">
65
+ <th class="has-text-white is-borderless">Partitions</th>
66
+ <th class="is-borderless"></th>
67
+ <th class="is-borderless"></th>
68
+ <th class="is-borderless"></th>
69
+ </thead>
70
+
71
+ <tbody>
72
+ <tr>
73
+ <th>Id</th>
74
+ <th>Group Offset</th>
75
+ <th>Topic Offset</th>
76
+ <th>Lag</th>
77
+ </tr>
78
+ <% if @current_topic %>
79
+ <% @group.partitions_for(@current_topic.name).sort_by(&:partition_id).each do |p| %>
80
+ <tr>
81
+ <td><%= p.partition_id %></td>
82
+ <% if p.offset %>
83
+ <td><%= p.offset %></td>
84
+ <% else %>
85
+ <td class="has-text-grey-light">N/A</td>
86
+ <% end %>
87
+ <td><%= @current_topic.offset_for(p) %></td>
88
+ <% if p.lag %>
89
+ <td><%= p.lag %></td>
90
+ <% else %>
91
+ <td class="has-text-grey-light">N/A</td>
92
+ <% end %>
93
+ </tr>
94
+ <% end %>
95
+ <% end %>
96
+ </tbody>
97
+ </table>
98
+ </div>
99
+
100
+ <div class="column is-one-quarter">
101
+ <table class="table is-striped is-bordered is-fullwidth">
102
+ <thead class="has-background-primary">
103
+ <th class="is-borderless has-text-white">Members</th>
104
+ </thead>
105
+
106
+ <tbody>
107
+ <% @group.members.each do |member| %>
108
+ <tr>
109
+ <td><%= member.member_id %></td>
110
+ </tr>
111
+ <% end %>
112
+ </tbody>
113
+ </table>
114
+ </div>
115
+ </div>
@@ -0,0 +1,13 @@
1
+ <% if flash[:success] %>
2
+ <article class="message is-small is-success">
3
+ <div class="message-body">
4
+ <%= flash[:success].html_safe %>
5
+ </div>
6
+ </article>
7
+ <% elsif flash[:error] %>
8
+ <article class="message is-small is-danger">
9
+ <div class="message-body">
10
+ <%= format_flash_errors %>
11
+ </div>
12
+ </article>
13
+ <% end %>
@@ -0,0 +1,31 @@
1
+ <div class="level-left">
2
+ <div class="level-item" style="margin-left: 0.2rem">
3
+ <p class="subtitle is-5">
4
+ <strong><%= resources.count %></strong> <%= resource_name.pluralize %>
5
+ </p>
6
+ </div>
7
+ <div class="level-item">
8
+ <%= form_tag resource_path, method: :get do %>
9
+ <div class="field has-addons">
10
+ <p class="control has-icons-left">
11
+ <%= text_field_tag(filter_property, flash[:search], class: 'input is-small', id: 'search-input', placeholder: "Find a #{resource_name}") %>
12
+ <span class="icon is-small is-left"><i class="fas fa-search"></i>
13
+ </p>
14
+ <p class="control">
15
+ <%= submit_tag('Search', class: 'button is-small') %>
16
+ </p>
17
+ </div>
18
+ <% end %>
19
+ </div>
20
+ </div>
21
+
22
+ <script type="text/javascript">
23
+ <% if flash[:search] %>
24
+ var searchInput = document.getElementById("search-input");
25
+ var searchInputvalue = searchInput.value;
26
+
27
+ searchInput.focus();
28
+ searchInput.value = '';
29
+ searchInput.value = searchInputvalue;
30
+ <% end %>
31
+ </script>
@@ -0,0 +1,6 @@
1
+ <h1 class="title page-title"><%= title %></h1>
2
+ <% if defined?(subtitle) %>
3
+ <h2 class="subtitle"><%= subtitle %></h2>
4
+ <% end %>
5
+ <hr class="hr">
6
+ <%= render 'kafka_command/shared/alert' %>
@@ -0,0 +1,49 @@
1
+ <div class="field">
2
+ <%= label_tag :num_partitions, 'Partitions', class: 'label' %>
3
+ <div class="control">
4
+ <%= number_field_tag(:num_partitions, partitions_value, min: partitions_value, class: 'input') %>
5
+ </div>
6
+ <p class="help">
7
+ The number of partitions can only be increased. A topic must be deleted and recreated to decrease partitions.
8
+ </p>
9
+ </div>
10
+
11
+ <div class="field">
12
+ <%= label_tag :retention_ms, 'Log Retention Milliseconds', class: 'label' %>
13
+ <div class="control">
14
+ <%= number_field_tag(:retention_ms, retention_ms_value, min: 1, class: 'input') %>
15
+ </div>
16
+ <p class="help">
17
+ Controls the maximum time a partition's log segments are retained before they are discarded to free up space.
18
+ </p>
19
+ </div>
20
+
21
+ <div class="field">
22
+ <%= label_tag :retention_bytes, 'Log Retention Bytes', class: 'label' %>
23
+ <div class="control">
24
+ <%= number_field_tag(:retention_bytes, retention_bytes_value, class: 'input') %>
25
+ </div>
26
+ <p class="help">
27
+ Controls the maximum size a partition can grow before discarding old log segments to free up space. <strong>A value of -1 disables this configuration option.</strong>
28
+ </p>
29
+ </div>
30
+
31
+ <div class="field">
32
+ <%= label_tag :max_message_bytes, 'Max Message Bytes', class: 'label' %>
33
+ <div class="control">
34
+ <%= number_field_tag(:max_message_bytes, max_message_bytes_value, min: 1, class: 'input') %>
35
+ </div>
36
+ <p class="help">
37
+ The largest record batch size allowed by Kafka.
38
+ </p>
39
+ </div>
40
+
41
+ <% if @redirect_path.present? %>
42
+ <%= hidden_field_tag(:redirect_path, @redirect_path) %>
43
+ <% end %>
44
+
45
+ <div class="field">
46
+ <div class="control">
47
+ <%= submit_tag('Save', class: 'button is-primary') %>
48
+ </div>
49
+ </div>
@@ -0,0 +1,17 @@
1
+ <%= render partial: 'kafka_command/shared/title', locals: { title: @cluster.to_human, subtitle: @topic.name } %>
2
+
3
+ <div class="column is-one-third">
4
+ <%= form_tag topic_path(@topic), method: :patch do |f| %>
5
+ <%=
6
+ render(
7
+ partial: 'form_fields',
8
+ locals: {
9
+ retention_bytes_value: @topic.retention_bytes,
10
+ max_message_bytes_value: @topic.max_message_bytes,
11
+ retention_ms_value: @topic.retention_ms,
12
+ partitions_value: @topic.partitions.count
13
+ }
14
+ )
15
+ %>
16
+ <% end %>
17
+ </div>
@@ -0,0 +1,46 @@
1
+ <%= render partial: 'kafka_command/shared/title', locals: { title: @cluster.to_human, subtitle: 'Topics' } %>
2
+
3
+ <div class="level"a>
4
+ <%=
5
+ render(
6
+ partial: 'kafka_command/shared/search_bar',
7
+ locals: {
8
+ resources: @topics,
9
+ resource_name: 'topic',
10
+ resource_path: cluster_topics_path,
11
+ filter_property: :name
12
+ }
13
+ )
14
+ %>
15
+ <div class="level-right">
16
+ <%= link_to 'Add Topic', new_cluster_topic_path, class: 'button is-primary is-small' %>
17
+ </div>
18
+ </div>
19
+
20
+ <table class="table is-striped is-fullwidth is-bordered">
21
+ <thead>
22
+ <th>Name</th>
23
+ <th>Partitions</th>
24
+ <th>Replication Factor</th>
25
+ <th>Broker Spread</th>
26
+ <th></th>
27
+ </thead>
28
+ <tbody>
29
+ <% @topics.each do |topic| %>
30
+ <tr>
31
+ <td><%= link_to topic.name, topic_path(topic) %></td>
32
+ <td><%= topic.partitions.count %></td>
33
+ <td><%= topic.replication_factor %></td>
34
+ <td><%= "#{topic.brokers_spread} %" %></td>
35
+ <td>
36
+ <%= link_to "#{topic_path(topic)}/edit" do %>
37
+ <span class="icon has-text-grey"><i class="fas fa-edit"></i></span>
38
+ <% end %>
39
+ <%= link_to topic_path(topic), method: :delete, data: { confirm: "Are you sure you want to delete topic #{topic.name} ?" } do %>
40
+ <span class="icon has-text-danger"><i class="fas fa-trash-alt"></i></span>
41
+ <% end %>
42
+ </td>
43
+ </tr>
44
+ <% end %>
45
+ </tbody>
46
+ </table>
@@ -0,0 +1,36 @@
1
+ <%= render partial: 'kafka_command/shared/title', locals: { title: @cluster.to_human, subtitle: 'Add a topic' } %>
2
+
3
+ <div class="column is-one-third">
4
+ <%= form_tag cluster_topics_path, method: :post do |f| %>
5
+ <div class="field">
6
+ <%= label_tag :name, 'Name', class: 'label' %>
7
+ <div class="control">
8
+ <input class="input" type="text" name="name" placeholder="my-topic">
9
+ </div>
10
+ </div>
11
+
12
+ <div class="field">
13
+ <%= label_tag :replication_factor, 'Replication Factor', class: 'label' %>
14
+ <div class="control">
15
+ <%= number_field_tag(:replication_factor, 1, in: 1..(@cluster.brokers.count), class: 'input') %>
16
+ </div>
17
+ <p class="help">
18
+ The maximum value for replication factor is equal to the number of brokers in the cluster.
19
+ The <%= @cluster.to_human %> cluster has <strong><%= @cluster.brokers.count %></strong> brokers.
20
+ </p>
21
+ </div>
22
+
23
+ <%=
24
+ render(
25
+ partial: 'form_fields',
26
+ locals: {
27
+ retention_bytes_value: KafkaCommand::Topic::DEFAULT_RETENTION_BYTES,
28
+ retention_ms_value: KafkaCommand::Topic::DEFAULT_RETENTION_MS,
29
+ max_message_bytes_value: KafkaCommand::Topic::DEFAULT_MAX_MESSAGE_BYTES,
30
+ partitions_value: 5
31
+ }
32
+ )
33
+ %>
34
+
35
+ <% end %>
36
+ </div>
@@ -0,0 +1,126 @@
1
+ <%= render partial: 'kafka_command/shared/title', locals: { title: @cluster.to_human, subtitle: @topic.name } %>
2
+
3
+ <div class="columns">
4
+ <div class="column is-three-quarters">
5
+ <nav class="level">
6
+ <div class="level-item is-narrow has-text-centered">
7
+ <div>
8
+ <p class="heading">Replication Factor</p>
9
+ <p class="title"><%= @topic.replication_factor %></p>
10
+ </div>
11
+ </div>
12
+ <div class="level-item is-narrow has-text-centered">
13
+ <div>
14
+ <p class="heading">Partitions</p>
15
+ <p class="title"><%= @topic.partitions.count %></p>
16
+ </div>
17
+ </div>
18
+ <div class="level-item is-narrow has-text-centered">
19
+ <div>
20
+ <p class="heading">Consumer Groups</p>
21
+ <p class="title"><%= @topic.groups.count %></p>
22
+ </div>
23
+ </div>
24
+ <div class="level-item is-narrow has-text-centered">
25
+ <div>
26
+ <p class="heading">Brokers Spread</p>
27
+ <p class="title"><%= "#{@topic.brokers_spread}%" %></p>
28
+ </div>
29
+ </div>
30
+ <div class="level-item is-narrow has-text-centered">
31
+ <div>
32
+ <p class="heading">Offset Sum</p>
33
+ <p class="title"><%= @topic.offset_sum %></p>
34
+ </div>
35
+ </div>
36
+ </nav>
37
+ </div>
38
+
39
+ <div class="column is-one-quarter has-text-right">
40
+ <div class="box is-shadowless">
41
+ <div>
42
+ <%= link_to "#{topic_path(@topic)}/edit?redirect_path=#{URI.encode(topic_path(@topic))}" do %>
43
+ <span class="icon has-text-grey-light"><i class="fas fa-edit fa-2x"></i></span>
44
+ <% end %>
45
+ &nbsp;
46
+ <%= link_to topic_path(@topic), method: :delete, data: { confirm: "Are you sure you want to delete topic #{@topic.name} ?" } do %>
47
+ <span class="icon has-text-danger"><i class="fas fa-trash-alt fa-2x"></i></span>
48
+ <% end %>
49
+ </div>
50
+ </div>
51
+ </div>
52
+ </div>
53
+
54
+ <div class="columns">
55
+ <div class="column is-three-quarters">
56
+ <table class="table is-striped is-bordered is-fullwidth">
57
+ <thead class="has-background-primary is-borderless">
58
+ <th class="has-text-white is-borderless">Partitions</th>
59
+ <th class="is-borderless"></th>
60
+ <th class="is-borderless"></th>
61
+ <th class="is-borderless"></th>
62
+ </thead>
63
+
64
+ <tbody>
65
+ <tr>
66
+ <th>Id</th>
67
+ <th>Leader</th>
68
+ <th>In-sync Replicas</th>
69
+ <th>Offset</th>
70
+ </tr>
71
+ <% @topic.partitions.sort_by(&:partition_id).each do |p| %>
72
+ <tr>
73
+ <td><%= p.partition_id %></td>
74
+ <td><%= p.leader %></td>
75
+ <td><%= "#{p.isr.join(', ')}" %></td>
76
+ <td><%= p.offset %></td>
77
+ </tr>
78
+ <% end %>
79
+ </tbody>
80
+ </table>
81
+ </div>
82
+
83
+ <div class="column is-one-quarter">
84
+ <table class="table is-striped is-bordered is-fullwidth config-table">
85
+ <thead class="has-background-primary is-borderless">
86
+ <th class="has-text-white is-borderless">Configs</th>
87
+ <th class="is-borderless"></th>
88
+ </thead>
89
+
90
+ <tbody>
91
+ <tr>
92
+ <td>Max message bytes</td>
93
+ <td><%= @topic.max_message_bytes %></td>
94
+ </tr>
95
+ <tr>
96
+ <td>Log retention milliseconds</td>
97
+ <td><%= @topic.retention_ms %></td>
98
+ </tr>
99
+ <tr>
100
+ <td>Log retention bytes</td>
101
+ <td><%= @topic.retention_bytes %></td>
102
+ </tr>
103
+ </tbody>
104
+ </table>
105
+
106
+ <table class="table is-striped is-bordered is-fullwidth">
107
+ <thead class="has-background-primary">
108
+ <th class="has-text-white">Consumers</th>
109
+ </thead>
110
+
111
+ <tbody>
112
+ <% if @topic.groups.any? %>
113
+ <% @topic.groups.each do |group| %>
114
+ <tr>
115
+ <td><%= link_to group.group_id, "#{consumer_groups_path(group)}?topic=#{URI.escape(@topic.name)}" %></td>
116
+ </tr>
117
+ <% end %>
118
+ <% else %>
119
+ <tr>
120
+ <td class="has-text-grey-light">None</td>
121
+ </tr>
122
+ <% end %>
123
+ </tbody>
124
+ </table>
125
+ </div>
126
+ </div>
@@ -0,0 +1,50 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Kafka Command</title>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <%= csrf_meta_tags %>
7
+
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.1/css/bulma.min.css">
9
+ <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/all.css" integrity="sha384-lKuwvrZot6UHsBSfcMvOkWwlCMgc0TaWr+30HWe3a4ltaBwTZhyTEggF5tJv8tbt" crossorigin="anonymous">
10
+ <%= stylesheet_link_tag 'kafka_command/application', media: 'all', 'data-turbolinks-track': 'reload' %>
11
+ <%= javascript_include_tag 'kafka_command/application', 'data-turbolinks-track': 'reload' %>
12
+ </head>
13
+
14
+ <body>
15
+ <nav class="navbar is-light" role="navigation">
16
+ <div class="container">
17
+ <div class="navbar-brand kafka-logo">
18
+ <p class="navbar-item is-paddingless">
19
+ <%= image_tag('kafka_command/kafka.png', alt: '') %>
20
+ </p>
21
+ </div>
22
+ <div class="navbar-start">
23
+ <div class="navbar-menu is-active">
24
+ <% if @cluster %>
25
+ <div class="navbar-item has-dropdown is-hoverable">
26
+ <%= link_to 'Clusters', clusters_path, class: 'navbar-link has-text-weight-bold' %>
27
+ <div class="navbar-dropdown is-boxed">
28
+ <% KafkaCommand::Cluster.all.each do |cluster| %>
29
+ <%= link_to trim_name(cluster.to_human), cluster_topics_path(cluster), class: 'navbar-item' %>
30
+ <% end %>
31
+ </div>
32
+ </div>
33
+
34
+ <%= link_to 'Topics', cluster_topics_path(@cluster), class: 'navbar-item' %>
35
+ <%= link_to 'Consumers', cluster_consumer_groups_path(@cluster), class: 'navbar-item' %>
36
+ <%= link_to 'Brokers', cluster_brokers_path(@cluster), class: 'navbar-item' %>
37
+ <% end %>
38
+ </div>
39
+ </div>
40
+ <div class="navbar-end">
41
+ <p class="subtitle navbar-item">Command</p>
42
+ </div>
43
+ </div>
44
+ </nav>
45
+
46
+ <div class="container">
47
+ <%= yield %>
48
+ </div>
49
+ </body>
50
+ </html>
data/bin/rails ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # This command will automatically be run when you run "rails" with Rails gems
5
+ # installed from the root of your application.
6
+
7
+ ENGINE_ROOT = File.expand_path('..', __dir__)
8
+ ENGINE_PATH = File.expand_path('../lib/kafka_command/engine', __dir__)
9
+ APP_PATH = File.expand_path('../spec/dummy/config/application', __dir__)
10
+
11
+ # Set up gems listed in the Gemfile.
12
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
13
+ require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
14
+
15
+ require 'rails/all'
16
+ require 'rails/engine/commands'