ultra_settings 1.1.2 → 2.1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +43 -0
- data/VERSION +1 -1
- data/app/application.css +9 -8
- data/app/application_vars.css.erb +31 -0
- data/app/configuration.html.erb +1 -1
- data/app/index.html.erb +2 -2
- data/app/layout.css +5 -3
- data/app/layout.html.erb +1 -0
- data/app/layout_vars.css.erb +19 -0
- data/lib/ultra_settings/coerce.rb +22 -11
- data/lib/ultra_settings/configuration.rb +4 -4
- data/lib/ultra_settings/rack_app.rb +3 -2
- data/lib/ultra_settings/railtie.rb +6 -3
- data/lib/ultra_settings/web_view.rb +18 -3
- data/lib/ultra_settings.rb +2 -2
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 35efcdf391347182451bb26d3740e3d18c6e8c5bb4ff4d0100d0f418bb2eef7c
|
4
|
+
data.tar.gz: ed5721d08d451893fbce63317d92d024a7f2cff1e0c68616455b3343b62eb7a6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4636930f294c4c28389da0ed2e627554b1128a92f7fd622a0ac6eb905e6295ae46c513c449a3552e548b840a24d1c05ea106d32a8896a603ce98c596b0d72313
|
7
|
+
data.tar.gz: f8aa279a14715cc6a5a170d43dbaa1e38a55104b92dd25c403fb3b24b4066048bdb69159bf54cf9a3fb44a4280ccd91dba524ddbdca783eabca678ac5f28460e
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,27 @@ 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.1.0
|
8
|
+
|
9
|
+
### Added
|
10
|
+
|
11
|
+
- Added option to specify the color scheme for the web UI when mounting the rack app to support dark mode.
|
12
|
+
|
13
|
+
### Fixed
|
14
|
+
|
15
|
+
- Times stored as strings representing the seconds since the epoch are now correctly parsed as Time objects.
|
16
|
+
|
17
|
+
## 2.0.0
|
18
|
+
|
19
|
+
### Fixed
|
20
|
+
|
21
|
+
- **Breaking Change:** Fix conflict with overridding the standard `include?` method on configuration classes. These methods are now defined as `UltraSettings.added?` and `UltraSettings::Configuration.include_field?`.
|
22
|
+
- **Breaking Change:** Include namespace in autoloaded configuration names for Rails applications to avoid conflicts on classes in different namespaces.
|
23
|
+
|
24
|
+
### Changed
|
25
|
+
|
26
|
+
- Use configuration class in in web UI dropdown menu.
|
27
|
+
|
7
28
|
## 1.1.2
|
8
29
|
|
9
30
|
### Added
|
data/README.md
CHANGED
@@ -346,6 +346,12 @@ mount Rack::Builder.new do
|
|
346
346
|
end, at: "/ultra_settings"
|
347
347
|
```
|
348
348
|
|
349
|
+
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`.
|
350
|
+
|
351
|
+
```ruby
|
352
|
+
UltraSettings::RackApp.new(color_scheme: :dark)
|
353
|
+
```
|
354
|
+
|
349
355
|
#### Embedding the Settings View in Admin Tools
|
350
356
|
|
351
357
|
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 +427,25 @@ end
|
|
421
427
|
|
422
428
|
This approach keeps your tests clean and readable while allowing for flexible configuration management during testing.
|
423
429
|
|
430
|
+
### Rollout percentages
|
431
|
+
|
432
|
+
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.
|
433
|
+
|
434
|
+
```ruby
|
435
|
+
class MyServiceConfiguration < UltraSettings::Configuration
|
436
|
+
field :use_http2_percentage,
|
437
|
+
type: :float,
|
438
|
+
default: 0.0,
|
439
|
+
description: "Rollout percentage for using the new HTTP/2 driver"
|
440
|
+
|
441
|
+
# Using ConsistentRandom#rand instead of Kernel#rand to ensure that we
|
442
|
+
# get the same result within a request and don't oscillate back and forth
|
443
|
+
# every time we check if this is enabled.
|
444
|
+
def use_http2?
|
445
|
+
ConsistentRandom.new("MyServiceConfiguration.use_http2").rand < use_http2_percentage
|
446
|
+
end
|
447
|
+
end
|
448
|
+
```
|
424
449
|
|
425
450
|
## Installation
|
426
451
|
|
@@ -446,6 +471,24 @@ Open a pull request on [GitHub](https://github.com/bdurand/ultra_settings).
|
|
446
471
|
|
447
472
|
Please use the [standardrb](https://github.com/testdouble/standard) syntax and lint your code with `standardrb --fix` before submitting.
|
448
473
|
|
474
|
+
You can start a local rack server to test the web UI by running
|
475
|
+
|
476
|
+
```bash
|
477
|
+
bundle exec rackup
|
478
|
+
```
|
479
|
+
|
480
|
+
You can test with some setting set by setting environment variable used in the test configuration.
|
481
|
+
|
482
|
+
```bash
|
483
|
+
MY_SERVICE_HOST=host.example.com MY_SERVICE_TOKEN=secret bundle exec rspec
|
484
|
+
```
|
485
|
+
|
486
|
+
You can test dark mode by setting the `COLOR_SCHEME` environment variable.
|
487
|
+
|
488
|
+
```bash
|
489
|
+
COLOR_SCHEME=dark bundle exec rackup
|
490
|
+
```
|
491
|
+
|
449
492
|
## License
|
450
493
|
|
451
494
|
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
|
-
|
1
|
+
2.1.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
|
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
|
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:
|
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:
|
36
|
+
color: var(--code-color);
|
36
37
|
font-weight: 600;
|
37
38
|
}
|
38
39
|
|
39
40
|
.ultra-settings-table em {
|
40
|
-
color:
|
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:
|
53
|
-
background-color:
|
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
|
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 %>
|
data/app/configuration.html.erb
CHANGED
data/app/index.html.erb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
<div class="ultra-settings-nav">
|
2
|
-
<form onsubmit="return false">
|
2
|
+
<form onsubmit="return false" style="margin-bottom: 0.5rem;">
|
3
3
|
<select class="<%= html_escape(select_class) %>" size="1" id="config-selector">
|
4
4
|
<% UltraSettings.__configuration_names__.sort.each do |name| %>
|
5
|
-
<option value="config-<%= html_escape(name) %>"><%= html_escape(name) %></option>
|
5
|
+
<option value="config-<%= html_escape(name) %>"><%= html_escape(UltraSettings.send(name).class.name) %></option>
|
6
6
|
<% end %>
|
7
7
|
</select>
|
8
8
|
</form>
|
data/app/layout.css
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
-
* {
|
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:
|
9
|
-
background-color:
|
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
|
-
|
38
|
+
boolean(value)
|
37
39
|
when :datetime
|
38
|
-
|
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
|
54
|
-
|
55
|
-
|
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
|
-
|
70
|
-
|
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
|
@@ -64,7 +64,7 @@ module UltraSettings
|
|
64
64
|
secret: secret
|
65
65
|
)
|
66
66
|
|
67
|
-
class_eval
|
67
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1 # rubocop:disable Security/Eval
|
68
68
|
def #{name}
|
69
69
|
__get_value__(#{name.inspect})
|
70
70
|
end
|
@@ -86,12 +86,12 @@ module UltraSettings
|
|
86
86
|
#
|
87
87
|
# @param name [Symbol, String] The name of the field.
|
88
88
|
# @return [Boolean]
|
89
|
-
def
|
89
|
+
def include_field?(name)
|
90
90
|
name = name.to_s
|
91
91
|
return true if defined_fields.include?(name)
|
92
92
|
|
93
93
|
if superclass <= Configuration
|
94
|
-
superclass.
|
94
|
+
superclass.include_field?(name)
|
95
95
|
else
|
96
96
|
false
|
97
97
|
end
|
@@ -446,7 +446,7 @@ module UltraSettings
|
|
446
446
|
end
|
447
447
|
|
448
448
|
def include?(name)
|
449
|
-
self.class.
|
449
|
+
self.class.include_field?(name.to_s)
|
450
450
|
end
|
451
451
|
|
452
452
|
def override!(values, &block)
|
@@ -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
|
@@ -23,9 +23,12 @@ module UltraSettings
|
|
23
23
|
|
24
24
|
app_config_dir = Rails.root.join(directory)
|
25
25
|
app_config_dir.glob("**/*_configuration.rb").each do |file_path|
|
26
|
-
|
27
|
-
class_name =
|
28
|
-
UltraSettings.
|
26
|
+
relative_path = file_path.relative_path_from(app_config_dir).to_s
|
27
|
+
class_name = relative_path.chomp(".rb").classify
|
28
|
+
unless UltraSettings.added?(class_name)
|
29
|
+
config_name = class_name.delete_suffix("Configuration").underscore.tr("/", "_")
|
30
|
+
UltraSettings.add(config_name, class_name)
|
31
|
+
end
|
29
32
|
end
|
30
33
|
end
|
31
34
|
end
|
@@ -5,10 +5,13 @@ module UltraSettings
|
|
5
5
|
class WebView
|
6
6
|
attr_reader :css
|
7
7
|
|
8
|
-
|
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 =
|
11
|
-
@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
|
data/lib/ultra_settings.rb
CHANGED
@@ -46,7 +46,7 @@ module UltraSettings
|
|
46
46
|
def add(name, klass = nil)
|
47
47
|
name = name.to_s
|
48
48
|
unless name.match?(VALID_NAME__PATTERN)
|
49
|
-
raise
|
49
|
+
raise ArgumentError.new("Invalid configuration name: #{name.inspect}")
|
50
50
|
end
|
51
51
|
|
52
52
|
class_name = klass&.to_s
|
@@ -67,7 +67,7 @@ module UltraSettings
|
|
67
67
|
#
|
68
68
|
# @param class_name [Class, String] The name of the configuration class.
|
69
69
|
# @return [Boolean]
|
70
|
-
def
|
70
|
+
def added?(class_name)
|
71
71
|
@configurations.values.collect(&:to_s).include?(class_name.to_s)
|
72
72
|
end
|
73
73
|
|
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:
|
4
|
+
version: 2.1.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-
|
11
|
+
date: 2024-10-12 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
|