karafka-web 0.10.0 → 0.10.1

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 +10 -0
  4. data/Gemfile +1 -0
  5. data/Gemfile.lock +12 -4
  6. data/certs/cert.pem +26 -0
  7. data/karafka-web.gemspec +2 -2
  8. data/lib/karafka/web/pro/ui/app.rb +40 -0
  9. data/lib/karafka/web/pro/ui/controllers/commanding_controller.rb +4 -4
  10. data/lib/karafka/web/pro/ui/controllers/recurring_tasks_controller.rb +131 -0
  11. data/lib/karafka/web/pro/ui/views/consumers/_consumer_controls.erb +13 -12
  12. data/lib/karafka/web/pro/ui/views/recurring_tasks/_actions.erb +58 -0
  13. data/lib/karafka/web/pro/ui/views/recurring_tasks/_batch_actions.erb +45 -0
  14. data/lib/karafka/web/pro/ui/views/recurring_tasks/_breadcrumbs.erb +22 -0
  15. data/lib/karafka/web/pro/ui/views/recurring_tasks/_log.erb +26 -0
  16. data/lib/karafka/web/pro/ui/views/recurring_tasks/_not_active.erb +12 -0
  17. data/lib/karafka/web/pro/ui/views/recurring_tasks/_tabs.erb +17 -0
  18. data/lib/karafka/web/pro/ui/views/recurring_tasks/_task.erb +46 -0
  19. data/lib/karafka/web/pro/ui/views/recurring_tasks/logs.erb +34 -0
  20. data/lib/karafka/web/pro/ui/views/recurring_tasks/schedule.erb +43 -0
  21. data/lib/karafka/web/pro/ui/views/shared/_navigation.erb +10 -0
  22. data/lib/karafka/web/pro/ui/views/topics/replication.erb +1 -1
  23. data/lib/karafka/web/ui/helpers/application_helper.rb +8 -1
  24. data/lib/karafka/web/ui/models/recurring_tasks/log.rb +26 -0
  25. data/lib/karafka/web/ui/models/recurring_tasks/schedule.rb +86 -0
  26. data/lib/karafka/web/ui/models/recurring_tasks/task.rb +30 -0
  27. data/lib/karafka/web/ui/public/javascripts/application.js +1 -1
  28. data/lib/karafka/web/ui/public/javascripts/application.min.js +7 -7
  29. data/lib/karafka/web/ui/public/javascripts/application.min.js.br +0 -0
  30. data/lib/karafka/web/ui/public/javascripts/application.min.js.gz +0 -0
  31. data/lib/karafka/web/ui/public/javascripts/components/live_poll.js +36 -0
  32. data/lib/karafka/web/ui/public/stylesheets/application.css +5 -1
  33. data/lib/karafka/web/ui/public/stylesheets/application.min.css +2 -2
  34. data/lib/karafka/web/ui/public/stylesheets/application.min.css.br +0 -0
  35. data/lib/karafka/web/ui/public/stylesheets/application.min.css.gz +0 -0
  36. data/lib/karafka/web/ui/public/stylesheets/libs/tailwind.css +17 -5
  37. data/lib/karafka/web/ui/views/dashboard/_ranges_selector.erb +1 -1
  38. data/lib/karafka/web/ui/views/shared/_navigation.erb +11 -0
  39. data/lib/karafka/web/ui/views/shared/icons/_check.erb +3 -0
  40. data/lib/karafka/web/ui/views/shared/icons/_play.erb +3 -0
  41. data/lib/karafka/web/ui/views/status/info/_components.erb +48 -11
  42. data/lib/karafka/web/ui/views/ux/_data_table.erb +34 -4
  43. data/lib/karafka/web/ui/views/ux/_status_rows.erb +12 -0
  44. data/lib/karafka/web/version.rb +1 -1
  45. data.tar.gz.sig +0 -0
  46. metadata +41 -26
  47. metadata.gz.sig +0 -0
  48. data/certs/cert_chain.pem +0 -26
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 973e9a2d6a32a1bd3bd1df4b812dd024631a4f444239c92bf3659cb1b31b262f
4
- data.tar.gz: 49177c26dfee8b2b88a2bf39d46c98f00d696ac6251cecf8588f9e12cf1e55f0
3
+ metadata.gz: 40794ee9c8b084cf75de0403b904134cbc6a762dbfa338bb9ce574b56e46ac36
4
+ data.tar.gz: dedefe087c54e118abb90e819b36b29aa253c4351aa18bd8a78d01e2c096ce79
5
5
  SHA512:
6
- metadata.gz: 553207e2fc13646488ee0d2a219faf945829f9bc2d7d65878f65905e8cfda8b23c3c747add70b7a4cbe2e7a232bbe623089ebcadd905f989ac5e8b17685d00c4
7
- data.tar.gz: 8df62e9627819ac505eb8c64f31950e48f35cbdc9426c8ae9bf12de8250e3f44e4f358357d0f1e5847411419598b87a4d92c2e95262278f9c2d10e1364648de3
6
+ metadata.gz: 88b9b6ed8082b0484dcd32a20b2cee2b77d4df762b65d0487325e03ef375e1e7ac621d612f81b3f4a6870aa4352e07286979863d656161ca7d8d880e5709d108
7
+ data.tar.gz: f9350f68d872c76d289933fbf2548b692179bae30fb2c0fccf923cdefca7131bbf3e6bf362e21a442c424fc4b43472e8334115b90c32449e80ef45a3518c3228
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Karafka Web Changelog
2
2
 
3
+ ## 0.10.1 (Unreleased)
4
+ - **[Feature]** Support Recurring Tasks management (Pro).
5
+ - [Enhancement] Optimize command buttons so they occupy less space.
6
+ - [Enhancement] Improve tables headers capitalization.
7
+ - [Enhancement] Prevent live-polling when user hovers over actionable links to mitigate race conditions.
8
+ - [Fix] Fix partial lack of tables hover in daily mode.
9
+ - [Fix] Fix lack of tables hover in dark mode.
10
+ - [Fix] Normalize various tables types styling.
11
+ - [Fix] Fix ranges selectors position on wide screens.
12
+
3
13
  ## 0.10.0 (2024-08-19)
4
14
  - **[Breaking]** Rename and reorganize visibility filter to policies engine since it is not only about visibility.
5
15
  - **[Feature]** Replace Bootstrap with with tailwind + DaisyUI.
data/Gemfile CHANGED
@@ -9,6 +9,7 @@ gemspec
9
9
  group :test do
10
10
  gem 'byebug'
11
11
  gem 'factory_bot'
12
+ gem 'fugit'
12
13
  gem 'ostruct'
13
14
  gem 'rack-test'
14
15
  gem 'rspec'
data/Gemfile.lock CHANGED
@@ -1,9 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- karafka-web (0.10.0)
4
+ karafka-web (0.10.1)
5
5
  erubi (~> 1.4)
6
- karafka (>= 2.4.7, < 2.5.0)
6
+ karafka (>= 2.4.9, < 2.5.0)
7
7
  karafka-core (>= 2.4.0, < 2.5.0)
8
8
  roda (~> 3.68, >= 3.69)
9
9
  tilt (~> 2.0)
@@ -30,19 +30,25 @@ GEM
30
30
  docile (1.4.0)
31
31
  drb (2.2.1)
32
32
  erubi (1.13.0)
33
+ et-orbi (1.2.11)
34
+ tzinfo
33
35
  factory_bot (6.4.6)
34
36
  activesupport (>= 5.0.0)
35
37
  ffi (1.17.0)
38
+ fugit (1.11.1)
39
+ et-orbi (~> 1, >= 1.2.11)
40
+ raabro (~> 1.4)
36
41
  i18n (1.14.5)
37
42
  concurrent-ruby (~> 1.0)
38
- karafka (2.4.7)
43
+ karafka (2.4.9)
39
44
  base64 (~> 0.2)
40
45
  karafka-core (>= 2.4.3, < 2.5.0)
46
+ karafka-rdkafka (>= 0.17.2)
41
47
  waterdrop (>= 2.7.3, < 3.0.0)
42
48
  zeitwerk (~> 2.3)
43
49
  karafka-core (2.4.4)
44
50
  karafka-rdkafka (>= 0.15.0, < 0.18.0)
45
- karafka-rdkafka (0.17.1)
51
+ karafka-rdkafka (0.17.3)
46
52
  ffi (~> 1.15)
47
53
  mini_portile2 (~> 2.6)
48
54
  rake (> 12)
@@ -50,6 +56,7 @@ GEM
50
56
  minitest (5.24.0)
51
57
  mutex_m (0.2.0)
52
58
  ostruct (0.6.0)
59
+ raabro (1.4.0)
53
60
  rack (3.1.4)
54
61
  rack-test (2.1.0)
55
62
  rack (>= 1.3)
@@ -95,6 +102,7 @@ PLATFORMS
95
102
  DEPENDENCIES
96
103
  byebug
97
104
  factory_bot
105
+ fugit
98
106
  karafka-web!
99
107
  ostruct
100
108
  rack-test
data/certs/cert.pem ADDED
@@ -0,0 +1,26 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIEcDCCAtigAwIBAgIBATANBgkqhkiG9w0BAQsFADA/MRAwDgYDVQQDDAdjb250
3
+ YWN0MRcwFQYKCZImiZPyLGQBGRYHa2FyYWZrYTESMBAGCgmSJomT8ixkARkWAmlv
4
+ MB4XDTI0MDgyMzEwMTkyMFoXDTQ5MDgxNzEwMTkyMFowPzEQMA4GA1UEAwwHY29u
5
+ dGFjdDEXMBUGCgmSJomT8ixkARkWB2thcmFma2ExEjAQBgoJkiaJk/IsZAEZFgJp
6
+ bzCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAKjLhLjQqUlNayxkXnO+
7
+ PsmCDs/KFIzhrsYMfLZRZNaWmzV3ujljMOdDjd4snM2X06C41iVdQPWjpe3j8vVe
8
+ ZXEWR/twSbOP6Eeg8WVH2wCOo0x5i7yhVn4UBLH4JpfEMCbemVcWQ9ry9OMg4WpH
9
+ Uu4dRwxFV7hzCz3p0QfNLRI4miAxnGWcnlD98IJRjBAksTuR1Llj0vbOrDGsL9ZT
10
+ JeXP2gdRLd8SqzAFJEWrbeTBCBU7gfSh3oMg5SVDLjaqf7Kz5wC/8bDZydzanOxB
11
+ T6CDXPsCnllmvTNx2ei2T5rGYJOzJeNTmJLLK6hJWUlAvaQSvCwZRvFJ0tVGLEoS
12
+ flqSr6uGyyl1eMUsNmsH4BqPEYcAV6P2PKTv2vUR8AP0raDvZ3xL1TKvfRb8xRpo
13
+ vPopCGlY5XBWEc6QERHfVLTIVsjnls2/Ujj4h8/TSfqqYnaHKefIMLbuD/tquMjD
14
+ iWQsW2qStBV0T+U7FijKxVfrfqZP7GxQmDAc9o1iiyAa3QIDAQABo3cwdTAJBgNV
15
+ HRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQU3O4dTXmvE7YpAkszGzR9DdL9
16
+ sbEwHQYDVR0RBBYwFIESY29udGFjdEBrYXJhZmthLmlvMB0GA1UdEgQWMBSBEmNv
17
+ bnRhY3RAa2FyYWZrYS5pbzANBgkqhkiG9w0BAQsFAAOCAYEAVKTfoLXn7mqdSxIR
18
+ eqxcR6Huudg1jes81s1+X0uiRTR3hxxKZ3Y82cPsee9zYWyBrN8TA4KA0WILTru7
19
+ Ygxvzha0SRPsSiaKLmgOJ+61ebI4+bOORzIJLpD6GxCxu1r7MI4+0r1u1xe0EWi8
20
+ agkVo1k4Vi8cKMLm6Gl9b3wG9zQBw6fcgKwmpjKiNnOLP+OytzUANrIUJjoq6oal
21
+ TC+f/Uc0TLaRqUaW/bejxzDWWHoM3SU6aoLPuerglzp9zZVzihXwx3jPLUVKDFpF
22
+ Rl2lcBDxlpYGueGo0/oNzGJAAy6js8jhtHC9+19PD53vk7wHtFTZ/0ugDQYnwQ+x
23
+ oml2fAAuVWpTBCgOVFe6XCQpMKopzoxQ1PjKztW2KYxgJdIBX87SnL3aWuBQmhRd
24
+ i9zWxov0mr44TWegTVeypcWGd/0nxu1+QHVNHJrpqlPBRvwQsUm7fwmRInGpcaB8
25
+ ap8wNYvryYzrzvzUxIVFBVM5PacgkFqRmolCa8I7tdKQN+R1
26
+ -----END CERTIFICATE-----
data/karafka-web.gemspec CHANGED
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
17
17
  spec.licenses = %w[LGPL-3.0-only Commercial]
18
18
 
19
19
  spec.add_dependency 'erubi', '~> 1.4'
20
- spec.add_dependency 'karafka', '>= 2.4.7', '< 2.5.0'
20
+ spec.add_dependency 'karafka', '>= 2.4.9', '< 2.5.0'
21
21
  spec.add_dependency 'karafka-core', '>= 2.4.0', '< 2.5.0'
22
22
  spec.add_dependency 'roda', '~> 3.68', '>= 3.69'
23
23
  spec.add_dependency 'tilt', '~> 2.0'
@@ -30,7 +30,7 @@ Gem::Specification.new do |spec|
30
30
  spec.signing_key = File.expand_path('~/.ssh/gem-private_key.pem')
31
31
  end
32
32
 
33
- spec.cert_chain = %w[certs/cert_chain.pem]
33
+ spec.cert_chain = %w[certs/cert.pem]
34
34
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec)/}) }
35
35
  spec.executables = %w[karafka-web]
36
36
  spec.require_paths = %w[lib]
@@ -232,6 +232,46 @@ module Karafka
232
232
  end
233
233
  end
234
234
 
235
+ r.on 'recurring_tasks' do
236
+ controller = Controllers::RecurringTasksController.new(params)
237
+
238
+ r.get 'schedule' do
239
+ controller.schedule
240
+ end
241
+
242
+ r.get 'logs' do
243
+ controller.logs
244
+ end
245
+
246
+ r.post 'trigger_all' do
247
+ controller.trigger_all
248
+ end
249
+
250
+ r.post 'enable_all' do
251
+ controller.enable_all
252
+ end
253
+
254
+ r.post 'disable_all' do
255
+ controller.disable_all
256
+ end
257
+
258
+ r.post String, 'trigger' do |task_id|
259
+ controller.trigger(task_id)
260
+ end
261
+
262
+ r.post String, 'enable' do |task_id|
263
+ controller.enable(task_id)
264
+ end
265
+
266
+ r.post String, 'disable' do |task_id|
267
+ controller.disable(task_id)
268
+ end
269
+
270
+ r.get do
271
+ r.redirect root_path('recurring_tasks/schedule')
272
+ end
273
+ end
274
+
235
275
  r.on 'health' do
236
276
  controller = Controllers::HealthController.new(params)
237
277
 
@@ -27,7 +27,7 @@ module Karafka
27
27
 
28
28
  redirect(
29
29
  :back,
30
- success: dispatched_to_one_msg(:probe, process_id)
30
+ success: dispatched_to_one(:probe, process_id)
31
31
  )
32
32
  end
33
33
 
@@ -39,7 +39,7 @@ module Karafka
39
39
 
40
40
  redirect(
41
41
  :back,
42
- success: dispatched_to_one_msg(:quiet, process_id)
42
+ success: dispatched_to_one(:quiet, process_id)
43
43
  )
44
44
  end
45
45
 
@@ -51,7 +51,7 @@ module Karafka
51
51
 
52
52
  redirect(
53
53
  :back,
54
- success: dispatched_to_one_msg(:stop, process_id)
54
+ success: dispatched_to_one(:stop, process_id)
55
55
  )
56
56
  end
57
57
 
@@ -96,7 +96,7 @@ module Karafka
96
96
  # @param command [Symbol]
97
97
  # @param process_id [String]
98
98
  # @return [String] flash message that command has been dispatched to a given process
99
- def dispatched_to_one_msg(command, process_id)
99
+ def dispatched_to_one(command, process_id)
100
100
  command_name = command.to_s.capitalize
101
101
 
102
102
  "The #{command_name} command has been dispatched to the #{process_id} process."
@@ -0,0 +1,131 @@
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 Pro
17
+ module Ui
18
+ module Controllers
19
+ # Controller for viewing and managing recurring tasks
20
+ class RecurringTasksController < Web::Ui::Controllers::ClusterController
21
+ self.sortable_attributes = %w[
22
+ id
23
+ enabled
24
+ cron
25
+ previous_time
26
+ next_time
27
+ ].freeze
28
+
29
+ # Displays the current schedule
30
+ def schedule
31
+ @schedule = Models::RecurringTasks::Schedule.current
32
+
33
+ @tasks = refine(@schedule.tasks) if @schedule
34
+
35
+ render
36
+ end
37
+
38
+ # Displays the execution logs
39
+ def logs
40
+ @watermark_offsets = Models::WatermarkOffsets.find(logs_topic, 0)
41
+ previous_offset, @logs, next_offset, = current_page_data
42
+
43
+ paginate(
44
+ previous_offset,
45
+ @params.current_offset,
46
+ next_offset,
47
+ @logs.map(&:offset)
48
+ )
49
+
50
+ # We remap this so we can represent the payloads as logs that we expect
51
+ @logs.map! { |log| Models::RecurringTasks::Log.new(log.payload) }
52
+
53
+ render
54
+ end
55
+
56
+ # Actions definitions that trigger appropriate recurring tasks action + display a nice
57
+ # flash message.
58
+ %i[
59
+ trigger
60
+ enable
61
+ disable
62
+ ].each do |action|
63
+ define_method :"#{action}_all" do
64
+ command(action, '*')
65
+
66
+ redirect(
67
+ :back,
68
+ success: dispatched_to_all(action)
69
+ )
70
+ end
71
+
72
+ define_method action do |task_id|
73
+ command(action, task_id)
74
+
75
+ redirect(
76
+ :back,
77
+ success: dispatched_to_one(action, task_id)
78
+ )
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ # @return [Array] Array with requested messages as well as pagination details and other
85
+ # obtained metadata
86
+ def current_page_data
87
+ Models::Message.offset_page(
88
+ logs_topic,
89
+ 0,
90
+ @params.current_offset,
91
+ @watermark_offsets
92
+ )
93
+ end
94
+
95
+ # @return [String] recurring tasks logs topic
96
+ def logs_topic
97
+ ::Karafka::App.config.recurring_tasks.topics.logs
98
+ end
99
+
100
+ # Runs the recurring tasks command
101
+ #
102
+ # @param command [String] command we want to invoke
103
+ # @param task_id [String] task id or '*' to target expected task
104
+ def command(command, task_id)
105
+ Karafka::Pro::RecurringTasks.public_send(command, task_id)
106
+ end
107
+
108
+ # Generates a nice flash message about the dispatch
109
+ # @param command [Symbol]
110
+ # @param task_id [String]
111
+ # @return [String] flash message that command has been dispatched to a given task
112
+ def dispatched_to_one(command, task_id)
113
+ command_name = command.to_s.capitalize
114
+
115
+ "The #{command_name} command has been dispatched to the #{task_id} task."
116
+ end
117
+
118
+ # Generates a nice flash message about dispatch of multi-task command
119
+ # @param command [Symbol]
120
+ # @return [String] flash message that command has been dispatched
121
+ def dispatched_to_all(command)
122
+ command_name = command.to_s.capitalize
123
+
124
+ "The #{command_name} command has been dispatched to all tasks."
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -43,17 +43,16 @@
43
43
  <%= process.lag_hybrid %>
44
44
  </td>
45
45
 
46
- <td class="commands">
46
+ <td class="commands-inline-3">
47
47
  <%
48
48
  probe_path = root_path('commanding', process.id, 'probe')
49
49
  disabled_class = process.status != 'stopped' ? '' : 'btn-disabled'
50
50
  %>
51
- <form action="<%= probe_path %>" method="post" class="">
51
+ <form action="<%= probe_path %>" method="post" class="inline" title="Probe">
52
52
  <%== csrf_tag(probe_path) %>
53
53
 
54
- <button type="submit" class="btn btn-info btn-sm w-full max-w-xs mb-1 <%= disabled_class %>">
54
+ <button type="submit" class="btn btn-info btn-sm <%= disabled_class %>">
55
55
  <%== icon(:gear) %>
56
- Probe
57
56
  </button>
58
57
  </form>
59
58
 
@@ -61,16 +60,17 @@
61
60
  quiet_path = root_path('commanding', process.id, 'quiet')
62
61
  disabled_class = process.status == 'running' ? '' : 'btn-disabled'
63
62
 
64
- unless process.execution_mode == 'standalone'
63
+ if process.execution_mode == 'standalone'
64
+ title = 'Quiet'
65
+ else
65
66
  disabled_class = 'btn-disabled'
66
67
  title = 'Supported only in standalone consumer processes'
67
68
  end
68
69
  %>
69
- <form action="<%= quiet_path %>" method="post" class="" title="<%= title %>">
70
+ <form action="<%= quiet_path %>" method="post" class="inline" title="<%= title %>">
70
71
  <%== csrf_tag(quiet_path) %>
71
- <button type="submit" class="btn btn-warning btn-sm w-full max-w-xs mb-1 <%= disabled_class %>">
72
+ <button type="submit" class="btn btn-warning btn-sm <%= disabled_class %>">
72
73
  <%== icon(:pause) %>
73
- Quiet
74
74
  </button>
75
75
  </form>
76
76
 
@@ -78,16 +78,17 @@
78
78
  stop_path = root_path('commanding', process.id, 'stop')
79
79
  disabled_class = process.status != 'stopping' && process.status != 'stopped' ? '' : 'btn-disabled'
80
80
 
81
- unless process.execution_mode == 'standalone'
81
+ if process.execution_mode == 'standalone'
82
+ title = 'Stop'
83
+ else
82
84
  disabled_class = 'btn-disabled'
83
85
  title = 'Supported only in standalone consumer processes'
84
86
  end
85
87
  %>
86
- <form action="<%= stop_path %>" method="post" class="" title="<%= title %>">
88
+ <form action="<%= stop_path %>" method="post" class="inline" title="<%= title %>">
87
89
  <%== csrf_tag(stop_path) %>
88
- <button type="submit" class="btn btn-error btn-sm w-full max-w-xs <%= disabled_class %>">
90
+ <button type="submit" class="btn btn-error btn-sm <%= disabled_class %>">
89
91
  <%== icon(:stop) %>
90
- Stop
91
92
  </button>
92
93
  </form>
93
94
  </td>
@@ -0,0 +1,58 @@
1
+ <%
2
+ trigger_path = root_path('recurring_tasks', task.id, 'trigger')
3
+ disabled_class = task.enabled? ? '' : 'btn-disabled'
4
+ %>
5
+ <form
6
+ action="<%= trigger_path %>"
7
+ method="post"
8
+ class="inline"
9
+ >
10
+ <%== csrf_tag(trigger_path) %>
11
+ <button
12
+ type="submit"
13
+ class="btn btn-primary btn-sm "
14
+ title="Trigger"
15
+ >
16
+ <%== icon(:play) %>
17
+ </button>
18
+ </form>
19
+
20
+ <%
21
+ enable_path = root_path('recurring_tasks', task.id, 'enable')
22
+ disabled_class = task.enabled? ? 'btn-disabled' : ''
23
+ %>
24
+ <form
25
+ action="<%= enable_path %>"
26
+ method="post"
27
+ class="inline"
28
+ title="Enable"
29
+ >
30
+ <%== csrf_tag(enable_path) %>
31
+ <button
32
+ type="submit"
33
+ class="btn btn-success btn-sm <%= disabled_class %>"
34
+ >
35
+ <%== icon(:check) %>
36
+ </button>
37
+ </form>
38
+
39
+ <%
40
+ disable_path = root_path('recurring_tasks', task.id, 'disable')
41
+ disabled_class = task.enabled? ? '' : 'btn-disabled'
42
+ %>
43
+ <form
44
+ action="<%= disable_path %>"
45
+ method="post"
46
+ class="inline"
47
+ title="Disable"
48
+
49
+ >
50
+ <%== csrf_tag(disable_path) %>
51
+ <button
52
+ type="submit"
53
+ class="btn btn-warning btn-sm <%= disabled_class %>"
54
+
55
+ >
56
+ <%== icon(:pause) %>
57
+ </button>
58
+ </form>
@@ -0,0 +1,45 @@
1
+ <div class="col-span-12 flex justify-end space-x-2 mb-6">
2
+ <%
3
+ trigger_all_path = root_path('recurring_tasks', 'trigger_all')
4
+ disable_class = @tasks.empty? ? 'btn-disabled' : ''
5
+ %>
6
+ <form
7
+ action="<%= trigger_all_path %>"
8
+ method="post"
9
+ class="inline-flex items-center"
10
+ >
11
+ <%== csrf_tag(trigger_all_path) %>
12
+ <button type="submit" class="btn btn-primary btn-sm <%= disable_class %>">
13
+ <%== icon(:play) %>
14
+ Trigger All
15
+ </button>
16
+ </form>
17
+
18
+ <%
19
+ enable_all_path = root_path('recurring_tasks', 'enable_all')
20
+ disable_class = @tasks.all?(&:enabled?) ? 'btn-disabled' : ''
21
+ %>
22
+ <form
23
+ action="<%= enable_all_path %>"
24
+ method="post"
25
+ class="inline-flex items-center"
26
+ >
27
+ <%== csrf_tag(enable_all_path) %>
28
+ <button type="submit" class="btn btn-success btn-sm <%= disable_class %>">
29
+ <%== icon(:check) %>
30
+ Enable All
31
+ </button>
32
+ </form>
33
+
34
+ <%
35
+ disable_all_path = root_path('recurring_tasks', 'disable_all')
36
+ disable_class = @tasks.none?(&:enabled?) ? 'btn-disabled' : ''
37
+ %>
38
+ <form action="<%= disable_all_path %>" method="post" class="inline-flex items-center">
39
+ <%== csrf_tag(disable_all_path) %>
40
+ <button type="submit" class="btn btn-warning btn-sm <%= disable_class %>">
41
+ <%== icon(:pause) %>
42
+ Disable All
43
+ </button>
44
+ </form>
45
+ </div>
@@ -0,0 +1,22 @@
1
+ <li>
2
+ <a href="<%= root_path('recurring_tasks') %>">
3
+ Recurring Tasks
4
+ </a>
5
+ </li>
6
+
7
+ <% if current_path.include?('/schedule') %>
8
+ <li>
9
+ <a href="<%= root_path('recurring_tasks/schedule') %>">
10
+ Schedule
11
+ <%= @schedule ? @schedule.schedule_version : '' %>
12
+ </a>
13
+ </li>
14
+ <% end %>
15
+
16
+ <% if current_path.include?('/logs') %>
17
+ <li>
18
+ <a href="<%= root_path('recurring_tasks/logs') %>">
19
+ Logs
20
+ </a>
21
+ </li>
22
+ <% end %>
@@ -0,0 +1,26 @@
1
+ <% result = log.task[:result] == 'success' %>
2
+ <tr class="<%= result ? '' : 'status-row-error' %>">
3
+ <td>
4
+ <%= log.id %>
5
+ </td>
6
+ <td>
7
+ <% if result %>
8
+ <%== badge_success 'Success' %>
9
+ <% else %>
10
+ <%== badge_error 'Error' %>
11
+ <% end %>
12
+ </td>
13
+ <td>
14
+ <% if result %>
15
+ <%== log.task[:time_taken].round(2) %> ms
16
+ <% else %>
17
+ N/A
18
+ <% end %>
19
+ </td>
20
+ <td>
21
+ <%== relative_time log.dispatched_at %>
22
+ </td>
23
+ <td>
24
+ <%= log.schedule_version %>
25
+ </td>
26
+ </tr>
@@ -0,0 +1,12 @@
1
+ <% alert_box_info('Recurring Tasks Data Unavailable') do %>
2
+ <p class="mb-2">
3
+ We are unable to display data related to recurring tasks. Please ensure that:
4
+ </p>
5
+
6
+ <ul class="list-disc list-inside">
7
+ <li>Recurring Tasks are enabled in your routing configuration.</li>
8
+ <li>The necessary Kafka topics have been created.</li>
9
+ <li>A schedule has been defined.</li>
10
+ <li>our Karafka server, with schedule awareness, is running and has persisted the state.</li>
11
+ </ul>
12
+ <% end %>
@@ -0,0 +1,17 @@
1
+ <div class="col-span-12 mb-9">
2
+ <div class="tab-container">
3
+ <a
4
+ href="<%= root_path('recurring_tasks', 'schedule') %>"
5
+ class="custom-tab <%= nav_class(include: 'schedule') %>"
6
+ >
7
+ Schedules
8
+ </a>
9
+
10
+ <a
11
+ href="<%= root_path('recurring_tasks', 'logs') %>"
12
+ class="custom-tab <%= nav_class(include: '/logs') %>"
13
+ >
14
+ Logs
15
+ </a>
16
+ </div>
17
+ </div>
@@ -0,0 +1,46 @@
1
+ <tr class="<%= task.enabled? ? '' : 'status-row-warning text-muted' %>">
2
+ <td>
3
+ <%= task.id %>
4
+ </td>
5
+
6
+ <td>
7
+ <% if task.enabled? %>
8
+ <%== badge_success('Enabled') %>
9
+ <% else %>
10
+ <%== badge_warning('Disabled') %>
11
+ <% end %>
12
+ </td>
13
+
14
+ <td>
15
+ <code><%= task.cron %></code>
16
+ </td>
17
+
18
+ <td>
19
+ <% if task.previous_time.positive? %>
20
+ <%== relative_time task.previous_time %>
21
+ <% else %>
22
+ Never
23
+ <% end %>
24
+ </td>
25
+
26
+ <td>
27
+ <% if task.enabled? %>
28
+ <% if task.next_time < Time.now.to_i %>
29
+ Right now
30
+ <% else %>
31
+ <%== relative_time task.next_time %>
32
+ <% end %>
33
+ <% else %>
34
+ Never
35
+ <% end %>
36
+ </td>
37
+
38
+ <td class="commands-inline-3">
39
+ <%==
40
+ partial(
41
+ 'recurring_tasks/actions',
42
+ locals: { task: task }
43
+ )
44
+ %>
45
+ </td>
46
+ </tr>