pgbouncerhero 3.0.0 → 3.2.0

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -0
  3. data/README.md +119 -4
  4. data/app/assets/builds/pgbouncerhero/application.css +2 -2
  5. data/app/assets/stylesheets/pgbouncerhero/application.css +11 -1
  6. data/app/controllers/pg_bouncer_hero/application_controller.rb +4 -3
  7. data/app/controllers/pg_bouncer_hero/database_controller.rb +93 -9
  8. data/app/helpers/pg_bouncer_hero/application_helper.rb +54 -0
  9. data/app/javascript/pgbouncerhero/controllers/data_table_controller.js +183 -0
  10. data/app/javascript/pgbouncerhero/controllers/fleet_controller.js +52 -0
  11. data/app/javascript/pgbouncerhero/controllers/polling_controller.js +49 -5
  12. data/app/views/layouts/pg_bouncer_hero/application.html.erb +1 -1
  13. data/app/views/pg_bouncer_hero/database/_actions.html.erb +1 -1
  14. data/app/views/pg_bouncer_hero/database/_clients.html.erb +9 -8
  15. data/app/views/pg_bouncer_hero/database/_conf.html.erb +10 -9
  16. data/app/views/pg_bouncer_hero/database/_databases.html.erb +30 -9
  17. data/app/views/pg_bouncer_hero/database/_menu.html.erb +19 -4
  18. data/app/views/pg_bouncer_hero/database/_pools.html.erb +10 -9
  19. data/app/views/pg_bouncer_hero/database/_servers.html.erb +31 -0
  20. data/app/views/pg_bouncer_hero/database/_state.html.erb +11 -0
  21. data/app/views/pg_bouncer_hero/database/_stats.html.erb +12 -11
  22. data/app/views/pg_bouncer_hero/database/_users.html.erb +31 -0
  23. data/app/views/pg_bouncer_hero/database/clients.html.erb +4 -2
  24. data/app/views/pg_bouncer_hero/database/conf.html.erb +4 -2
  25. data/app/views/pg_bouncer_hero/database/databases.html.erb +4 -2
  26. data/app/views/pg_bouncer_hero/database/pools.html.erb +4 -2
  27. data/app/views/pg_bouncer_hero/database/servers.html.erb +6 -0
  28. data/app/views/pg_bouncer_hero/database/state.html.erb +6 -0
  29. data/app/views/pg_bouncer_hero/database/stats.html.erb +4 -2
  30. data/app/views/pg_bouncer_hero/database/users.html.erb +6 -0
  31. data/app/views/pg_bouncer_hero/home/_card.html.erb +5 -2
  32. data/app/views/pg_bouncer_hero/home/_card_content.html.erb +48 -31
  33. data/app/views/pg_bouncer_hero/home/index.html.erb +28 -8
  34. data/app/views/shared/_data_table_toolbar.html.erb +13 -0
  35. data/config/routes.rb +16 -2
  36. data/lib/generators/pgbouncerhero/templates/config.yml +7 -0
  37. data/lib/pgbouncerhero/connection.rb +54 -13
  38. data/lib/pgbouncerhero/database.rb +73 -3
  39. data/lib/pgbouncerhero/methods/basics.rb +86 -10
  40. data/lib/pgbouncerhero/version.rb +1 -1
  41. data/lib/pgbouncerhero.rb +139 -38
  42. metadata +83 -3
data/lib/pgbouncerhero.rb CHANGED
@@ -1,26 +1,47 @@
1
- require "pgbouncerhero/version"
2
-
3
- require "pgbouncerhero/methods/basics"
4
-
5
- require "pgbouncerhero/group"
6
- require "pgbouncerhero/engine" if defined?(Rails)
7
-
8
- require "pgbouncerhero/connection"
9
-
1
+ require "erb"
2
+ require "monitor"
3
+ require "pathname"
4
+ require "yaml"
5
+ require "rails"
6
+ require "active_support/notifications"
10
7
  require "pg"
11
8
  require "importmap-rails"
12
9
  require "turbo-rails"
13
10
  require "stimulus-rails"
14
11
 
12
+ require "pgbouncerhero/version"
13
+ require "pgbouncerhero/methods/basics"
14
+ require "pgbouncerhero/connection"
15
+ require "pgbouncerhero/group"
16
+ require "pgbouncerhero/engine"
17
+
15
18
  module PgBouncerHero
19
+ ADMIN_COMMAND_EVENT = "admin_command.pgbouncerhero"
20
+
21
+ class ConfigurationError < StandardError; end
22
+
23
+ BOOLEAN_SETTINGS = {
24
+ true => true,
25
+ false => false,
26
+ "1" => true,
27
+ "0" => false,
28
+ "true" => true,
29
+ "false" => false,
30
+ "yes" => true,
31
+ "no" => false,
32
+ "on" => true,
33
+ "off" => false
34
+ }.freeze
35
+ private_constant :BOOLEAN_SETTINGS
36
+
37
+ STATE_MONITOR = Monitor.new
38
+ private_constant :STATE_MONITOR
39
+
16
40
  mattr_accessor :importmap, default: Importmap::Map.new
17
41
 
18
42
  class << self
19
- attr_accessor :env
20
- end
21
- self.env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
43
+ attr_reader :env
22
44
 
23
- class << self
24
45
  def time_zone=(time_zone)
25
46
  @time_zone = time_zone.is_a?(ActiveSupport::TimeZone) ? time_zone : ActiveSupport::TimeZone[time_zone.to_s]
26
47
  end
@@ -29,38 +50,118 @@ module PgBouncerHero
29
50
  @time_zone || Time.zone
30
51
  end
31
52
 
53
+ def env=(env)
54
+ STATE_MONITOR.synchronize do
55
+ @env = env.to_s
56
+ reset!
57
+ end
58
+ end
59
+
60
+ def config_path
61
+ STATE_MONITOR.synchronize { @config_path || default_config_path }
62
+ end
63
+
64
+ def config_path=(path)
65
+ STATE_MONITOR.synchronize do
66
+ @config_path = path && Pathname(path)
67
+ reset!
68
+ end
69
+ end
70
+
32
71
  def config
33
- @config ||= begin
34
- path = "config/pgbouncerhero.yml"
35
-
36
- config = YAML.safe_load(ERB.new(File.read(path)).result, aliases: true) if File.exist?(path)
37
- config ||= {}
38
-
39
- if config[env]
40
- config[env]
41
- elsif config["pgbouncers"]
42
- config
43
- else
44
- {
45
- "pgbouncers" => {
46
- "default" => {
47
- "primary" => {
48
- "url" => ENV["PGBOUNCERHERO_DATABASE_URL"]
49
- }
50
- }
51
- }
52
- }
72
+ STATE_MONITOR.synchronize { @config ||= selected_config(load_config) }
73
+ end
74
+
75
+ def groups
76
+ STATE_MONITOR.synchronize do
77
+ @groups ||= config.fetch("pgbouncers").to_h do |group_id, _databases|
78
+ [ group_id.parameterize, PgBouncerHero::Group.new(group_id, config.fetch("pgbouncers")) ]
53
79
  end
54
80
  end
55
81
  end
56
82
 
57
- def groups
58
- @groups ||= begin
59
- mapped = config["pgbouncers"].map do |group_id, _hash|
60
- [ group_id.parameterize, PgBouncerHero::Group.new(group_id, config["pgbouncers"]) ]
83
+ def read_only?
84
+ value = ENV.fetch("PGBOUNCERHERO_READ_ONLY") { config.fetch("read_only", false) }
85
+ parse_read_only(value)
86
+ end
87
+
88
+ def read_only_for?(database)
89
+ value = ENV.fetch("PGBOUNCERHERO_READ_ONLY") do
90
+ database.config.fetch("read_only") { config.fetch("read_only", false) }
91
+ end
92
+ parse_read_only(value)
93
+ end
94
+
95
+ def parse_read_only(value)
96
+ BOOLEAN_SETTINGS.fetch(value.is_a?(String) ? value.downcase : value)
97
+ rescue KeyError
98
+ raise ConfigurationError, "read_only must be a boolean"
99
+ end
100
+
101
+ def disconnect!
102
+ STATE_MONITOR.synchronize do
103
+ @groups&.each_value do |group|
104
+ group.databases.each(&:disconnect!)
61
105
  end
62
- Hash[mapped]
63
106
  end
64
107
  end
108
+
109
+ def reset!
110
+ STATE_MONITOR.synchronize do
111
+ disconnect!
112
+ @config = nil
113
+ @groups = nil
114
+ end
115
+ end
116
+
117
+ private :parse_read_only
118
+
119
+ private
120
+
121
+ def default_config_path
122
+ (Rails.root || Pathname.pwd).join("config/pgbouncerhero.yml")
123
+ end
124
+
125
+ def load_config
126
+ return {} unless config_path.file?
127
+
128
+ loaded = YAML.safe_load(ERB.new(config_path.read).result, aliases: true)
129
+ return {} if loaded.nil?
130
+ return loaded if loaded.is_a?(Hash)
131
+
132
+ raise ConfigurationError, "#{config_path} must contain a YAML mapping"
133
+ end
134
+
135
+ def selected_config(loaded)
136
+ selected = loaded.fetch(env, loaded)
137
+ unless selected.is_a?(Hash)
138
+ raise ConfigurationError, "configuration for #{env.inspect} must be a YAML mapping"
139
+ end
140
+
141
+ return default_config unless selected.key?("pgbouncers")
142
+
143
+ pgbouncers = selected["pgbouncers"]
144
+ unless pgbouncers.is_a?(Hash) && pgbouncers.any? && pgbouncers.values.all? { |databases| databases.is_a?(Hash) && databases.any? }
145
+ raise ConfigurationError, '"pgbouncers" must contain at least one group with one database'
146
+ end
147
+
148
+ selected
149
+ end
150
+
151
+ def default_config
152
+ {
153
+ "pgbouncers" => {
154
+ "default" => {
155
+ "primary" => {
156
+ "url" => ENV["PGBOUNCERHERO_DATABASE_URL"]
157
+ }
158
+ }
159
+ }
160
+ }
161
+ end
65
162
  end
163
+
164
+ self.env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
66
165
  end
166
+
167
+ at_exit { PgBouncerHero.disconnect! }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pgbouncerhero
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Quentin Rousseau
@@ -16,6 +16,9 @@ dependencies:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
18
  version: '7.2'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '9'
19
22
  type: :runtime
20
23
  prerelease: false
21
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -23,6 +26,29 @@ dependencies:
23
26
  - - ">="
24
27
  - !ruby/object:Gem::Version
25
28
  version: '7.2'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '9'
32
+ - !ruby/object:Gem::Dependency
33
+ name: propshaft
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '1.0'
39
+ - - "<"
40
+ - !ruby/object:Gem::Version
41
+ version: '2'
42
+ type: :runtime
43
+ prerelease: false
44
+ version_requirements: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '1.0'
49
+ - - "<"
50
+ - !ruby/object:Gem::Version
51
+ version: '2'
26
52
  - !ruby/object:Gem::Dependency
27
53
  name: importmap-rails
28
54
  requirement: !ruby/object:Gem::Requirement
@@ -30,6 +56,9 @@ dependencies:
30
56
  - - ">="
31
57
  - !ruby/object:Gem::Version
32
58
  version: '1.2'
59
+ - - "<"
60
+ - !ruby/object:Gem::Version
61
+ version: '3'
33
62
  type: :runtime
34
63
  prerelease: false
35
64
  version_requirements: !ruby/object:Gem::Requirement
@@ -37,6 +66,9 @@ dependencies:
37
66
  - - ">="
38
67
  - !ruby/object:Gem::Version
39
68
  version: '1.2'
69
+ - - "<"
70
+ - !ruby/object:Gem::Version
71
+ version: '3'
40
72
  - !ruby/object:Gem::Dependency
41
73
  name: turbo-rails
42
74
  requirement: !ruby/object:Gem::Requirement
@@ -44,6 +76,9 @@ dependencies:
44
76
  - - ">="
45
77
  - !ruby/object:Gem::Version
46
78
  version: '1.0'
79
+ - - "<"
80
+ - !ruby/object:Gem::Version
81
+ version: '3'
47
82
  type: :runtime
48
83
  prerelease: false
49
84
  version_requirements: !ruby/object:Gem::Requirement
@@ -51,6 +86,9 @@ dependencies:
51
86
  - - ">="
52
87
  - !ruby/object:Gem::Version
53
88
  version: '1.0'
89
+ - - "<"
90
+ - !ruby/object:Gem::Version
91
+ version: '3'
54
92
  - !ruby/object:Gem::Dependency
55
93
  name: stimulus-rails
56
94
  requirement: !ruby/object:Gem::Requirement
@@ -58,6 +96,9 @@ dependencies:
58
96
  - - ">="
59
97
  - !ruby/object:Gem::Version
60
98
  version: '1.0'
99
+ - - "<"
100
+ - !ruby/object:Gem::Version
101
+ version: '2'
61
102
  type: :runtime
62
103
  prerelease: false
63
104
  version_requirements: !ruby/object:Gem::Requirement
@@ -65,6 +106,9 @@ dependencies:
65
106
  - - ">="
66
107
  - !ruby/object:Gem::Version
67
108
  version: '1.0'
109
+ - - "<"
110
+ - !ruby/object:Gem::Version
111
+ version: '2'
68
112
  - !ruby/object:Gem::Dependency
69
113
  name: pg
70
114
  requirement: !ruby/object:Gem::Requirement
@@ -72,6 +116,9 @@ dependencies:
72
116
  - - ">="
73
117
  - !ruby/object:Gem::Version
74
118
  version: '1.2'
119
+ - - "<"
120
+ - !ruby/object:Gem::Version
121
+ version: '2'
75
122
  type: :runtime
76
123
  prerelease: false
77
124
  version_requirements: !ruby/object:Gem::Requirement
@@ -79,6 +126,29 @@ dependencies:
79
126
  - - ">="
80
127
  - !ruby/object:Gem::Version
81
128
  version: '1.2'
129
+ - - "<"
130
+ - !ruby/object:Gem::Version
131
+ version: '2'
132
+ - !ruby/object:Gem::Dependency
133
+ name: connection_pool
134
+ requirement: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '3.0'
139
+ - - "<"
140
+ - !ruby/object:Gem::Version
141
+ version: '4'
142
+ type: :runtime
143
+ prerelease: false
144
+ version_requirements: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: '3.0'
149
+ - - "<"
150
+ - !ruby/object:Gem::Version
151
+ version: '4'
82
152
  - !ruby/object:Gem::Dependency
83
153
  name: rake
84
154
  requirement: !ruby/object:Gem::Requirement
@@ -113,14 +183,14 @@ dependencies:
113
183
  requirements:
114
184
  - - "~>"
115
185
  - !ruby/object:Gem::Version
116
- version: '5.0'
186
+ version: '6.0'
117
187
  type: :development
118
188
  prerelease: false
119
189
  version_requirements: !ruby/object:Gem::Requirement
120
190
  requirements:
121
191
  - - "~>"
122
192
  - !ruby/object:Gem::Version
123
- version: '5.0'
193
+ version: '6.0'
124
194
  - !ruby/object:Gem::Dependency
125
195
  name: appraisal
126
196
  requirement: !ruby/object:Gem::Requirement
@@ -195,6 +265,8 @@ files:
195
265
  - app/controllers/pg_bouncer_hero/home_controller.rb
196
266
  - app/helpers/pg_bouncer_hero/application_helper.rb
197
267
  - app/javascript/pgbouncerhero/application.js
268
+ - app/javascript/pgbouncerhero/controllers/data_table_controller.js
269
+ - app/javascript/pgbouncerhero/controllers/fleet_controller.js
198
270
  - app/javascript/pgbouncerhero/controllers/polling_controller.js
199
271
  - app/views/layouts/pg_bouncer_hero/application.html.erb
200
272
  - app/views/pg_bouncer_hero/database/_actions.html.erb
@@ -203,18 +275,25 @@ files:
203
275
  - app/views/pg_bouncer_hero/database/_databases.html.erb
204
276
  - app/views/pg_bouncer_hero/database/_menu.html.erb
205
277
  - app/views/pg_bouncer_hero/database/_pools.html.erb
278
+ - app/views/pg_bouncer_hero/database/_servers.html.erb
279
+ - app/views/pg_bouncer_hero/database/_state.html.erb
206
280
  - app/views/pg_bouncer_hero/database/_stats.html.erb
281
+ - app/views/pg_bouncer_hero/database/_users.html.erb
207
282
  - app/views/pg_bouncer_hero/database/clients.html.erb
208
283
  - app/views/pg_bouncer_hero/database/conf.html.erb
209
284
  - app/views/pg_bouncer_hero/database/databases.html.erb
210
285
  - app/views/pg_bouncer_hero/database/pools.html.erb
286
+ - app/views/pg_bouncer_hero/database/servers.html.erb
287
+ - app/views/pg_bouncer_hero/database/state.html.erb
211
288
  - app/views/pg_bouncer_hero/database/stats.html.erb
212
289
  - app/views/pg_bouncer_hero/database/summary.html.erb
290
+ - app/views/pg_bouncer_hero/database/users.html.erb
213
291
  - app/views/pg_bouncer_hero/home/_card.html.erb
214
292
  - app/views/pg_bouncer_hero/home/_card_content.html.erb
215
293
  - app/views/pg_bouncer_hero/home/_card_loading_content.html.erb
216
294
  - app/views/pg_bouncer_hero/home/index.html.erb
217
295
  - app/views/shared/_alert.html.erb
296
+ - app/views/shared/_data_table_toolbar.html.erb
218
297
  - app/views/shared/_flash_messages.html.erb
219
298
  - config/importmap.rb
220
299
  - config/routes.rb
@@ -233,6 +312,7 @@ licenses:
233
312
  metadata:
234
313
  source_code_uri: https://github.com/kwent/pgbouncerhero
235
314
  changelog_uri: https://github.com/kwent/pgbouncerhero/blob/master/CHANGELOG.md
315
+ documentation_uri: https://github.com/kwent/pgbouncerhero#readme
236
316
  bug_tracker_uri: https://github.com/kwent/pgbouncerhero/issues
237
317
  rubygems_mfa_required: 'true'
238
318
  rdoc_options: []