karafka-web 0.10.2 → 0.10.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +17 -0
  4. data/Gemfile.lock +12 -10
  5. data/config/locales/pro_errors.yml +7 -1
  6. data/docker-compose.yml +1 -1
  7. data/karafka-web.gemspec +8 -2
  8. data/lib/karafka/web/config.rb +6 -1
  9. data/lib/karafka/web/contracts/config.rb +1 -0
  10. data/lib/karafka/web/management/actions/create_topics.rb +1 -1
  11. data/lib/karafka/web/pro/commanding/commands/{probe.rb → trace.rb} +3 -3
  12. data/lib/karafka/web/pro/commanding/listener.rb +1 -1
  13. data/lib/karafka/web/pro/commanding/manager.rb +2 -2
  14. data/lib/karafka/web/pro/commanding.rb +1 -1
  15. data/lib/karafka/web/pro/loader.rb +23 -2
  16. data/lib/karafka/web/pro/ui/app.rb +12 -2
  17. data/lib/karafka/web/pro/ui/controllers/commanding_controller.rb +4 -4
  18. data/lib/karafka/web/pro/ui/controllers/routing_controller.rb +18 -1
  19. data/lib/karafka/web/pro/ui/controllers/scheduled_messages/messages_controller.rb +60 -0
  20. data/lib/karafka/web/pro/ui/lib/branding/config.rb +40 -0
  21. data/lib/karafka/web/pro/ui/lib/branding/contracts/config.rb +56 -0
  22. data/lib/karafka/web/pro/ui/lib/branding.rb +36 -0
  23. data/lib/karafka/web/pro/ui/views/commands/_command.erb +1 -1
  24. data/lib/karafka/web/pro/ui/views/consumers/_consumer_controls.erb +3 -3
  25. data/lib/karafka/web/pro/ui/views/consumers/consumer/_commands.erb +4 -4
  26. data/lib/karafka/web/pro/ui/views/recurring_tasks/_actions.erb +2 -2
  27. data/lib/karafka/web/pro/ui/views/routing/_consumer_group.erb +2 -0
  28. data/lib/karafka/web/pro/ui/views/routing/_topic.erb +10 -0
  29. data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/_key.erb +9 -0
  30. data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/_message.erb +51 -13
  31. data/lib/karafka/web/pro/ui/views/shared/_navigation.erb +20 -15
  32. data/lib/karafka/web/pro/ui/views/shared/branding/_label.erb +20 -0
  33. data/lib/karafka/web/pro/ui/views/shared/branding/_notice.erb +13 -0
  34. data/lib/karafka/web/tracking/consumers/listeners/errors.rb +38 -9
  35. data/lib/karafka/web/tracking/producers/reporter.rb +1 -8
  36. data/lib/karafka/web/ui/helpers/tailwind_helper.rb +5 -4
  37. data/lib/karafka/web/ui/public/javascripts/application.js +1 -0
  38. data/lib/karafka/web/ui/public/javascripts/application.min.js +4 -3
  39. data/lib/karafka/web/ui/public/javascripts/application.min.js.br +0 -0
  40. data/lib/karafka/web/ui/public/javascripts/application.min.js.gz +0 -0
  41. data/lib/karafka/web/ui/public/javascripts/components/btn_toggle_manager.js +31 -11
  42. data/lib/karafka/web/ui/public/stylesheets/application.css +21 -0
  43. data/lib/karafka/web/ui/public/stylesheets/application.min.css +2 -1
  44. data/lib/karafka/web/ui/public/stylesheets/application.min.css.br +0 -0
  45. data/lib/karafka/web/ui/public/stylesheets/application.min.css.gz +0 -0
  46. data/lib/karafka/web/ui/public/stylesheets/libs/tailwind.css +69 -2
  47. data/lib/karafka/web/ui/views/layout.erb +5 -2
  48. data/lib/karafka/web/ui/views/shared/_brand.erb +3 -2
  49. data/lib/karafka/web/ui/views/shared/_navigation.erb +19 -15
  50. data/lib/karafka/web/ui/views/shared/alerts/_error.erb +3 -1
  51. data/lib/karafka/web/ui/views/shared/alerts/_info.erb +1 -1
  52. data/lib/karafka/web/ui/views/shared/alerts/_primary.erb +3 -1
  53. data/lib/karafka/web/ui/views/shared/alerts/_secondary.erb +3 -1
  54. data/lib/karafka/web/ui/views/shared/alerts/_success.erb +3 -1
  55. data/lib/karafka/web/ui/views/shared/alerts/_warning.erb +3 -1
  56. data/lib/karafka/web/ui/views/shared/icons/_chevron.erb +4 -0
  57. data/lib/karafka/web/version.rb +1 -1
  58. data/lib/karafka/web.rb +6 -1
  59. data/package-lock.json +40 -35
  60. data.tar.gz.sig +0 -0
  61. metadata +13 -15
  62. metadata.gz.sig +0 -0
  63. data/.coditsu/ci.yml +0 -3
  64. data/.diffend.yml +0 -3
  65. data/.github/FUNDING.yml +0 -1
  66. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -50
  67. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  68. data/.github/workflows/ci.yml +0 -133
  69. data/.gitignore +0 -71
  70. data/.rspec +0 -1
  71. data/.ruby-gemset +0 -1
  72. data/.ruby-version +0 -1
@@ -10,6 +10,7 @@
10
10
  <th>Subscription group</th>
11
11
  <th><%== sort_link('Topic', :name) %></th>
12
12
  <th>Type</th>
13
+ <th>Assigned</th>
13
14
  <th><%== sort_link(:active?) %></th>
14
15
  <th></th>
15
16
  </tr>
@@ -21,6 +22,7 @@
21
22
  'routing/topic',
22
23
  locals: {
23
24
  subscription_group: topic.subscription_group,
25
+ consumer_group: consumer_group.id,
24
26
  topic: topic
25
27
  }
26
28
  )
@@ -20,6 +20,16 @@
20
20
  <% end %>
21
21
  </td>
22
22
 
23
+ <td>
24
+ <% assigned = @assigned[consumer_group].include?(topic.name) %>
25
+
26
+ <% if assigned %>
27
+ <%== badge_success assigned %>
28
+ <% else %>
29
+ <%== badge_secondary assigned %>
30
+ <% end %>
31
+ </td>
32
+
23
33
  <td>
24
34
  <% if topic.active? %>
25
35
  <%== badge_success topic.active? %>
@@ -0,0 +1,9 @@
1
+ <% if @visibility_filter.key?(message) %>
2
+ <% if safe_key.success? %>
3
+ <%= safe_key.result %>
4
+ <% else %>
5
+ <span class="text-muted">[Deserialization Failed]</span>
6
+ <% end %>
7
+ <% else %>
8
+ <span class="text-muted">[Filtered]</span>
9
+ <% end %>
@@ -39,8 +39,22 @@
39
39
  </td>
40
40
 
41
41
  <% if type == 'cancel' %>
42
- <td colspan="5" class="text-center text-muted">
43
- Cancellation messages do not contain those details.
42
+ <td>
43
+ <%== relative_time(message.timestamp) %>
44
+ </td>
45
+ <td class="text-center text-muted">
46
+ N/A
47
+ </td>
48
+ <td>
49
+ <%==
50
+ partial(
51
+ 'scheduled_messages/explorer/key',
52
+ locals: { message: message, safe_key: safe_key }
53
+ )
54
+ %>
55
+ </td>
56
+ <td colspan="2" class="text-center text-muted">
57
+ N/A
44
58
  </td>
45
59
  <% else %>
46
60
  <td>
@@ -50,15 +64,12 @@
50
64
  <%== relative_time message.headers['schedule_target_epoch'].to_i %>
51
65
  </td>
52
66
  <td>
53
- <% if @visibility_filter.key?(message) %>
54
- <% if safe_key.success? %>
55
- <%= safe_key.result %>
56
- <% else %>
57
- <span class="text-muted">[Deserialization Failed]</span>
58
- <% end %>
59
- <% else %>
60
- <span class="text-muted">[Filtered]</span>
61
- <% end %>
67
+ <%==
68
+ partial(
69
+ 'scheduled_messages/explorer/key',
70
+ locals: { message: message, safe_key: safe_key }
71
+ )
72
+ %>
62
73
  </td>
63
74
  <td>
64
75
  <%= message.headers['schedule_target_topic'] %>
@@ -73,10 +84,37 @@
73
84
  <% end %>
74
85
 
75
86
  <td>
87
+ <% if type == 'schedule' %>
88
+ <%
89
+ cancel_path = root_path(
90
+ "scheduled_messages/messages",
91
+ message.topic,
92
+ message.partition,
93
+ message.offset,
94
+ 'cancel'
95
+ )
96
+ %>
97
+ <form
98
+ action="<%= cancel_path %>"
99
+ method="post"
100
+ class="inline"
101
+ >
102
+ <%== csrf_tag(cancel_path) %>
103
+ <button
104
+ type="submit"
105
+ class="btn btn-error btn-sm confirm-action"
106
+ title="Cancel dispatch of this scheduled message"
107
+ >
108
+ <%== icon(:x_circle) %>
109
+ </button>
110
+ </form>
111
+ <% end %>
112
+
76
113
  <%==
77
114
  link_button_primary_sm(
78
- 'Details',
79
- explorer_path(message.topic, message.partition, message.offset)
115
+ icon(:info_circle),
116
+ explorer_path(message.topic, message.partition, message.offset),
117
+ title: 'Display details of this message'
80
118
  )
81
119
  %>
82
120
  </td>
@@ -1,14 +1,15 @@
1
1
  <nav class="sidebar-nav">
2
2
  <%== partial 'shared/brand' %>
3
+ <%== partial 'shared/branding/label' %>
3
4
 
4
- <ul class="menu">
5
+ <ul class="menu grow">
5
6
  <li>
6
7
  <a
7
8
  class="sidebar-nav-item <%= nav_class(start_with: '/dashboard') %>"
8
9
  href="<%= root_path('dashboard') %>"
9
10
  >
10
11
  <%== icon(:home) %>
11
- Dashboard
12
+ <span class="sidebar-nav-item-label">Dashboard</span>
12
13
  </a>
13
14
  </li>
14
15
 
@@ -18,7 +19,7 @@
18
19
  href="<%= root_path('consumers') %>"
19
20
  >
20
21
  <%== icon(:cpu) %>
21
- Consumers
22
+ <span class="sidebar-nav-item-label">Consumers</span>
22
23
  </a>
23
24
  </li>
24
25
 
@@ -28,7 +29,7 @@
28
29
  href="<%= root_path('jobs/running') %>"
29
30
  >
30
31
  <%== icon(:arrow_on_squares) %>
31
- Jobs
32
+ <span class="sidebar-nav-item-label">Jobs</span>
32
33
  </a>
33
34
  </li>
34
35
 
@@ -38,7 +39,7 @@
38
39
  href="<%= root_path('health/overview') %>"
39
40
  >
40
41
  <%== icon(:heart) %>
41
- Health
42
+ <span class="sidebar-nav-item-label">Health</span>
42
43
  </a>
43
44
  </li>
44
45
 
@@ -48,7 +49,7 @@
48
49
  href="<%= root_path('routing') %>"
49
50
  >
50
51
  <%== icon(:queue_list) %>
51
- Routing
52
+ <span class="sidebar-nav-item-label">Routing</span>
52
53
  </a>
53
54
  </li>
54
55
 
@@ -58,7 +59,7 @@
58
59
  href="<%= root_path('explorer') %>"
59
60
  >
60
61
  <%== icon(:document_glass) %>
61
- Explorer
62
+ <span class="sidebar-nav-item-label">Explorer</span>
62
63
  </a>
63
64
  </li>
64
65
 
@@ -68,7 +69,7 @@
68
69
  href="<%= root_path('recurring_tasks') %>"
69
70
  >
70
71
  <%== icon(:arrow_path_rounded) %>
71
- Cron
72
+ <span class="sidebar-nav-item-label">Cron</span>
72
73
  </a>
73
74
  </li>
74
75
 
@@ -78,7 +79,7 @@
78
79
  href="<%= root_path('scheduled_messages') %>"
79
80
  >
80
81
  <%== icon(:calendar) %>
81
- Schedules
82
+ <span class="sidebar-nav-item-label">Schedules</span>
82
83
  </a>
83
84
  </li>
84
85
 
@@ -88,7 +89,7 @@
88
89
  href="<%= root_path('errors') %>"
89
90
  >
90
91
  <%== icon(:bug) %>
91
- Errors
92
+ <span class="sidebar-nav-item-label">Errors</span>
92
93
  </a>
93
94
  </li>
94
95
 
@@ -98,7 +99,7 @@
98
99
  href="<%= root_path('dlq') %>"
99
100
  >
100
101
  <%== icon(:x_circle) %>
101
- Dead
102
+ <span class="sidebar-nav-item-label">Dead</span>
102
103
  </a>
103
104
  </li>
104
105
 
@@ -108,7 +109,7 @@
108
109
  href="<%= root_path('cluster') %>"
109
110
  >
110
111
  <%== icon(:blocks) %>
111
- Cluster
112
+ <span class="sidebar-nav-item-label">Cluster</span>
112
113
  </a>
113
114
  </li>
114
115
 
@@ -118,7 +119,7 @@
118
119
  href="<%= root_path('topics') %>"
119
120
  >
120
121
  <%== icon(:list_bullets) %>
121
- Topics
122
+ <span class="sidebar-nav-item-label">Topics</span>
122
123
  </a>
123
124
  </li>
124
125
 
@@ -128,7 +129,7 @@
128
129
  href="<%= root_path('status') %>"
129
130
  >
130
131
  <%== icon(:check_badge) %>
131
- Status
132
+ <span class="sidebar-nav-item-label">Status</span>
132
133
  </a>
133
134
  </li>
134
135
 
@@ -138,8 +139,12 @@
138
139
  href="<%= root_path('support') %>"
139
140
  >
140
141
  <%== icon(:lifebuoy) %>
141
- Support
142
+ <span class="sidebar-nav-item-label">Support</span>
142
143
  </a>
143
144
  </li>
144
145
  </ul>
146
+
147
+ <button type="button" class="btn-toggle-nav-collapsed grid gap-2 grid-flow-col items-center justify-start ml-6 text-sm invisible lg:visible" data-toggle-target="drawer-side">
148
+ <%== icon(:chevron) %>
149
+ </button>
145
150
  </nav>
@@ -0,0 +1,20 @@
1
+ <% branding_cfg = Karafka::Web.config.ui.branding %>
2
+ <% if branding_cfg.label %>
3
+ <div class="branding-label text-left mt-4 ml-6">
4
+ <%==
5
+ public_send(
6
+ :"badge_#{branding_cfg.type}",
7
+ branding_cfg.label
8
+ )
9
+ %>
10
+ </div>
11
+
12
+ <div class="branding-mark text-left mt-4 ml-6">
13
+ <%==
14
+ public_send(
15
+ :"badge_#{branding_cfg.type}",
16
+ branding_cfg.label[0]
17
+ )
18
+ %>
19
+ </div>
20
+ <% end %>
@@ -0,0 +1,13 @@
1
+ <% branding_cfg = Karafka::Web.config.ui.branding %>
2
+ <% if branding_cfg.notice %>
3
+ <div id="branding" class="col-span-12">
4
+ <div class="mb-3">
5
+ <%==
6
+ public_send(
7
+ :"alert_#{branding_cfg.type}",
8
+ branding_cfg.notice
9
+ )
10
+ %>
11
+ </div>
12
+ </div>
13
+ <% end %>
@@ -18,17 +18,24 @@ module Karafka
18
18
  #
19
19
  # @param event [Karafka::Core::Monitoring::Event]
20
20
  def on_error_occurred(event)
21
- track do |sampler|
22
- # Collect extra info if it was a consumer related error.
23
- # Those come from user code
24
- details = if event[:caller].is_a?(Karafka::BaseConsumer)
25
- extract_consumer_info(event[:caller])
26
- else
27
- {}
28
- end
21
+ caller_ref = event[:caller]
22
+
23
+ # Collect extra info if it was a consumer related error.
24
+ # Those come from user code
25
+ details = case caller_ref
26
+ when Karafka::BaseConsumer
27
+ extract_consumer_info(caller_ref)
28
+ when Karafka::Connection::Client
29
+ extract_client_info(caller_ref)
30
+ when Karafka::Connection::Listener
31
+ extract_listener_info(caller_ref)
32
+ else
33
+ {}
34
+ end
29
35
 
30
- error_class, error_message, backtrace = extract_error_info(event[:error])
36
+ error_class, error_message, backtrace = extract_error_info(event[:error])
31
37
 
38
+ track do |sampler|
32
39
  sampler.errors << {
33
40
  schema_version: SCHEMA_VERSION,
34
41
  type: event[:type],
@@ -70,6 +77,7 @@ module Karafka
70
77
  {
71
78
  topic: consumer.topic.name,
72
79
  consumer_group: consumer.topic.consumer_group.id,
80
+ subscription_group: consumer.topic.subscription_group.id,
73
81
  partition: consumer.partition,
74
82
  first_offset: consumer.messages.metadata.first_offset,
75
83
  last_offset: consumer.messages.metadata.last_offset,
@@ -80,6 +88,27 @@ module Karafka
80
88
  tags: consumer.tags
81
89
  }
82
90
  end
91
+
92
+ # @param client [::Karafka::Connection::Client]
93
+ # @return [Hash] hash with client specific info for details of error
94
+ def extract_client_info(client)
95
+ {
96
+ consumer_group: client.subscription_group.consumer_group.id,
97
+ subscription_group: client.subscription_group.id,
98
+ name: client.name,
99
+ id: client.id
100
+ }
101
+ end
102
+
103
+ # @param listener [::Karafka::Connection::Listener]
104
+ # @return [Hash] hash with listener specific info for details of error
105
+ def extract_listener_info(listener)
106
+ {
107
+ consumer_group: listener.subscription_group.consumer_group.id,
108
+ subscription_group: listener.subscription_group.id,
109
+ id: listener.id
110
+ }
111
+ end
83
112
  end
84
113
  end
85
114
  end
@@ -10,13 +10,6 @@ module Karafka
10
10
  # because there is no expectation on immediate status updates for producers and their
11
11
  # dispatch flow is always periodic based.
12
12
  class Reporter < Tracking::Reporter
13
- # Minimum number of messages to produce to produce them in sync mode
14
- # This acts as a small back-off not to overload the system in case we would have
15
- # extremely big number of errors happening
16
- PRODUCE_SYNC_THRESHOLD = 25
17
-
18
- private_constant :PRODUCE_SYNC_THRESHOLD
19
-
20
13
  # This mutex is shared between tracker and samplers so there is no case where metrics
21
14
  # would be collected same time tracker reports
22
15
  MUTEX = Mutex.new
@@ -82,7 +75,7 @@ module Karafka
82
75
  # normal operations we should not have that many messages to dispatch and it should not
83
76
  # slowdown any processing.
84
77
  def produce(messages)
85
- if messages.count >= PRODUCE_SYNC_THRESHOLD
78
+ if messages.count >= ::Karafka::Web.config.tracking.producers.sync_threshold
86
79
  ::Karafka::Web.producer.produce_many_sync(messages)
87
80
  else
88
81
  ::Karafka::Web.producer.produce_many_async(messages)
@@ -34,9 +34,10 @@ module Karafka
34
34
  # @param name [String] button name
35
35
  # @param path [String] path to where to go
36
36
  # @param classes [String] extra css classes
37
+ # @param title [String, nil] title (if any)
37
38
  # @return [String] button link html
38
- def link_button(name, path, classes: '')
39
- %(<a href="#{path}" class="btn #{classes}">#{name}</a>)
39
+ def link_button(name, path, classes: '', title: nil)
40
+ %(<a href="#{path}" class="btn #{classes}" title="#{title}">#{name}</a>)
40
41
  end
41
42
 
42
43
  # Defines various methods for badges and links that simplify defining them without
@@ -54,8 +55,8 @@ module Karafka
54
55
  link_button(name, path, classes: "#{classes} btn-#{type}")
55
56
  end
56
57
 
57
- define_method :"link_button_#{type}_sm" do |name, path, classes: ''|
58
- link_button(name, path, classes: "#{classes} btn-#{type} btn-sm")
58
+ define_method :"link_button_#{type}_sm" do |name, path, classes: '', title: nil|
59
+ link_button(name, path, classes: "#{classes} btn-#{type} btn-sm", title: title)
59
60
  end
60
61
 
61
62
  # @param message [String] alert message
@@ -110,6 +110,7 @@ function addListeners() {
110
110
  loadOffsetLookupDatePicker();
111
111
 
112
112
  new BtnToggleManager();
113
+ new BtnToggleManager('.btn-toggle-nav-collapsed', 'collapsed');
113
114
  new ThemeManager();
114
115
 
115
116
  refreshTitle()
@@ -51,7 +51,7 @@ function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"f
51
51
  /*! Source: lib/karafka/web/ui/public/javascripts/charts/data_formatting_utility.js */
52
52
  let DataFormattingUtils={niceBytes(e,t=2){let i=0,s=parseInt(e,10)||0;for(;1024<=s&&++i;)s/=1024;return s.toFixed(s<10&&0<i?1:t)+" "+["bytes","KB","MB","GB","TB","PB","EB","ZB","YB"][i]},formatLabelX(e,t){return"date"!==t?e:("00"+((t=new Date(1e3*e)).getMonth()+1)).slice(-2)+"/"+("00"+t.getDate()).slice(-2)+"/"+t.getFullYear()+" "+("00"+t.getHours()).slice(-2)+":"+("00"+t.getMinutes()).slice(-2)+":"+("00"+t.getSeconds()).slice(-2)},formatTooltip(e,t){var i=t.parsed.y,s=t.dataset.label;switch(e){case"percentage":return Math.floor(i)===i?s+": "+i+" %":s+": "+Math.round(100*i)/100+" %";case"memory":return s+": "+DataFormattingUtils.niceBytes(1024*i,2);default:return t.yLabel}},formatLabelY(e,t){switch(e){case"percentage":return Math.floor(t)===t?t+"%":Math.round(100*t)/100+"%";case"memory":return DataFormattingUtils.niceBytes(1024*t,1);default:return Math.floor(t)===t?t:Math.round(100*t)/100}},isFractionalPrecision(e){return e!==Math.floor(e)}};
53
53
  /*! Source: lib/karafka/web/ui/public/javascripts/charts/dataset_state_manager.js */class DatasetStateManager{constructor(){this.storageKey="karafkaDisabledDatasets"}readAll(){var e=localStorage.getItem(this.storageKey);return e?JSON.parse(e):{}}saveAll(e){localStorage.setItem(this.storageKey,JSON.stringify(e))}saveCurrent(){var e=document.querySelectorAll(".chartjs"),t=window.location.href.split("?")[0];let i={};var s=this.readAll();e.forEach(e=>{var e=e.id,t=Chart.getChart(e);t&&t.legend&&t.legend.legendItems&&0<(t=t.legend.legendItems.map((e,t)=>e.hidden?t:null).filter(e=>null!==e)).length&&(i[e]=t)}),s[t]=i,this.saveAll(s)}getCurrentChart(e){var t=window.location.href.split("?")[0];return(this.readAll()[t]||{})[e]||[]}}
54
- /*! Source: lib/karafka/web/ui/public/javascripts/components/btn_toggle_manager.js */class BtnToggleManager{constructor(){this.init()}init(){document.querySelectorAll(".btn-toggle").forEach(t=>{let i=t.getAttribute("data-toggle-target"),s=document.getElementById(i);s&&(this.restoreVisibility(t,s),t.addEventListener("click",()=>{var e=!s.classList.contains("hidden");s.classList.toggle("hidden"),t.classList.toggle("active",!e),this.saveVisibility(i,!e)}))})}saveVisibility(e,t){localStorage.setItem(e+"_visibility",t)}restoreVisibility(e,t){var i=localStorage.getItem(t.id+"_visibility"),i=i?"true"===i:!t.classList.contains("hidden");t.classList.toggle("hidden",!i),e.classList.toggle("active",i)}}
54
+ /*! Source: lib/karafka/web/ui/public/javascripts/components/btn_toggle_manager.js */class BtnToggleManager{constructor(e=".btn-toggle",t="hidden"){this.btnClass=e,this.visibilityClass=t,this.init()}init(){document.querySelectorAll(this.btnClass).forEach(t=>{let i=t.getAttribute("data-toggle-target"),s=document.getElementById(i);s&&(this.restoreVisibility(t,s),t._isClickListenerAdded||(t.addEventListener("click",()=>{var e=!s.classList.contains(this.visibilityClass);s.classList.toggle(this.visibilityClass),t.classList.toggle("active",!e),this.saveVisibility(i,!e)}),t._isClickListenerAdded=!0))})}saveVisibility(e,t){localStorage.setItem(e+"_visibility",t)}restoreVisibility(e,t){var i=localStorage.getItem(t.id+"_visibility"),i=i?"true"===i:!t.classList.contains(this.visibilityClass);t.classList.toggle(this.visibilityClass,!i),e.classList.toggle("active",i)}}
55
55
  /*! Source: lib/karafka/web/ui/public/javascripts/components/charts.js */function refreshCharts(e){(new LineChartsManager).refreshAndRender(e,!0),(new BarChartManager).refreshAndRenderBarCharts(e,!0)}function manageCharts(){(new LineChartsManager).refreshAndRender(document,!1),(new BarChartManager).refreshAndRenderBarCharts(document,!1)}
56
56
  /*! Source: lib/karafka/web/ui/public/javascripts/components/live_poll.js */var livePollTimer=null,oldDOM=null,datePicker=null,startURL=window.location.href;let isHoveringOverClickable=!1;function initLivePolling(){document.addEventListener("mouseover",function(e){isElementClickable(e.target)&&(isHoveringOverClickable=!0)}),document.addEventListener("mouseout",function(e){isElementClickable(e.target)&&(isHoveringOverClickable=!1)}),null==localStorage.karafkaLivePoll&&(localStorage.karafkaLivePoll="enabled")}function isFormActive(){var e=document.activeElement;return["INPUT","TEXTAREA","SELECT","BUTTON","FIELDSET"].includes(e.tagName)}function isElementClickable(e){return!!("A"===e.tagName||"BUTTON"===e.tagName||"INPUT"===e.tagName&&("button"===e.type||"submit"===e.type)||e.hasAttribute("onclick")||"function"==typeof e.onclick||"SPAN"===e.tagName&&e.closest(".tab-container")||e.closest("button")||e.closest("a"))}function isUserHoveringOverClickable(){return isHoveringOverClickable}function isAnyTextSelected(){var e="";return void 0!==window.getSelection?e=window.getSelection().toString():void 0!==document.selection&&"Text"==document.selection.type&&(e=document.selection.createRange().text),""!=e}function isCollapsingHappening(){return 0<document.querySelectorAll(".collapsing").length}function isPollingPossible(e=!1){return!(isFormActive()||isUserHoveringOverClickable()||isAnyTextSelected()||isOffsetLookupCalendarVisible()||isAnyModalOpen()||isCollapsingHappening()||isTurboOperating()||e&&startURL!=window.location.href)}function isAnyModalOpen(){var e;for(e of document.querySelectorAll("dialog"))if(e.open)return!0;return!1}function bindPollingButtonClick(){var e=document.getElementById("live-poll");null!=e&&e.addEventListener("click",handleLivePollingButtonClick)}function handleLivePollingButtonClick(){toggleLivePollState(),setLivePollButton(),setPollingListener()}function toggleLivePollState(){"enabled"==localStorage.karafkaLivePoll?localStorage.karafkaLivePoll="disabled":localStorage.karafkaLivePoll="enabled"}function setLivePollButton(){null!=(selector=document.getElementById("live-poll"))&&("enabled"==localStorage.karafkaLivePoll?(selector.classList.add("text-base-content"),selector.classList.remove("text-gray-500")):(selector.classList.add("text-gray-500"),selector.classList.remove("text-base-content")))}function checkResponse(e){if(e.ok)return e;throw e}function refreshPage(e){if(!isPollingPossible())return!1;var t,e=(new DOMParser).parseFromString(e,"text/html"),i=e.getElementById("content");oldDOM!=i.innerHTML&&(t=document.querySelectorAll(".chartjs").length,0==i.querySelectorAll(".chartjs").length||0==t?(document.getElementById("content").replaceWith(i),addListeners()):(t=e.getElementById("refreshable"),document.getElementById("refreshable").replaceWith(t),refreshCharts(e)),oldDOM=i.innerHTML)}function showError(e){console.error(e)}function scheduleLivePoll(){null==oldDOM&&(oldDOM=document.getElementById("content").innerHTML);let e=parseInt(localStorage.karafkaTimeInterval)||5e3;e<1e3&&(localStorage.karafkaTimeInterval=5e3,e=5e3),livePollTimer=setTimeout(livePollCallback,e)}function livePollCallback(){clearTimeout(livePollTimer),livePollTimer=null,isPollingPossible(!1)?(startURL=window.location.href,fetch(window.location.href).then(checkResponse).then(e=>e.text()).then(refreshPage).catch(showError).finally(setPollingListener)):setPollingListener()}function setPollingListener(){var e=document.getElementById("live-poll"),t=localStorage.karafkaLivePoll;"disabled"==t||null==t||null==e?(clearTimeout(livePollTimer),livePollTimer=null):(clearTimeout(livePollTimer),scheduleLivePoll())}
57
57
  /*! Source: lib/karafka/web/ui/public/javascripts/components/offset_datetime.js */function loadOffsetLookupDatePicker(){var e={locale:{days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],daysShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],daysMin:["Su","Mo","Tu","We","Th","Fr","Sa"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],monthsShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],today:"Today",clear:"Clear",dateFormat:"yyyy-MM-dd",timeFormat:"HH:mm",firstDay:1},timepicker:!0,onSelect:({})=>{document.getElementById("offset-lookup-datepicker").value=""},onShow:function(){offsetLookupDatePicker.selectDate((new Date).getTime()),offsetLookupDatePicker.maxDate=new Date},onHide:function(){offsetLookupDatePicker.selectDate((new Date).getTime())},buttons:[{content(e){return"Go to offset"},onClick(e){var t=e.selectedDates[0]||new Date,i=e.$el.dataset.target;e.hide(),location.href=i+"/"+formatRedirectDateTime(t)}}]};null!=offsetLookupDatePicker&&offsetLookupDatePicker.destroy(),null!=document.getElementById("offset-lookup-datepicker")&&((offsetLookupDatePicker=new AirDatepicker("#offset-lookup-datepicker",e)).maxDate=new Date,offsetLookupDatePicker.selectDate((new Date).getTime()))}function formatRedirectDateTime(e){return e.getFullYear()+`-${String(e.getMonth()+1).padStart(2,"0")}-${String(e.getDate()).padStart(2,"0")}/${String(e.getHours()).padStart(2,"0")}:`+String(e.getMinutes()).padStart(2,"0")}function isOffsetLookupCalendarVisible(){return null!=offsetLookupDatePicker&&offsetLookupDatePicker.visible}
@@ -59,6 +59,7 @@ let DataFormattingUtils={niceBytes(e,t=2){let i=0,s=parseInt(e,10)||0;for(;1024<
59
59
  /*! Source: lib/karafka/web/ui/public/javascripts/components/tabs_manager.js */class TabsManager{constructor(){this.storageKey="karafkaActiveTabsv2"}readAllActiveTabs(){var e=localStorage.getItem(this.storageKey);return e?JSON.parse(e):{}}saveAllActiveTabs(e){localStorage.setItem(this.storageKey,JSON.stringify(e))}saveCurrentActiveTabs(){var e=document.querySelectorAll(".inline-tabs > .active"),t=window.location.href.split("?")[0];let i=[];var s=this.readAllActiveTabs();e.forEach(e=>{i.push(e.id)}),s[t]=i,this.saveAllActiveTabs(s)}setActiveTabs(){var e=window.location.href.split("?")[0],e=this.readAllActiveTabs()[e];(e=e||Array.from(document.querySelectorAll(".inline-tabs > .active")).map(e=>e.id))&&e.forEach(e=>{e=document.getElementById(e);e&&(e.parentElement.querySelectorAll(".custom-tab").forEach(function(e){e.classList.remove("active")}),e.classList.add("active"),document.getElementById(e.getAttribute("data-target")).classList.remove("hidden"))})}manageTabs(){this.setActiveTabs();var t=this;document.querySelectorAll(".inline-tabs > .custom-tab").forEach(function(e){e.addEventListener("click",function(e){this.parentElement.querySelectorAll(".custom-tab").forEach(function(e){e.classList.remove("active");e=e.getAttribute("data-target");document.getElementById(e).classList.add("hidden")}),this.classList.add("active"),t.saveCurrentActiveTabs(),t.setActiveTabs()})})}}
60
60
  /*! Source: lib/karafka/web/ui/public/javascripts/components/theme_manager.js */class ThemeManager{constructor(){this.themeSelectorButton=document.getElementById("theme-selector"),this.themeSelectorLight=document.getElementById("theme-selector-light"),this.themeSelectorDark=document.getElementById("theme-selector-dark"),this.init()}init(){this.lightThemeLink=document.querySelector(".highlight-light"),this.darkThemeLink=document.querySelector(".highlight-dark"),this.lightThemeLink&&this.darkThemeLink&&this.themeSelectorButton?(this.restoreTheme(),this.bindThemeSelectorButton()):console.error("Theme CSS links or theme selector button not found")}bindThemeSelectorButton(){this.themeSelectorButton&&!this.themeSelectorButton.dataset.bound&&(this.themeSelectorButton.addEventListener("click",()=>{this.toggleTheme()}),this.themeSelectorButton.dataset.bound="true")}setTheme(e){document.documentElement.setAttribute("data-theme",e),"dark"===e?(this.lightThemeLink.disabled=!0,this.darkThemeLink.disabled=!1,this.themeSelectorLight.classList.add("hidden"),this.themeSelectorDark.classList.remove("hidden")):(this.lightThemeLink.disabled=!1,this.darkThemeLink.disabled=!0,this.themeSelectorLight.classList.remove("hidden"),this.themeSelectorDark.classList.add("hidden")),localStorage.setItem("theme",e)}toggleTheme(){var e=document.documentElement.getAttribute("data-theme");this.setTheme("dark"===e?"corporate":"dark")}restoreTheme(){var e=localStorage.getItem("theme"),e=e||(window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"corporate");this.setTheme(e),window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",e=>{localStorage.getItem("theme")||this.setTheme(e.matches?"dark":"corporate")})}}
61
61
  /*! Source: lib/karafka/web/ui/public/javascripts/components/turbo_tracker.js */let turboIsOperating=!1;function isTurboOperating(){return turboIsOperating}
62
- /*! Source: lib/karafka/web/ui/public/javascripts/application.js */function updateTimeAgo(){0!=(e=document.querySelectorAll("time")).length&&(timeago.render(e),timeago.cancel());for(var e=document.getElementsByClassName("time-title"),t=0;t<e.length;t++){var i=e[t],s=i.getAttribute("title");i.setAttribute("title",timeago.format(s))}}function refreshTitle(){var e=document.querySelectorAll(".breadcrumbs a"),e=Array.from(e).slice(1).map(e=>e.textContent.trim());0<e.length?document.title=e.join(" > ")+" - Karafka Web UI":document.title="Karafka Web UI"}function redirectToPartition(){var e=document.getElementById("current-partition");null!=e&&e.addEventListener("change",function(){Turbo.visit(this.value)})}function bindActionsConfirmations(){for(var t=document.getElementsByClassName("confirm-action"),i=0;i<t.length;i++){var s=t[i];let e="click";"FORM"===s.nodeName&&(e="submit"),s.addEventListener(e,function(e){window.confirm("Are you sure?")||e.preventDefault()})}}function bindLockableButtons(){document.querySelectorAll(".btn-lockable").forEach(function(i){i.addEventListener("click",function(e){var t=i.closest("form");t?t.addEventListener("submit",function(){i.disabled=!0,i.textContent+="...",document.querySelectorAll(".modal").forEach(function(e){e.classList.add("modal-locked")})},{once:!0}):(i.disabled=!0,i.textContent+="...")})})}function addListeners(){initLivePolling(),bindPollingButtonClick(),bindLockableButtons(),setLivePollButton(),setPollingListener(),hljs.highlightAll(),updateTimeAgo(),redirectToPartition(),(new TabsManager).manageTabs(),manageCharts(),bindActionsConfirmations(),loadOffsetLookupDatePicker(),new BtnToggleManager,new ThemeManager,refreshTitle(),new SearchMetadataVisibilityManager,new SearchModalManager}document.addEventListener("turbo:visit",function(){turboIsOperating=!0}),document.addEventListener("turbo:before-fetch-request",function(){turboIsOperating=!0}),document.addEventListener("turbo:before-fetch-response",function(){turboIsOperating=!0}),document.addEventListener("turbo:load",function(){turboIsOperating=!1}),document.addEventListener("turbo:frame-load",function(){turboIsOperating=!1}),document.addEventListener("turbo:frame-render",function(){turboIsOperating=!1}),document.addEventListener("turbo:load",addListeners),Turbo.setProgressBarDelay(100)
62
+ /*! Source: lib/karafka/web/ui/public/javascripts/application.js */function updateTimeAgo(){0!=(e=document.querySelectorAll("time")).length&&(timeago.render(e),timeago.cancel());for(var e=document.getElementsByClassName("time-title"),t=0;t<e.length;t++){var i=e[t],s=i.getAttribute("title");i.setAttribute("title",timeago.format(s))}}function refreshTitle(){var e=document.querySelectorAll(".breadcrumbs a"),e=Array.from(e).slice(1).map(e=>e.textContent.trim());0<e.length?document.title=e.join(" > ")+" - Karafka Web UI":document.title="Karafka Web UI"}function redirectToPartition(){var e=document.getElementById("current-partition");null!=e&&e.addEventListener("change",function(){Turbo.visit(this.value)})}function bindActionsConfirmations(){for(var t=document.getElementsByClassName("confirm-action"),i=0;i<t.length;i++){var s=t[i];let e="click";"FORM"===s.nodeName&&(e="submit"),s.addEventListener(e,function(e){window.confirm("Are you sure?")||e.preventDefault()})}}function bindLockableButtons(){document.querySelectorAll(".btn-lockable").forEach(function(i){i.addEventListener("click",function(e){var t=i.closest("form");t?t.addEventListener("submit",function(){i.disabled=!0,i.textContent+="...",document.querySelectorAll(".modal").forEach(function(e){e.classList.add("modal-locked")})},{once:!0}):(i.disabled=!0,i.textContent+="...")})})}function addListeners(){initLivePolling(),bindPollingButtonClick(),bindLockableButtons(),setLivePollButton(),setPollingListener(),hljs.highlightAll(),updateTimeAgo(),redirectToPartition(),(new TabsManager).manageTabs(),manageCharts(),bindActionsConfirmations(),loadOffsetLookupDatePicker(),new BtnToggleManager,new BtnToggleManager(".btn-toggle-nav-collapsed","collapsed"),new ThemeManager,refreshTitle(),new SearchMetadataVisibilityManager,new SearchModalManager}document.addEventListener("turbo:visit",function(){turboIsOperating=!0}),document.addEventListener("turbo:before-fetch-request",function(){turboIsOperating=!0}),document.addEventListener("turbo:before-fetch-response",function(){turboIsOperating=!0}),document.addEventListener("turbo:load",function(){turboIsOperating=!1}),document.addEventListener("turbo:frame-load",function(){turboIsOperating=!1}),document.addEventListener("turbo:frame-render",function(){turboIsOperating=!1}),document.addEventListener("turbo:load",addListeners),Turbo.setProgressBarDelay(100)
63
63
  /*! Source: lib/karafka/web/ui/public/javascripts/charts/types/bar.js */;class BarChartManager{constructor(){this.datasetStateManager=new DatasetStateManager}refreshAndRenderBarCharts(c,u=!1){(u?c:document).querySelectorAll(".chartjs-bar").forEach(e=>{var t=e.id,i=(u?c:document).getElementById(t),s=JSON.parse(i.dataset.datasets);let r=[],a=[],n=[];var o=i.dataset.label_type_y;let l=i.dataset.label_type_x,h=this.datasetStateManager.getCurrentChart(t);Object.entries(s).forEach(([e,t],i)=>{t.forEach(([e,t])=>{n.push(t),0===i&&r.push(DataFormattingUtils.formatLabelX(e,l))}),a.push({data:t.map(([,e])=>e),label:e,hidden:h.includes(i),borderWidth:2.5})});var i=Math.min(...n),s=Math.max(...n),i=Math.round(i-.1*i),s=Math.round(s+.005*s),d=Math.round(n.reduce((e,t)=>e+t,0)/n.length);a.push({type:"line",label:"Average",data:new Array(r.length).fill(d),borderWidth:2,fill:!1,pointRadius:0,hoverBorderWidth:3,pointHitRadius:20}),u?((d=Chart.getChart(t)).data.datasets=a,d.data.labels=r,d.options.scales.y.min=i,d.options.scales.y.max=s,d.update("none")):this.renderBarChart(e,r,a,i,s,o)})}renderBarChart(e,t,i,s,r,a){new Chart(e,{type:"bar",data:{labels:t,datasets:i},options:{responsive:!0,maintainAspectRatio:!1,aspectRatio:5,scales:{x:{display:!0},y:{beginAtZero:!1,min:s,max:r,ticks:{maxTicksLimit:8,callback:function(e,t,i){return DataFormattingUtils.formatLabelY(a,e,t,i)}}}},animation:!1,animations:{colors:!1,x:!1},transitions:{active:{animation:{duration:!1}}},plugins:{legend:{position:"hidden"},tooltip:{callbacks:{label:function(e){let t=e.dataset.label||"";return"Average"!==t?DataFormattingUtils.formatTooltip(a,e):t+=": "+e.formattedValue}}}}}})}}
64
- /*! Source: lib/karafka/web/ui/public/javascripts/charts/types/line.js */class LineChartsManager{constructor(){this.datasetStateManager=new DatasetStateManager}getLegendHeightPercentage(e){var t=e.chartArea,e=e.height,t=e-(t.bottom-t.top);return Math.round(t/e*100)}afterRenderPlugin(){let i=this;return{id:"afterRender",afterRender:function(e){var t=i.getLegendHeightPercentage(e),e=document.getElementById(e.canvas.id);50<t&&""==e.parentElement.style.height&&(e.parentElement.style.height="400px")}}}refreshAndRender(d,c=!1){(c?d:document).querySelectorAll(".chartjs-line").forEach(e=>{var t=e.id,i=(c?d:document).getElementById(t),s=JSON.parse(i.dataset.datasets);let r=[],a=[],n=0;var o=i.dataset.label_type_y;let l=i.dataset.label_type_x,h=this.datasetStateManager.getCurrentChart(t);Object.entries(s).forEach(([e,t],i)=>{t.forEach(([e,t])=>{0===i&&r.push(DataFormattingUtils.formatLabelX(e,l)),DataFormattingUtils.isFractionalPrecision(t)&&(n=2)}),a.push({data:t,label:e,hidden:h.includes(i),borderWidth:2.5,pointHitRadius:10})}),c?((i=Chart.getChart(t)).data.datasets=a,i.data.labels=r,i.update("none")):this.render(e,r,a,n,o)})}render(e,t,i,s,r){var a=null,a=10<i.length?"point":"x";new Chart(e,{type:"line",data:{labels:t,datasets:i},options:{responsive:!0,maintainAspectRatio:!1,aspectRatio:5,title:{display:!1},interaction:{mode:"nearest",axis:"x",intersect:!1},animation:!1,transitions:{active:{animation:{duration:!1}}},plugins:{legend:{position:"bottom",labels:{padding:20},onClick:(e,t,i)=>{var s=t.datasetIndex,i=i.chart;i.isDatasetVisible(s)?(i.hide(s),t.hidden=!0):(i.show(s),t.hidden=!1),this.datasetStateManager.saveCurrent()}},tooltip:{mode:a,filter:function(e,t,i){return t<10},callbacks:{label:function(e){return DataFormattingUtils.formatTooltip(r,e)}}}},scales:{x:{display:!1},y:{ticks:{precision:s,count:5,callback:function(e,t,i){return DataFormattingUtils.formatLabelY(r,e,t,i)}}}},elements:{point:{radius:0,style:!1},line:{style:"star",radius:0,spanGaps:!1}},hover:{mode:"index",intersect:!1}},plugins:[this.afterRenderPlugin()]})}}
64
+ /*! Source: lib/karafka/web/ui/public/javascripts/charts/types/line.js */class LineChartsManager{constructor(){this.datasetStateManager=new DatasetStateManager}getLegendHeightPercentage(e){var t=e.chartArea,e=e.height,t=e-(t.bottom-t.top);return Math.round(t/e*100)}afterRenderPlugin(){let i=this;return{id:"afterRender",afterRender:function(e){var t=i.getLegendHeightPercentage(e),e=document.getElementById(e.canvas.id);50<t&&""==e.parentElement.style.height&&(e.parentElement.style.height="400px")}}}refreshAndRender(d,c=!1){(c?d:document).querySelectorAll(".chartjs-line").forEach(e=>{var t=e.id,i=(c?d:document).getElementById(t),s=JSON.parse(i.dataset.datasets);let r=[],a=[],n=0;var o=i.dataset.label_type_y;let l=i.dataset.label_type_x,h=this.datasetStateManager.getCurrentChart(t);Object.entries(s).forEach(([e,t],i)=>{t.forEach(([e,t])=>{0===i&&r.push(DataFormattingUtils.formatLabelX(e,l)),DataFormattingUtils.isFractionalPrecision(t)&&(n=2)}),a.push({data:t,label:e,hidden:h.includes(i),borderWidth:2.5,pointHitRadius:10})}),c?((i=Chart.getChart(t)).data.datasets=a,i.data.labels=r,i.update("none")):this.render(e,r,a,n,o)})}render(e,t,i,s,r){var a=null,a=10<i.length?"point":"x";new Chart(e,{type:"line",data:{labels:t,datasets:i},options:{responsive:!0,maintainAspectRatio:!1,aspectRatio:5,title:{display:!1},interaction:{mode:"nearest",axis:"x",intersect:!1},animation:!1,transitions:{active:{animation:{duration:!1}}},plugins:{legend:{position:"bottom",labels:{padding:20},onClick:(e,t,i)=>{var s=t.datasetIndex,i=i.chart;i.isDatasetVisible(s)?(i.hide(s),t.hidden=!0):(i.show(s),t.hidden=!1),this.datasetStateManager.saveCurrent()}},tooltip:{mode:a,filter:function(e,t,i){return t<10},callbacks:{label:function(e){return DataFormattingUtils.formatTooltip(r,e)}}}},scales:{x:{display:!1},y:{ticks:{precision:s,count:5,callback:function(e,t,i){return DataFormattingUtils.formatLabelY(r,e,t,i)}}}},elements:{point:{radius:0,style:!1},line:{style:"star",radius:0,spanGaps:!1}},hover:{mode:"index",intersect:!1}},plugins:[this.afterRenderPlugin()]})}}
65
+ //# sourceMappingURL=application.min.js.map
@@ -1,10 +1,21 @@
1
+ /*
2
+ This class allows buttons to toggle visibility classes on target elements.
3
+ It also saves the visibility state in local storage so that it persists across page loads.
4
+
5
+ By default, it will look for elements with class 'btn-toggle' and toggle the
6
+ 'hidden' class on their target elements (defined by the data attribute `data-toggle-target`)
7
+ when clicked.
8
+ */
9
+
1
10
  class BtnToggleManager {
2
- constructor() {
11
+ constructor(btnClass = '.btn-toggle', visibilityClass = 'hidden') {
12
+ this.btnClass = btnClass;
13
+ this.visibilityClass = visibilityClass;
3
14
  this.init();
4
15
  }
5
16
 
6
17
  init() {
7
- document.querySelectorAll('.btn-toggle').forEach(button => {
18
+ document.querySelectorAll(this.btnClass).forEach(button => {
8
19
  const targetId = button.getAttribute('data-toggle-target');
9
20
  const targetElement = document.getElementById(targetId);
10
21
 
@@ -13,13 +24,22 @@ class BtnToggleManager {
13
24
  // Establish initial state from local storage or based on visibility
14
25
  this.restoreVisibility(button, targetElement);
15
26
 
16
- // Add event listener to toggle visibility
17
- button.addEventListener('click', () => {
18
- const isVisible = !targetElement.classList.contains('hidden');
19
- targetElement.classList.toggle('hidden');
20
- button.classList.toggle('active', !isVisible);
21
- this.saveVisibility(targetId, !isVisible);
22
- });
27
+ // Check if the event listener has already been added
28
+ if (!button._isClickListenerAdded) {
29
+ // Define the event handler and store the flag to prevent duplicate listeners
30
+ const handleClick = () => {
31
+ const isVisible = !targetElement.classList.contains(this.visibilityClass);
32
+ targetElement.classList.toggle(this.visibilityClass);
33
+ button.classList.toggle('active', !isVisible);
34
+ this.saveVisibility(targetId, !isVisible);
35
+ };
36
+
37
+ // Add the event listener
38
+ button.addEventListener('click', handleClick);
39
+
40
+ // Set flag to indicate the listener has been added
41
+ button._isClickListenerAdded = true;
42
+ }
23
43
  });
24
44
  }
25
45
 
@@ -29,9 +49,9 @@ class BtnToggleManager {
29
49
 
30
50
  restoreVisibility(button, targetElement) {
31
51
  const storedVisibility = localStorage.getItem(targetElement.id + '_visibility');
32
- const isVisible = storedVisibility ? (storedVisibility === 'true') : !targetElement.classList.contains('hidden');
52
+ const isVisible = storedVisibility ? (storedVisibility === 'true') : !targetElement.classList.contains(this.visibilityClass);
33
53
 
34
- targetElement.classList.toggle('hidden', !isVisible);
54
+ targetElement.classList.toggle(this.visibilityClass, !isVisible);
35
55
  button.classList.toggle('active', isVisible);
36
56
  }
37
57
  }
@@ -35,3 +35,24 @@ table td.commands {
35
35
  table td.commands-inline-3 {
36
36
  width: 180px
37
37
  }
38
+
39
+ .drawer-side .btn-toggle-nav-collapsed svg {
40
+ transform: scale(1, 1);
41
+ transition: transform 0.2s;
42
+ }
43
+
44
+ .drawer-side.collapsed .btn-toggle-nav-collapsed svg {
45
+ transform: scale(-1, -1);
46
+ }
47
+
48
+ .drawer-side .btn-toggle-nav-collapsed::after {
49
+ content: 'Collapse';
50
+ }
51
+
52
+ .drawer-side.collapsed .btn-toggle-nav-collapsed::after {
53
+ content: none;
54
+ }
55
+
56
+ .drawer-side.collapsed:hover .btn-toggle-nav-collapsed::after {
57
+ content: 'Expand';
58
+ }