ultra_settings 1.1.2 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|