ultra_settings 2.0.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0ff5c5961b5070a9234049d2588bd59150e82d2cf87b46c0686067c6d72dd815
4
- data.tar.gz: 8df668372d8f6c7dd4c72ad55a789c8bd854bf0bfa0509a5e4a1da69bc74e93e
3
+ metadata.gz: a34c970ed06134a778e29f09ef7640a255a82f45bbf74e703963a7d9b911b598
4
+ data.tar.gz: 8065a6941af25b2d18b0e6617af074443c4de7da24541fad0d062f562a640c4d
5
5
  SHA512:
6
- metadata.gz: 7734298e87bd1fa4b7649ee5ddc64a0ef50f0e6e439bda7eb2042af055141ad5147ee07187ea53bbafbd5372430674a442f77e8c0277099cc8ff9ad7e1b20429
7
- data.tar.gz: 565fc7f9071292490d41a634c1b0c89530cbc065f925d52c3dcc25d8e1009bf8fe45ed84aa809fe6ab150d20d79bd9f90f47524ee2a161dfc9d90fc60ae5fb65
6
+ metadata.gz: d740cea45e6d52bf0392f9c867661b24b55f01ed0b3bb557f9730876af973d08036a47197539cee655a584a11b07b3ecb7ffcbd51cf4a6032605c9ab59b9277b
7
+ data.tar.gz: f11544c978f8b17790d9fe50b5fb03338a680da9dd4e045cc6e45cad2ab5e6910b51c737fd2735052eb835232023707dbd93bb6460627376765c01779c1f099f
data/CHANGELOG.md CHANGED
@@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## 2.2.0
8
+
9
+ ### Added
10
+
11
+ - Added option for `UltraSettings.runtime_settings_url` to allow configuring a link for editing runtime settings from the web UI.
12
+
13
+ ## 2.1.0
14
+
15
+ ### Added
16
+
17
+ - Added option to specify the color scheme for the web UI when mounting the rack app to support dark mode.
18
+
19
+ ### Fixed
20
+
21
+ - Times stored as strings representing the seconds since the epoch are now correctly parsed as Time objects.
22
+
7
23
  ## 2.0.0
8
24
 
9
25
  ### Fixed
data/README.md CHANGED
@@ -10,6 +10,24 @@ UltraSettings is a Ruby gem designed for managing application settings from vari
10
10
 
11
11
  UltraSettings emphasizes well-documented configuration. You can include documentation directly in the configuration code. The gem also includes a [web UI](#web-ui) that can be mounted as a Rack app or embedded in other views allowing admin users to easily view configuration settings and documentation.
12
12
 
13
+ ## Table Of Contents
14
+
15
+ - [Key Features](#key-features)
16
+ - [Usage](#usage)
17
+ - [Defining Configurations](#defining-configurations)
18
+ - [Field Options](#field-options)
19
+ - [Environment Variables](#environment-variables)
20
+ - [Runtime Settings](#runtime-settings)
21
+ - [YAML Files](#yaml-files)
22
+ - [Removing The Hierarchy](#removing-the-hierarchy)
23
+ - [Accessing Settings](#accessing-settings)
24
+ - [Web UI](#web-ui)
25
+ - [Testing With UltraSettings](#testing-with-ultrasettings)
26
+ - [Rollout Percentages](#rollout-percentages)
27
+ - [Installation](#installation)
28
+ - [Contributing](#contributing)
29
+ - [License](#license)
30
+
13
31
  ## Key Features
14
32
 
15
33
  This gem supports a three-layer hierarchy for defining configuration sources:
@@ -176,6 +194,8 @@ You can customize the behavior of runtime setting names with the following optio
176
194
 
177
195
  - **Disabling Runtime Settings:** You can disable runtime settings as a default source for fields by setting `runtime_settings_disabled` to `true` in your configuration class. You can disable runtime settings on individual fields by setting `runtime_setting` on the field to `false`.
178
196
 
197
+ - **Editing Links** You can specify a URL for editing runtime settings from the web UI by setting `UltraSettings.runtime_settings_url` to the desired URL. This will add links to the runtime settings in the web UI. You can use the placeholder `${name}` in the URL which will be replaced with the name of the runtime setting. If you are using the `super_settings` gem for runtime settings, then you can target a setting by adding `#edit=${name}` to the root URL where `super_settings` is mounted.
198
+
179
199
  If a setting value cannot be loaded from the runtime settings, then it's value will attempt to be loaded from a YAML file.
180
200
 
181
201
  ### YAML Files
@@ -346,6 +366,12 @@ mount Rack::Builder.new do
346
366
  end, at: "/ultra_settings"
347
367
  ```
348
368
 
369
+ You can specify the color scheme by setting by providing the `color_scheme` option to the `UltraSettings::RackApp` class. The default color scheme is `:light`. You can also set the scheme to `:dark` or `:system`.
370
+
371
+ ```ruby
372
+ UltraSettings::RackApp.new(color_scheme: :dark)
373
+ ```
374
+
349
375
  #### Embedding the Settings View in Admin Tools
350
376
 
351
377
  If you prefer to embed the settings view directly into your own admin tools or dashboard, you can use the `UltraSettings::ApplicationView` class to render the settings interface within your existing views:
@@ -421,6 +447,25 @@ end
421
447
 
422
448
  This approach keeps your tests clean and readable while allowing for flexible configuration management during testing.
423
449
 
450
+ ### Rollout percentages
451
+
452
+ A common usage of configuration is to control rollout of new features by specifying a percentage value and then testing if a random number is less than it. If you implement this pattern in your configuration, then you should use something like the [consistent_random](https://github.com/bdurand/consistent_random) gem to ensure you are generating consistent values without your units of work.
453
+
454
+ ```ruby
455
+ class MyServiceConfiguration < UltraSettings::Configuration
456
+ field :use_http2_percentage,
457
+ type: :float,
458
+ default: 0.0,
459
+ description: "Rollout percentage for using the new HTTP/2 driver"
460
+
461
+ # Using ConsistentRandom#rand instead of Kernel#rand to ensure that we
462
+ # get the same result within a request and don't oscillate back and forth
463
+ # every time we check if this is enabled.
464
+ def use_http2?
465
+ ConsistentRandom.new("MyServiceConfiguration.use_http2").rand < use_http2_percentage
466
+ end
467
+ end
468
+ ```
424
469
 
425
470
  ## Installation
426
471
 
@@ -446,6 +491,24 @@ Open a pull request on [GitHub](https://github.com/bdurand/ultra_settings).
446
491
 
447
492
  Please use the [standardrb](https://github.com/testdouble/standard) syntax and lint your code with `standardrb --fix` before submitting.
448
493
 
494
+ You can start a local rack server to test the web UI by running
495
+
496
+ ```bash
497
+ bundle exec rackup
498
+ ```
499
+
500
+ You can test with some setting set by setting environment variable used in the test configuration.
501
+
502
+ ```bash
503
+ MY_SERVICE_HOST=host.example.com MY_SERVICE_TOKEN=secret bundle exec rackup
504
+ ```
505
+
506
+ You can test dark mode by setting the `COLOR_SCHEME` environment variable.
507
+
508
+ ```bash
509
+ COLOR_SCHEME=dark bundle exec rackup
510
+ ```
511
+
449
512
  ## License
450
513
 
451
514
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.0
1
+ 2.2.0
data/app/application.css CHANGED
@@ -15,29 +15,30 @@
15
15
 
16
16
  .ultra-settings-table thead th {
17
17
  vertical-align: bottom;
18
- border-bottom: 2px solid #dee2e6;
18
+ border-bottom: 2px solid var(--table-border-color);
19
+ background-color: var(--table-header-bg-color);
19
20
  }
20
21
 
21
22
  .ultra-settings-table td, .ultra-settings-table th {
22
23
  padding: 0.75rem;
23
24
  vertical-align: top;
24
- border-top: 1px solid #dee2e6;
25
+ border-top: 1px solid var(--table-border-color);
25
26
  }
26
27
 
27
28
  .ultra-settings-table tbody tr:nth-of-type(odd) {
28
- background-color: rgba(0, 0, 0, .03);
29
+ background-color: var(--alt-row-color);
29
30
  }
30
31
 
31
32
  .ultra-settings-table code {
32
33
  font-family: monospace;
33
34
  font-size: 0.9rem;
34
35
  display: inline;
35
- color: darkred;
36
+ color: var(--code-color);
36
37
  font-weight: 600;
37
38
  }
38
39
 
39
40
  .ultra-settings-table em {
40
- color: gray;
41
+ color: var(--em-color);
41
42
  font-style: italic;
42
43
  font-size: 0.9rem;
43
44
  }
@@ -49,13 +50,13 @@
49
50
  font-size: 1rem;
50
51
  font-weight: 400;
51
52
  line-height: 1.5;
52
- color: #212529;
53
- background-color: #fff;
53
+ color: var(--form-control-color);
54
+ background-color: var(--form-control-bg-color);
54
55
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");
55
56
  background-repeat: no-repeat;
56
57
  background-position: right .75rem center;
57
58
  background-size: 16px 12px;
58
- border: 1px solid #ced4da;
59
+ border: 1px solid var(--form-control-border-color);
59
60
  border-radius: .25rem;
60
61
  transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
61
62
  -webkit-appearance: none;
@@ -0,0 +1,31 @@
1
+ <% unless color_scheme == :dark %>
2
+ .ultra-settings-configuration {
3
+ --table-header-bg-color: #fff;
4
+ --table-border-color: #dee2e6;
5
+ --alt-row-color: rgba(0, 0, 0, .05);
6
+ --form-control-color: #495057;
7
+ --form-control-bg-color: #fff;
8
+ --form-control-border-color: #ced4da;
9
+ --code-color: darkred;
10
+ --em-color: gray;
11
+ }
12
+ <% end %>
13
+
14
+ <% if color_scheme == :system %>
15
+ @media (prefers-color-scheme: dark) {
16
+ <% end %>
17
+ <% if color_scheme == :system || color_scheme == :dark %>
18
+ .ultra-settings-configuration {
19
+ --table-header-bg-color: #333;
20
+ --table-border-color: #555;
21
+ --alt-row-color: rgba(0, 0, 0, .30);
22
+ --form-control-color: #eee;
23
+ --form-control-bg-color: #666;
24
+ --form-control-border-color: #555;
25
+ --code-color: pink;
26
+ --em-color: #999;
27
+ }
28
+ <% end %>
29
+ <% if color_scheme == :system %>
30
+ }
31
+ <% end %>
@@ -20,7 +20,7 @@
20
20
  <th>Notes</th>
21
21
  </tr>
22
22
  </thead>
23
- <tbody>
23
+ <tbody translate="no">
24
24
  <% configuration.class.fields.each do |field| %>
25
25
  <% source = configuration.__source__(field.name) %>
26
26
  <tr>
@@ -52,14 +52,14 @@
52
52
  <% end %>
53
53
  </td>
54
54
 
55
- <td>
55
+ <td style="word-wrap: break-word;">
56
56
  <% unless field.description.to_s.empty? %>
57
57
  <div>
58
58
  <%= html_escape(field.description) %>
59
59
  </div>
60
60
  <% end %>
61
61
 
62
- <ul style="margin: 0; padding: 0;list-style-type: disc; list-style-position: inside;">
62
+ <ul style="margin: 0 0 0 1rem; padding: 0; list-style-type: disc; list-style-position: outside;">
63
63
  <% if field.env_var && !configuration.class.environment_variables_disabled? %>
64
64
  <li>
65
65
  <% if source == :env %>
@@ -68,9 +68,8 @@
68
68
  <% else %>
69
69
  Can be
70
70
  <% end %>
71
- set with the
71
+ set with the environment variable
72
72
  <code><%= show_defined_value(field.env_var, configuration.__value_from_source__(field.name, :env), field.secret?) %></code>
73
- environment variable.
74
73
  <% if source == :env %>
75
74
  </strong>
76
75
  <% end %>
@@ -84,12 +83,21 @@
84
83
  <% else %>
85
84
  Can be
86
85
  <% end %>
87
- set with the
86
+ set with the runtime setting
88
87
  <code><%= show_defined_value(field.runtime_setting, configuration.__value_from_source__(field.name, :settings), field.secret?) %></code>
89
- runtime setting.
90
88
  <% if source == :settings %>
91
89
  </strong>
92
90
  <% end %>
91
+
92
+ <% edit_url = UltraSettings.runtime_settings_url(field.runtime_setting) %>
93
+ <% if edit_url %>
94
+ <a href="<%= html_escape(edit_url) %>" title="Edit <%= html_escape(field.runtime_setting) %>" style="text-decoration: none; color: inherit; vertical-align: middle;">
95
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pencil-square" viewBox="0 0 16 16">
96
+ <path d="M15.502 1.94a.5.5 0 0 1 0 .706L14.459 3.69l-2-2L13.502.646a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z"/>
97
+ <path fill-rule="evenodd" d="M1 13.5A1.5 1.5 0 0 0 2.5 15h11a1.5 1.5 0 0 0 1.5-1.5v-6a.5.5 0 0 0-1 0v6a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5v-11a.5.5 0 0 1 .5-.5H9a.5.5 0 0 0 0-1H2.5A1.5 1.5 0 0 0 1 2.5z"/>
98
+ </svg>
99
+ </a>
100
+ <% end %>
93
101
  </li>
94
102
  <% end %>
95
103
  <% if field.yaml_key && !configuration.class.yaml_config_disabled? %>
@@ -100,9 +108,8 @@
100
108
  <% else %>
101
109
  Can be
102
110
  <% end %>
103
- set with the
111
+ set with the configuration file key
104
112
  <code><%= show_defined_value(field.yaml_key, configuration.__value_from_source__(field.name, :yaml), field.secret?) %></code>
105
- key in the configuration file.
106
113
  <% if source == :yaml %>
107
114
  </strong>
108
115
  <% end %>
data/app/layout.css CHANGED
@@ -1,12 +1,14 @@
1
- * {box-sizing:border-box;}
1
+ * {
2
+ box-sizing:border-box;
3
+ }
2
4
 
3
5
  body {
4
6
  font-family: sans-serif;
5
7
  font-size: 1rem;
6
8
  line-height: 1.5;
7
9
  text-align: left;
8
- color: #212529;
9
- background-color: #ffffff;
10
+ color: var(--text-color);
11
+ background-color: var(--background-color);
10
12
  margin: 0;
11
13
  padding: 0;
12
14
  }
data/app/layout.html.erb CHANGED
@@ -5,6 +5,7 @@
5
5
  <title>Application Configuration</title>
6
6
  <meta charset="utf-8">
7
7
  <meta name="viewport" content="width=device-width, initial-scale=1">
8
+ <meta name="format-detection" content="telephone=no email=no date=no address=no">
8
9
  <style type="text/css">
9
10
  <%= @layout_css %>
10
11
  <%= css %>
@@ -0,0 +1,19 @@
1
+ <% unless color_scheme != :dark %>
2
+ :root {
3
+ --text-color: #212529;
4
+ --background-color: #ffffff;
5
+ }
6
+ <% end %>
7
+
8
+ <% if color_scheme == :system %>
9
+ @media (prefers-color-scheme: dark) {
10
+ <% end %>
11
+ <% if color_scheme == :system || color_scheme == :dark %>
12
+ :root {
13
+ --text-color: #fff;
14
+ --background-color: #333;
15
+ }
16
+ <% end %>
17
+ <% if color_scheme == :system %>
18
+ }
19
+ <% end %>
@@ -18,6 +18,8 @@ module UltraSettings
18
18
  ]).freeze
19
19
  # rubocop:enable Lint/BooleanSymbol
20
20
 
21
+ NUMERIC_REGEX = /\A-?\d+(?:\.\d+)?\z/
22
+
21
23
  class << self
22
24
  # Cast a value to a specific type.
23
25
  #
@@ -33,13 +35,19 @@ module UltraSettings
33
35
  when :float
34
36
  value.is_a?(Float) ? value : value.to_s&.to_f
35
37
  when :boolean
36
- Coerce.boolean(value)
38
+ boolean(value)
37
39
  when :datetime
38
- Coerce.time(value)
40
+ time(value)
39
41
  when :array
40
42
  Array(value).map(&:to_s)
41
43
  when :symbol
42
44
  value.to_s.to_sym
45
+ when :rollout
46
+ if numeric?(value)
47
+ value.to_f
48
+ else
49
+ boolean(value)
50
+ end
43
51
  else
44
52
  value.to_s
45
53
  end
@@ -50,13 +58,9 @@ module UltraSettings
50
58
  # @param value [Object]
51
59
  # @return [Boolean]
52
60
  def boolean(value)
53
- if value == false
54
- false
55
- elsif blank?(value)
56
- nil
57
- else
58
- !FALSE_VALUES.include?(value)
59
- end
61
+ return nil if blank?(value)
62
+
63
+ !FALSE_VALUES.include?(value)
60
64
  end
61
65
 
62
66
  # Cast a value to a Time object.
@@ -66,8 +70,9 @@ module UltraSettings
66
70
  def time(value)
67
71
  value = nil if value.nil? || value.to_s.empty?
68
72
  return nil if value.nil?
69
- time = if value.is_a?(Numeric)
70
- Time.at(value)
73
+
74
+ time = if numeric?(value)
75
+ Time.at(value.to_f)
71
76
  elsif value.respond_to?(:to_time)
72
77
  value.to_time
73
78
  else
@@ -79,9 +84,15 @@ module UltraSettings
79
84
  time
80
85
  end
81
86
 
87
+ # @return [Boolean] true if the value is a numeric type or a string representing a number.
88
+ def numeric?(value)
89
+ value.is_a?(Numeric) || (value.is_a?(String) && value.to_s.match?(NUMERIC_REGEX))
90
+ end
91
+
82
92
  # @return [Boolean] true if the value is nil or empty.
83
93
  def blank?(value)
84
94
  return true if value.nil?
95
+
85
96
  if value.respond_to?(:empty?)
86
97
  value.empty?
87
98
  else
@@ -7,6 +7,9 @@ module UltraSettings
7
7
  ALLOWED_NAME_PATTERN = /\A[a-z_][a-zA-Z0-9_]*\z/
8
8
  ALLOWED_TYPES = [:string, :symbol, :integer, :float, :boolean, :datetime, :array].freeze
9
9
 
10
+ @env_var_prefix = nil
11
+ @runtime_setting_prefix = nil
12
+
10
13
  class << self
11
14
  # Define a field on the configuration. This will create a getter method for the field.
12
15
  # The field value will be read from the environment, runtime settings, or a YAML file
@@ -64,7 +67,7 @@ module UltraSettings
64
67
  secret: secret
65
68
  )
66
69
 
67
- class_eval <<-RUBY, __FILE__, __LINE__ + 1 # rubocop:disable Security/Eval
70
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1 # rubocop:disable Security/Eval
68
71
  def #{name}
69
72
  __get_value__(#{name.inspect})
70
73
  end
@@ -435,10 +438,10 @@ module UltraSettings
435
438
  end
436
439
 
437
440
  def initialize
438
- @mutex = Mutex.new
439
- @memoized_values = {}
440
- @override_values = {}
441
- @yaml_config = nil
441
+ @ultra_settings_mutex = Mutex.new
442
+ @ultra_settings_memoized_values = {}
443
+ @ultra_settings_override_values = {}
444
+ @ultra_settings_yaml_config = nil
442
445
  end
443
446
 
444
447
  def [](name)
@@ -450,7 +453,7 @@ module UltraSettings
450
453
  end
451
454
 
452
455
  def override!(values, &block)
453
- save_val = @override_values[Thread.current.object_id]
456
+ save_val = @ultra_settings_override_values[Thread.current.object_id]
454
457
 
455
458
  temp_values = (save_val || {}).dup
456
459
  values.each do |key, value|
@@ -458,13 +461,13 @@ module UltraSettings
458
461
  end
459
462
 
460
463
  begin
461
- @mutex.synchronize do
462
- @override_values[Thread.current.object_id] = temp_values
464
+ @ultra_settings_mutex.synchronize do
465
+ @ultra_settings_override_values[Thread.current.object_id] = temp_values
463
466
  end
464
467
  yield
465
468
  ensure
466
- @mutex.synchronize do
467
- @override_values[Thread.current.object_id] = save_val
469
+ @ultra_settings_mutex.synchronize do
470
+ @ultra_settings_override_values[Thread.current.object_id] = save_val
468
471
  end
469
472
  end
470
473
  end
@@ -530,12 +533,12 @@ module UltraSettings
530
533
  field = self.class.send(:defined_fields)[name]
531
534
  return nil unless field
532
535
 
533
- if field.static? && @memoized_values.include?(name)
534
- return @memoized_values[name]
536
+ if field.static? && @ultra_settings_memoized_values.include?(name)
537
+ return @ultra_settings_memoized_values[name]
535
538
  end
536
539
 
537
- if @override_values[Thread.current.object_id]&.include?(name)
538
- value = field.coerce(@override_values[Thread.current.object_id][name])
540
+ if @ultra_settings_override_values[Thread.current.object_id]&.include?(name)
541
+ value = field.coerce(@ultra_settings_override_values[Thread.current.object_id][name])
539
542
  else
540
543
  env = ENV if field.env_var
541
544
  settings = UltraSettings.__runtime_settings__ if field.runtime_setting
@@ -549,11 +552,11 @@ module UltraSettings
549
552
  end
550
553
 
551
554
  if field.static?
552
- @mutex.synchronize do
553
- if @memoized_values.include?(name)
554
- value = @memoized_values[name]
555
+ @ultra_settings_mutex.synchronize do
556
+ if @ultra_settings_memoized_values.include?(name)
557
+ value = @ultra_settings_memoized_values[name]
555
558
  else
556
- @memoized_values[name] = value
559
+ @ultra_settings_memoized_values[name] = value
557
560
  end
558
561
  end
559
562
  end
@@ -578,7 +581,7 @@ module UltraSettings
578
581
  end
579
582
 
580
583
  def __yaml_config__
581
- @yaml_config ||= self.class.load_yaml_config || {}
584
+ @ultra_settings_yaml_config ||= self.class.load_yaml_config || {}
582
585
  end
583
586
  end
584
587
  end
@@ -5,8 +5,9 @@ module UltraSettings
5
5
  # No setting values are displayed, but you should still add some
6
6
  # sort of authentication if you want to use this in production.
7
7
  class RackApp
8
- def initialize
8
+ def initialize(color_scheme: nil)
9
9
  @webview = nil
10
+ @color_scheme = color_scheme
10
11
  end
11
12
 
12
13
  def call(env)
@@ -19,7 +20,7 @@ module UltraSettings
19
20
  if ENV.fetch("RAILS_ENV", ENV.fetch("RACK_ENV", "development")) == "development"
20
21
  @webview = nil
21
22
  end
22
- @webview ||= WebView.new
23
+ @webview ||= WebView.new(color_scheme: @color_scheme)
23
24
  end
24
25
  end
25
26
  end
@@ -5,10 +5,13 @@ module UltraSettings
5
5
  class WebView
6
6
  attr_reader :css
7
7
 
8
- def initialize
8
+ # @param color_scheme [Symbol] The color scheme to use in the UI. This can be `:light`,
9
+ # `:dark`, or `:system`. The default is `:light`.
10
+ def initialize(color_scheme: :light)
11
+ color_scheme = (color_scheme || :light).to_sym
9
12
  @layout_template = erb_template("layout.html.erb")
10
- @layout_css = read_app_file("layout.css")
11
- @css = read_app_file("application.css")
13
+ @layout_css = layout_css(color_scheme)
14
+ @css = application_css(color_scheme)
12
15
  end
13
16
 
14
17
  def render_settings
@@ -32,5 +35,17 @@ module UltraSettings
32
35
  def app_dir
33
36
  File.expand_path(File.join("..", "..", "app"), __dir__)
34
37
  end
38
+
39
+ def layout_css(color_scheme)
40
+ vars = erb_template("layout_vars.css.erb").result(binding)
41
+ css = read_app_file("layout.css")
42
+ "#{vars}\n#{css}"
43
+ end
44
+
45
+ def application_css(color_scheme)
46
+ vars = erb_template("application_vars.css.erb").result(binding)
47
+ css = read_app_file("application.css")
48
+ "#{vars}\n#{css}"
49
+ end
35
50
  end
36
51
  end
@@ -6,6 +6,7 @@ require "time"
6
6
  require "pathname"
7
7
  require "singleton"
8
8
  require "digest"
9
+ require "uri"
9
10
 
10
11
  require_relative "ultra_settings/configuration"
11
12
  require_relative "ultra_settings/coerce"
@@ -33,6 +34,7 @@ module UltraSettings
33
34
  @configurations = {}
34
35
  @mutex = Mutex.new
35
36
  @runtime_settings = nil
37
+ @runtime_settings_url = nil
36
38
 
37
39
  class << self
38
40
  # Adds a configuration to the root namespace. The configuration will be
@@ -167,6 +169,24 @@ module UltraSettings
167
169
  @runtime_settings
168
170
  end
169
171
 
172
+ # Set the URL for changing runtime settings. If this is set, then a link to the
173
+ # URL will be displayed in the web view for fields that support runtime settings.
174
+ # The URL may contain a `${name}` placeholder that will be replaced with the name
175
+ # of the setting.
176
+ attr_writer :runtime_settings_url
177
+
178
+ # Get the URL for changing runtime settings.
179
+ #
180
+ # @param name [String] The name of the setting.
181
+ # @return [String, nil]
182
+ # @api private
183
+ def runtime_settings_url(name)
184
+ url = @runtime_settings_url.to_s
185
+ return nil if url.empty?
186
+
187
+ url.gsub("${name}", URI.encode_www_form_component(name))
188
+ end
189
+
170
190
  def fields_secret_by_default=(value)
171
191
  Configuration.fields_secret_by_default = value
172
192
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ultra_settings
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Durand
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-09-28 00:00:00.000000000 Z
11
+ date: 2024-10-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -37,10 +37,12 @@ files:
37
37
  - VERSION
38
38
  - app/application.css
39
39
  - app/application.js
40
+ - app/application_vars.css.erb
40
41
  - app/configuration.html.erb
41
42
  - app/index.html.erb
42
43
  - app/layout.css
43
44
  - app/layout.html.erb
45
+ - app/layout_vars.css.erb
44
46
  - lib/ultra_settings.rb
45
47
  - lib/ultra_settings/application_view.rb
46
48
  - lib/ultra_settings/coerce.rb