ultra_settings 2.4.5 → 2.6.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/ARCHITECTURE.md +186 -0
- data/CHANGELOG.md +20 -0
- data/README.md +29 -4
- data/VERSION +1 -1
- data/app/application.css +193 -20
- data/app/application_vars.css.erb +73 -14
- data/app/configuration.html.erb +101 -130
- data/app/index.html.erb +16 -0
- data/app/layout.html.erb +1 -2
- data/lib/ultra_settings/application_view.rb +24 -28
- data/lib/ultra_settings/configuration_view.rb +6 -25
- data/lib/ultra_settings/field.rb +13 -1
- data/lib/ultra_settings/railtie.rb +0 -1
- data/lib/ultra_settings/uninitialized_runtime_settings.rb +31 -0
- data/lib/ultra_settings/view_helper.rb +22 -0
- data/lib/ultra_settings/web_view.rb +8 -27
- data/lib/ultra_settings.rb +5 -2
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4682a4cd5e85642db5e79cfbb156109d6701b5ad09f6744a0b092bdfc3d9277c
|
4
|
+
data.tar.gz: 8c4a0d4350ce43b7ce4b1c0187bf80c27e2d33261d9bd349ce9155d8fb80a069
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 00c9249324a0c6752dd1449d7b94101050e55aa1d27ba0c47cb3527bb0e8c5909233a64b98a0a0674528acae2413c5578c0097fabe9aaeda6a5b78c47a1854d7
|
7
|
+
data.tar.gz: a46516c99004973b58496e44c38c375b6698be941aba85fd2e3c0bf08db032c8b9c71ce371aef051bd8649419e5dc6752be0e1f259c2019ce3baa270c8b52460
|
data/ARCHITECTURE.md
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
# UltraSettings Configuration Architecture
|
2
|
+
|
3
|
+
This document describes the architecture of the UltraSettings Configuration and Field classes, which form the core of the configuration management system.
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
The UltraSettings library provides a flexible configuration management system built around two primary classes:
|
8
|
+
|
9
|
+
- **Configuration**: A singleton-based configuration manager that supports multiple data sources
|
10
|
+
- **Field**: A field definition that encapsulates metadata and value resolution logic
|
11
|
+
|
12
|
+
## Architecture Diagram
|
13
|
+
|
14
|
+
```mermaid
|
15
|
+
classDiagram
|
16
|
+
class Configuration {
|
17
|
+
<<Singleton>>
|
18
|
+
+ALLOWED_NAME_PATTERN : Regex
|
19
|
+
+ALLOWED_TYPES : Array~Symbol~
|
20
|
+
-@env_var_prefix : String
|
21
|
+
-@runtime_setting_prefix : String
|
22
|
+
-@ultra_settings_mutex : Mutex
|
23
|
+
-@ultra_settings_memoized_values : Hash
|
24
|
+
-@ultra_settings_override_values : Hash
|
25
|
+
-@ultra_settings_yaml_config : Hash
|
26
|
+
|
27
|
+
+field(name, **options) void
|
28
|
+
+fields() Array~Field~
|
29
|
+
+include_field?(name) Boolean
|
30
|
+
+env_var_prefix=(value) void
|
31
|
+
+env_var_prefix() String
|
32
|
+
+runtime_setting_prefix=(value) void
|
33
|
+
+runtime_setting_prefix() String
|
34
|
+
+configuration_file=(value) void
|
35
|
+
+configuration_file() Pathname
|
36
|
+
+override!(values, &block) Object
|
37
|
+
+load_yaml_config() Hash
|
38
|
+
+[](name) Object
|
39
|
+
+include?(name) Boolean
|
40
|
+
+__source__(name) Symbol
|
41
|
+
+__value_from_source__(name, source) Object
|
42
|
+
+__to_hash__() Hash
|
43
|
+
-__get_value__(name) Object
|
44
|
+
-__use_default?(value, default_if) Boolean
|
45
|
+
-__yaml_config__() Hash
|
46
|
+
-defined_fields() Hash~String, Field~
|
47
|
+
-root_name() String
|
48
|
+
-construct_env_var(name, env_var) String
|
49
|
+
-construct_runtime_setting(name, runtime_setting) String
|
50
|
+
-construct_yaml_key(name, yaml_key) String
|
51
|
+
}
|
52
|
+
|
53
|
+
class Field {
|
54
|
+
+name : String
|
55
|
+
+type : Symbol
|
56
|
+
+description : String
|
57
|
+
+default : Object
|
58
|
+
+default_if : Proc|Symbol
|
59
|
+
+env_var : String
|
60
|
+
+runtime_setting : String
|
61
|
+
+yaml_key : String
|
62
|
+
-@static : Boolean
|
63
|
+
-@secret : Boolean|Proc
|
64
|
+
|
65
|
+
+initialize(**options) void
|
66
|
+
+value(env, settings, yaml_config) Object
|
67
|
+
+source(env, settings, yaml_config) Symbol
|
68
|
+
+coerce(value) Object
|
69
|
+
+static?() Boolean
|
70
|
+
+secret?() Boolean
|
71
|
+
-fetch_value_and_source(**sources) Array
|
72
|
+
-runtime_setting_value(settings) Object
|
73
|
+
-yaml_value(yaml_config) Object
|
74
|
+
}
|
75
|
+
|
76
|
+
class Coerce {
|
77
|
+
<<Utility>>
|
78
|
+
+coerce_value(value, type) Object
|
79
|
+
}
|
80
|
+
|
81
|
+
Configuration "1" --> "*" Field : manages
|
82
|
+
Field --> Coerce : uses
|
83
|
+
Configuration --> Field : creates via field()
|
84
|
+
|
85
|
+
note for Configuration "Singleton pattern ensures single instance per class.\nSupports inheritance for configuration hierarchies.\nThread-safe with mutex protection."
|
86
|
+
|
87
|
+
note for Field "Immutable once created.\nSupports multiple data sources with precedence:\n1. Environment Variables\n2. Runtime Settings\n3. YAML Configuration\n4. Default Values"
|
88
|
+
```
|
89
|
+
|
90
|
+
## Key Components
|
91
|
+
|
92
|
+
### Configuration Class
|
93
|
+
|
94
|
+
The `Configuration` class serves as the primary interface for defining and accessing configuration values. Key characteristics:
|
95
|
+
|
96
|
+
#### Singleton Pattern
|
97
|
+
- Uses Ruby's `Singleton` module to ensure one instance per configuration class
|
98
|
+
- Supports inheritance, allowing configuration subclasses to have their own singleton instances
|
99
|
+
|
100
|
+
#### Field Definition
|
101
|
+
- **`field(name, **options)`**: DSL method for defining configuration fields
|
102
|
+
- Dynamically creates getter methods using `class_eval`
|
103
|
+
- Supports various field types: `:string`, `:symbol`, `:integer`, `:float`, `:boolean`, `:datetime`, `:array`
|
104
|
+
- Validates field names against `ALLOWED_NAME_PATTERN`
|
105
|
+
|
106
|
+
#### Multi-Source Value Resolution
|
107
|
+
Fields can pull values from multiple sources in order of precedence:
|
108
|
+
1. **Environment Variables** - Highest priority
|
109
|
+
2. **Runtime Settings** - Dynamic configuration
|
110
|
+
3. **YAML Files** - File-based configuration
|
111
|
+
4. **Default Values** - Fallback values
|
112
|
+
|
113
|
+
#### Thread Safety
|
114
|
+
- Uses `Mutex` for thread-safe operations
|
115
|
+
- Protects memoized values and override values from race conditions
|
116
|
+
|
117
|
+
#### Value Overrides
|
118
|
+
- **`override!(values, &block)`**: Temporarily override field values within a block
|
119
|
+
- Thread-local overrides allow different values per thread
|
120
|
+
|
121
|
+
### Field Class
|
122
|
+
|
123
|
+
The `Field` class encapsulates the metadata and behavior for individual configuration fields:
|
124
|
+
|
125
|
+
#### Immutable Design
|
126
|
+
- All attributes are frozen upon initialization
|
127
|
+
- Ensures consistent behavior throughout the application lifecycle
|
128
|
+
|
129
|
+
#### Value Resolution Logic
|
130
|
+
- **`value(env:, settings:, yaml_config:)`**: Resolves field value from available sources
|
131
|
+
- **`source(env:, settings:, yaml_config:)`**: Identifies which source provided the value
|
132
|
+
- **`fetch_value_and_source()`**: Core resolution algorithm with source precedence
|
133
|
+
|
134
|
+
#### Type Coercion
|
135
|
+
- **`coerce(value)`**: Converts string values to appropriate types
|
136
|
+
- Delegates to `Coerce` utility class for type-specific conversion logic
|
137
|
+
|
138
|
+
#### Security Features
|
139
|
+
- **`secret?`**: Marks fields containing sensitive data
|
140
|
+
- Supports both boolean and callable (lambda) secret detection
|
141
|
+
- Secret fields are masked in serialization methods
|
142
|
+
|
143
|
+
#### Special Behaviors
|
144
|
+
- **Static Fields**: Values cached and never change after first resolution
|
145
|
+
- **Default Conditions**: Custom logic for when to use default values via `default_if`
|
146
|
+
|
147
|
+
## Data Flow
|
148
|
+
|
149
|
+
1. **Field Definition**: Developer calls `Configuration.field()` to define a configuration field
|
150
|
+
2. **Field Creation**: A new `Field` instance is created and stored in the configuration class
|
151
|
+
3. **Dynamic Method**: A getter method is dynamically created using `class_eval`
|
152
|
+
4. **Value Access**: When the getter is called, it delegates to `__get_value__`
|
153
|
+
5. **Source Resolution**: The field's `value()` method checks sources in precedence order
|
154
|
+
6. **Type Coercion**: Raw string values are coerced to the appropriate type
|
155
|
+
7. **Caching**: Static fields are memoized; others may use override values
|
156
|
+
|
157
|
+
## Key Design Patterns
|
158
|
+
|
159
|
+
### Factory Pattern
|
160
|
+
The `field()` method acts as a factory for creating `Field` instances with appropriate defaults and validations.
|
161
|
+
|
162
|
+
### Strategy Pattern
|
163
|
+
Different data sources (environment, runtime settings, YAML) are handled through a consistent interface but with source-specific logic.
|
164
|
+
|
165
|
+
### Template Method
|
166
|
+
The value resolution follows a template where the algorithm is defined in `Field#fetch_value_and_source`, but source-specific retrieval is delegated to private methods.
|
167
|
+
|
168
|
+
### Decorator Pattern
|
169
|
+
Field overrides decorate the normal value resolution with temporary alternative values.
|
170
|
+
|
171
|
+
## Inheritance Support
|
172
|
+
|
173
|
+
Configuration classes support inheritance:
|
174
|
+
- Subclasses inherit field definitions from parent classes
|
175
|
+
- Each class maintains its own singleton instance
|
176
|
+
- Prefixes and settings can be overridden per class
|
177
|
+
- Field definitions are merged from parent to child
|
178
|
+
|
179
|
+
## Thread Safety Considerations
|
180
|
+
|
181
|
+
- **Mutex Protection**: Critical sections are protected with mutex locks
|
182
|
+
- **Thread-Local Overrides**: Override values are stored per thread ID
|
183
|
+
- **Immutable Fields**: Field definitions are immutable after creation
|
184
|
+
- **Atomic Operations**: Value resolution is designed to be atomic
|
185
|
+
|
186
|
+
This architecture provides a robust, flexible, and thread-safe configuration management system that can adapt to various deployment environments and configuration sources.
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,26 @@ 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.6.0
|
8
|
+
|
9
|
+
### Added
|
10
|
+
|
11
|
+
- Added support for passing the type in `UltraSettings.runtime_settings_url` as `${type}` in the URL.
|
12
|
+
|
13
|
+
### Changed
|
14
|
+
|
15
|
+
- Significantly updated the web UI to improve the layout and usability. The UI is no longer an HTML table and has a cleaner, more modern design.
|
16
|
+
|
17
|
+
## 2.5.0
|
18
|
+
|
19
|
+
### Added
|
20
|
+
|
21
|
+
- Added mechanism for guarding against using runtime settings during initialization with the `UltraSettings::UninitializedRuntimeSettings` class.
|
22
|
+
|
23
|
+
### Changed
|
24
|
+
|
25
|
+
- Runtime settings will now try to load arrays from runtime settings using the `array` method if it is defined. This provides for better integration with the `super_settings` gem to avoid coercing values to and from strings.
|
26
|
+
|
7
27
|
## 2.4.5
|
8
28
|
|
9
29
|
### Fixed
|
data/README.md
CHANGED
@@ -170,6 +170,8 @@ end
|
|
170
170
|
UltraSettings.runtime_settings = RedisRuntimeSettings.new
|
171
171
|
```
|
172
172
|
|
173
|
+
The runtime settings implementation may also define an `array` method that takes a single parameter to return an array value. If this method is not implemented, then array values must be returned as single line CSV strings.
|
174
|
+
|
173
175
|
#### Using the `super_settings` gem
|
174
176
|
|
175
177
|
There is a companion gem [super_settings](https://github.com/bdurand/super_settings) that can be used as a drop in implementation for the runtime settings. You just need to set the runtime settings to the `SuperSettings` object.
|
@@ -178,6 +180,21 @@ There is a companion gem [super_settings](https://github.com/bdurand/super_setti
|
|
178
180
|
UltraSettings.runtime_settings = SuperSettings
|
179
181
|
```
|
180
182
|
|
183
|
+
#### Initialization Issues
|
184
|
+
|
185
|
+
Runtime settings should not be used during application initialization. Often the values set during initialization are static so it can be misleading to use runtime settings for them. There can also be race conditions where the runtime settings implementation itself needs to be initialized. You can guard against unintended usage of runtime settings during initialization by using the `UltraSettings::UninitializedRuntimeSettings` class. This class will raise an error if you try to access a setting before the runtime settings have been initialized.
|
186
|
+
|
187
|
+
```ruby
|
188
|
+
# Set the runtime settings to the uninitialized class to raise errors if anything tries to use
|
189
|
+
# reference a runtime setting during initializaton.
|
190
|
+
UltraSettings.runtime_settings = UltraSettings::UninitializedRuntimeSettings
|
191
|
+
|
192
|
+
# Switch to using the super_settings gem after the ActiveRecord has been initialized.
|
193
|
+
ActiveSupport.on_load(:active_record) do
|
194
|
+
UltraSettings.runtime_settings = SuperSettings
|
195
|
+
end
|
196
|
+
```
|
197
|
+
|
181
198
|
#### Customizing Runtime Settings
|
182
199
|
|
183
200
|
By default settings will be loaded from runtime settings by constructing a prefix from the configuration class name (i.e. `Configs::MySettingsConfiguration` uses the prefix `configs.my_settings.`) with the field name appended to it (e.g. `configs.my_settings.host`). By default runtime settings will be in all lowercase letters.
|
@@ -194,7 +211,7 @@ You can customize the behavior of runtime setting names with the following optio
|
|
194
211
|
|
195
212
|
- **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`.
|
196
213
|
|
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
|
214
|
+
- **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 placeholders `${name}` and `${type}` in the URL which will be replaced with the name and type of the runtime setting, respectively. If you are using the `super_settings` gem for runtime settings, then you can target a setting by adding `#edit=${name}&type=${type}` to the root URL where `super_settings` is mounted.
|
198
215
|
|
199
216
|
If a setting value cannot be loaded from the runtime settings, then it's value will attempt to be loaded from a YAML file.
|
200
217
|
|
@@ -405,17 +422,25 @@ If you prefer to embed the settings view directly into your own admin tools or d
|
|
405
422
|
```erb
|
406
423
|
<h1>Configuration</h1>
|
407
424
|
|
408
|
-
<%= UltraSettings::ApplicationView.new.render(select_class: "form-select"
|
425
|
+
<%= UltraSettings::ApplicationView.new.render(select_class: "form-select") %>
|
409
426
|
```
|
410
427
|
|
411
|
-
This approach allows for seamless integration of the settings UI into your application's admin interface, leveraging your existing authentication and authorization mechanisms. The settings are rendered
|
428
|
+
This approach allows for seamless integration of the settings UI into your application's admin interface, leveraging your existing authentication and authorization mechanisms. The settings are rendered with navigation handled by an HTML select element. You can specify the CSS classes for the select element to match your own application styles..
|
412
429
|
|
413
430
|
You can also embed the view for individual configurations within your own views using the `UltraSettings::ConfigurationView` class if you want more customization:
|
414
431
|
|
415
432
|
```erb
|
416
433
|
<h1>My Service Settings</h1>
|
417
434
|
|
418
|
-
<%= UltraSettings::ConfigurationView.new(MyServiceConfiguration.instance).render
|
435
|
+
<%= UltraSettings::ConfigurationView.new(MyServiceConfiguration.instance).render %>
|
436
|
+
```
|
437
|
+
|
438
|
+
You'll also need to include the CSS for the configuration view on your page.
|
439
|
+
|
440
|
+
```erb
|
441
|
+
<head>
|
442
|
+
<%= UltraSettings::ApplicationView.new.style_tag %>
|
443
|
+
</head>
|
419
444
|
```
|
420
445
|
|
421
446
|
### Testing With UltraSettings
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.6.0
|
data/app/application.css
CHANGED
@@ -6,41 +6,214 @@
|
|
6
6
|
margin: 0;
|
7
7
|
}
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
9
|
+
/* Configuration file header */
|
10
|
+
.ultra-settings-config-file {
|
11
|
+
margin-bottom: 1.5rem;
|
12
|
+
padding: 1rem;
|
13
|
+
background-color: var(--config-file-bg-color);
|
14
|
+
border: 1px solid var(--config-file-border-color);
|
15
|
+
border-radius: 0.375rem;
|
16
|
+
}
|
17
|
+
|
18
|
+
.ultra-settings-config-file-label {
|
19
|
+
font-weight: 600;
|
20
|
+
color: var(--text-color);
|
21
|
+
margin-right: 0.5rem;
|
22
|
+
}
|
23
|
+
|
24
|
+
.ultra-settings-config-file-path {
|
25
|
+
font-family: monospace;
|
26
|
+
font-size: 0.9rem;
|
27
|
+
color: var(--code-color);
|
28
|
+
font-weight: 600;
|
29
|
+
}
|
30
|
+
|
31
|
+
.ultra-settings-file-not-found {
|
32
|
+
color: var(--warning-color);
|
33
|
+
font-style: italic;
|
34
|
+
margin-left: 0.5rem;
|
35
|
+
}
|
36
|
+
|
37
|
+
/* Fields container */
|
38
|
+
.ultra-settings-fields {
|
39
|
+
display: flex;
|
40
|
+
flex-direction: column;
|
41
|
+
gap: 1rem;
|
14
42
|
}
|
15
43
|
|
16
|
-
|
17
|
-
|
18
|
-
border
|
19
|
-
|
44
|
+
/* Individual field card */
|
45
|
+
.ultra-settings-field {
|
46
|
+
border: 1px solid var(--field-border-color);
|
47
|
+
border-radius: 0.5rem;
|
48
|
+
background-color: var(--field-bg-color);
|
49
|
+
overflow: hidden;
|
20
50
|
}
|
21
51
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
52
|
+
/* Field header */
|
53
|
+
.ultra-settings-field-header {
|
54
|
+
display: flex;
|
55
|
+
justify-content: space-between;
|
56
|
+
align-items: center;
|
57
|
+
padding: 0.75rem 1rem;
|
58
|
+
background-color: var(--field-header-bg-color);
|
59
|
+
border-bottom: 1px solid var(--field-border-color);
|
26
60
|
}
|
27
61
|
|
28
|
-
.ultra-settings-
|
29
|
-
|
62
|
+
.ultra-settings-field-name {
|
63
|
+
display: flex;
|
64
|
+
align-items: center;
|
65
|
+
gap: 0.5rem;
|
30
66
|
}
|
31
67
|
|
32
|
-
.ultra-settings-
|
68
|
+
.ultra-settings-field-name code {
|
33
69
|
font-family: monospace;
|
34
|
-
font-size:
|
35
|
-
|
70
|
+
font-size: 1rem;
|
71
|
+
font-weight: 600;
|
36
72
|
color: var(--code-color);
|
73
|
+
}
|
74
|
+
|
75
|
+
.ultra-settings-field-type {
|
76
|
+
font-size: 0.875rem;
|
37
77
|
font-weight: 600;
|
78
|
+
color: var(--type-color);
|
79
|
+
text-transform: uppercase;
|
80
|
+
letter-spacing: 0.025em;
|
81
|
+
}
|
82
|
+
|
83
|
+
/* Field badges */
|
84
|
+
.ultra-settings-field-badge {
|
85
|
+
display: inline-block;
|
86
|
+
padding: 0.125rem 0.5rem;
|
87
|
+
font-size: 0.75rem;
|
88
|
+
font-weight: 500;
|
89
|
+
text-transform: uppercase;
|
90
|
+
letter-spacing: 0.025em;
|
91
|
+
border-radius: 0.25rem;
|
92
|
+
}
|
93
|
+
|
94
|
+
.ultra-settings-badge-secret {
|
95
|
+
background-color: var(--secret-badge-bg-color);
|
96
|
+
color: var(--secret-badge-text-color);
|
97
|
+
}
|
98
|
+
|
99
|
+
.ultra-settings-badge-static {
|
100
|
+
background-color: var(--static-badge-bg-color);
|
101
|
+
color: var(--static-badge-text-color);
|
102
|
+
}
|
103
|
+
|
104
|
+
/* Field value */
|
105
|
+
.ultra-settings-field-value {
|
106
|
+
padding: 1rem;
|
107
|
+
background-color: var(--value-bg-color);
|
108
|
+
border-bottom: 1px solid var(--field-border-color);
|
109
|
+
}
|
110
|
+
|
111
|
+
.ultra-settings-field-value code {
|
112
|
+
font-family: monospace;
|
113
|
+
font-size: 0.95rem;
|
114
|
+
word-break: break-all;
|
115
|
+
color: var(--value-text-color);
|
116
|
+
background-color: var(--value-code-bg-color);
|
117
|
+
padding: 0.25rem 0.5rem;
|
118
|
+
border-radius: 0.25rem;
|
119
|
+
border: 1px solid var(--value-code-border-color);
|
38
120
|
}
|
39
121
|
|
40
|
-
.ultra-settings-
|
41
|
-
color: var(--em-color);
|
122
|
+
.ultra-settings-nil-value {
|
42
123
|
font-style: italic;
|
124
|
+
color: var(--nil-color);
|
125
|
+
font-size: 0.9rem;
|
126
|
+
}
|
127
|
+
|
128
|
+
/* Field description */
|
129
|
+
.ultra-settings-field-description {
|
130
|
+
padding: 0.75rem 1rem;
|
131
|
+
color: var(--description-color);
|
43
132
|
font-size: 0.9rem;
|
133
|
+
line-height: 1.5;
|
134
|
+
border-bottom: 1px solid var(--field-border-color);
|
135
|
+
display: flex;
|
136
|
+
align-items: flex-start;
|
137
|
+
gap: 0.5rem;
|
138
|
+
}
|
139
|
+
|
140
|
+
.ultra-settings-description-text {
|
141
|
+
flex: 1;
|
142
|
+
}
|
143
|
+
|
144
|
+
.ultra-settings-info-icon {
|
145
|
+
width: 16px;
|
146
|
+
height: 16px;
|
147
|
+
flex-shrink: 0;
|
148
|
+
margin-top: 0.125rem; /* Align with first line of text based on line-height */
|
149
|
+
color: var(--description-color);
|
150
|
+
}
|
151
|
+
|
152
|
+
/* Sources section */
|
153
|
+
.ultra-settings-field-sources {
|
154
|
+
padding: 0.5rem;
|
155
|
+
}
|
156
|
+
|
157
|
+
.ultra-settings-source {
|
158
|
+
display: flex;
|
159
|
+
align-items: center;
|
160
|
+
justify-content: space-between;
|
161
|
+
padding: 0.5rem 0.75rem;
|
162
|
+
margin-bottom: 0.25rem;
|
163
|
+
border-radius: 0.25rem;
|
164
|
+
background-color: var(--source-bg-color);
|
165
|
+
border: 1px solid var(--source-border-color);
|
166
|
+
transition: all 0.2s ease;
|
167
|
+
}
|
168
|
+
|
169
|
+
.ultra-settings-source-active {
|
170
|
+
background-color: var(--source-active-bg-color);
|
171
|
+
border-color: var(--source-active-border-color);
|
172
|
+
}
|
173
|
+
|
174
|
+
.ultra-settings-source-type {
|
175
|
+
font-size: 0.8rem;
|
176
|
+
font-weight: 500;
|
177
|
+
color: var(--source-type-color);
|
178
|
+
text-transform: uppercase;
|
179
|
+
letter-spacing: 0.025em;
|
180
|
+
min-width: 120px;
|
181
|
+
}
|
182
|
+
|
183
|
+
.ultra-settings-source-value {
|
184
|
+
font-family: monospace;
|
185
|
+
font-size: 0.85rem;
|
186
|
+
color: var(--source-value-color);
|
187
|
+
flex: 1;
|
188
|
+
margin: 0 0.75rem;
|
189
|
+
word-break: break-all;
|
190
|
+
}
|
191
|
+
|
192
|
+
.ultra-settings-source-indicator {
|
193
|
+
font-size: 0.75rem;
|
194
|
+
font-weight: 600;
|
195
|
+
color: var(--source-indicator-color);
|
196
|
+
text-transform: uppercase;
|
197
|
+
letter-spacing: 0.025em;
|
198
|
+
}
|
199
|
+
|
200
|
+
.ultra-settings-edit-link {
|
201
|
+
color: var(--edit-link-color);
|
202
|
+
text-decoration: none;
|
203
|
+
margin-left: 0.5rem;
|
204
|
+
opacity: 0.7;
|
205
|
+
transition: opacity 0.2s ease;
|
206
|
+
display: inline-flex;
|
207
|
+
align-items: center;
|
208
|
+
}
|
209
|
+
|
210
|
+
.ultra-settings-edit-link:hover {
|
211
|
+
opacity: 1;
|
212
|
+
}
|
213
|
+
|
214
|
+
.ultra-settings-edit-icon {
|
215
|
+
width: 16px;
|
216
|
+
height: 16px;
|
44
217
|
}
|
45
218
|
|
46
219
|
.ultra-settings-select {
|
@@ -1,13 +1,42 @@
|
|
1
1
|
<% unless color_scheme == :dark %>
|
2
|
-
.ultra-settings {
|
3
|
-
--
|
4
|
-
--table-border-color: #dee2e6;
|
5
|
-
--alt-row-color: rgba(0, 0, 0, .05);
|
6
|
-
--form-control-color: #495057;
|
2
|
+
.ultra-settings, .ultra-settings-block {
|
3
|
+
--form-control-color: #484848;
|
7
4
|
--form-control-bg-color: #fff;
|
8
|
-
--form-control-border-color: #
|
9
|
-
--code-color:
|
10
|
-
|
5
|
+
--form-control-border-color: #e8e8e8;
|
6
|
+
--code-color: #d63384;
|
7
|
+
|
8
|
+
/* Card layout colors */
|
9
|
+
--config-file-bg-color: #f8f8f8;
|
10
|
+
--config-file-border-color: #e8e8e8;
|
11
|
+
--field-bg-color: #ffffff;
|
12
|
+
--field-border-color: #b8b8b8;
|
13
|
+
--field-header-bg-color: #f8f8f8;
|
14
|
+
--value-bg-color: #fdfdfe;
|
15
|
+
--value-text-color: #212529;
|
16
|
+
--value-code-bg-color: #f8f8f8;
|
17
|
+
--value-code-border-color: #e8e8e8;
|
18
|
+
--type-color: #6c757d;
|
19
|
+
--description-color: #4a83b5;
|
20
|
+
--nil-color: #6c757d;
|
21
|
+
--warning-color: #dc3545;
|
22
|
+
|
23
|
+
/* Source colors */
|
24
|
+
--source-bg-color: #f8f9fa;
|
25
|
+
--source-border-color: #e9ecef;
|
26
|
+
--source-active-bg-color: #e7f3ff;
|
27
|
+
--source-active-border-color: #0d6efd;
|
28
|
+
--source-type-color: #666666;
|
29
|
+
--source-value-color: #666666;
|
30
|
+
--source-indicator-color: #0d6efd;
|
31
|
+
|
32
|
+
/* Badge colors */
|
33
|
+
--secret-badge-bg-color: #dc3545;
|
34
|
+
--secret-badge-text-color: #ffffff;
|
35
|
+
--static-badge-bg-color: #888888;
|
36
|
+
--static-badge-text-color: #ffffff;
|
37
|
+
|
38
|
+
/* Edit link */
|
39
|
+
--edit-link-color: #0d6efd;
|
11
40
|
}
|
12
41
|
<% end %>
|
13
42
|
|
@@ -15,15 +44,45 @@
|
|
15
44
|
@media (prefers-color-scheme: dark) {
|
16
45
|
<% end %>
|
17
46
|
<% if color_scheme == :system || color_scheme == :dark %>
|
18
|
-
.ultra-settings {
|
19
|
-
--table-header-bg-color: #333;
|
20
|
-
--table-border-color: #555;
|
21
|
-
--alt-row-color: rgba(0, 0, 0, .30);
|
47
|
+
.ultra-settings, .ultra-settings-block {
|
22
48
|
--form-control-color: #eee;
|
23
49
|
--form-control-bg-color: #666;
|
24
50
|
--form-control-border-color: #555;
|
25
|
-
--code-color:
|
26
|
-
--em-color: #
|
51
|
+
--code-color: #ff6b9d;
|
52
|
+
--em-color: #adb5bd;
|
53
|
+
|
54
|
+
/* Card layout colors */
|
55
|
+
--config-file-bg-color: #2b2b2b;
|
56
|
+
--config-file-border-color: #444;
|
57
|
+
--field-bg-color: #1e1e1e;
|
58
|
+
--field-border-color: #444;
|
59
|
+
--field-header-bg-color: #2b2b2b;
|
60
|
+
--value-bg-color: #252525;
|
61
|
+
--value-text-color: #e9ecef;
|
62
|
+
--value-code-bg-color: #2b2b2b;
|
63
|
+
--value-code-border-color: #444;
|
64
|
+
--type-color: #adb5bd;
|
65
|
+
--description-color: #a9d9f8;
|
66
|
+
--nil-color: #cdcecfff;
|
67
|
+
--warning-color: #dc3545;
|
68
|
+
|
69
|
+
/* Source colors */
|
70
|
+
--source-bg-color: #2b2b2b;
|
71
|
+
--source-border-color: #444444;
|
72
|
+
--source-active-bg-color: #1a3a52;
|
73
|
+
--source-active-border-color: #0d6efd;
|
74
|
+
--source-type-color: #adb5bd;
|
75
|
+
--source-value-color: #ced4da;
|
76
|
+
--source-indicator-color: #6ea8fe;
|
77
|
+
|
78
|
+
/* Badge colors */
|
79
|
+
--secret-badge-bg-color: #dc3545;
|
80
|
+
--secret-badge-text-color: #ffffff;
|
81
|
+
--static-badge-bg-color: #6c757d;
|
82
|
+
--static-badge-text-color: #ffffff;
|
83
|
+
|
84
|
+
/* Edit link */
|
85
|
+
--edit-link-color: #a7c6f5;
|
27
86
|
}
|
28
87
|
<% end %>
|
29
88
|
<% if color_scheme == :system %>
|
data/app/configuration.html.erb
CHANGED
@@ -1,141 +1,112 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
<%= html_escape(relative_path(configuration.class.configuration_file)) %>
|
9
|
-
<% unless configuration.class.configuration_file&.exist? %>
|
10
|
-
<em>(File does not exist)</em>
|
11
|
-
<% end %>
|
12
|
-
</span>
|
13
|
-
</th>
|
14
|
-
</tr>
|
1
|
+
<div class="ultra-settings-block">
|
2
|
+
<% if !configuration.class.yaml_config_disabled? && configuration.class.configuration_file.is_a?(Pathname) %>
|
3
|
+
<div class="ultra-settings-config-file">
|
4
|
+
<span class="ultra-settings-config-file-label">Configuration File:</span>
|
5
|
+
<code class="ultra-settings-config-file-path"><%= html_escape(relative_path(configuration.class.configuration_file)) %></code>
|
6
|
+
<% unless configuration.class.configuration_file&.exist? %>
|
7
|
+
<span class="ultra-settings-file-not-found">(File does not exist)</span>
|
15
8
|
<% end %>
|
16
|
-
|
17
|
-
|
18
|
-
<th>Value</th>
|
19
|
-
<th>Type</th>
|
20
|
-
<th>Notes</th>
|
21
|
-
</tr>
|
22
|
-
</thead>
|
23
|
-
<tbody translate="no">
|
24
|
-
<% configuration.class.fields.each do |field| %>
|
25
|
-
<% source = configuration.__source__(field.name) %>
|
26
|
-
<tr>
|
27
|
-
<td>
|
28
|
-
<code><%= html_escape(field.name) %></code>
|
29
|
-
</td>
|
9
|
+
</div>
|
10
|
+
<% end %>
|
30
11
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
12
|
+
<div class="ultra-settings-fields">
|
13
|
+
<% configuration.class.fields.each do |field| %>
|
14
|
+
<% source = configuration.__source__(field.name) %>
|
15
|
+
<div class="ultra-settings-field">
|
16
|
+
<div class="ultra-settings-field-header">
|
17
|
+
<div class="ultra-settings-field-name">
|
18
|
+
<code><%= html_escape(field.name) %></code>
|
19
|
+
<% if field.secret? %>
|
20
|
+
<span class="ultra-settings-field-badge ultra-settings-badge-secret">secret</span>
|
38
21
|
<% end %>
|
39
|
-
|
40
|
-
|
41
|
-
<td>
|
42
|
-
<%= html_escape(field.type) %>
|
43
|
-
<%
|
44
|
-
options = []
|
45
|
-
options << 'static' if field.static?
|
46
|
-
options << 'secret' if field.secret?
|
47
|
-
%>
|
48
|
-
<% unless options.empty? %>
|
49
|
-
<div>
|
50
|
-
<em><%= html_escape(options.join(', ')) %></em>
|
51
|
-
</div>
|
22
|
+
<% if field.static? %>
|
23
|
+
<span class="ultra-settings-field-badge ultra-settings-badge-static">static</span>
|
52
24
|
<% end %>
|
53
|
-
</
|
25
|
+
</div>
|
26
|
+
<div class="ultra-settings-field-type">
|
27
|
+
<%= html_escape(field.type) %>
|
28
|
+
</div>
|
29
|
+
</div>
|
54
30
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
31
|
+
<div class="ultra-settings-field-value">
|
32
|
+
<% if configuration[field.name].nil? %>
|
33
|
+
<span class="ultra-settings-nil-value">nil</span>
|
34
|
+
<% elsif field.secret? %>
|
35
|
+
<code><%= html_escape(secret_value(configuration[field.name])) %></code>
|
36
|
+
<% else %>
|
37
|
+
<code><%= html_escape(display_value(configuration[field.name])) %></code>
|
38
|
+
<% end %>
|
39
|
+
</div>
|
40
|
+
|
41
|
+
<% unless field.description.to_s.empty? %>
|
42
|
+
<div class="ultra-settings-field-description">
|
43
|
+
<svg class="ultra-settings-info-icon" width="16" height="16" fill="currentColor">
|
44
|
+
<use href="#info-icon"/>
|
45
|
+
</svg>
|
46
|
+
<div class="ultra-settings-description-text">
|
47
|
+
<%= html_escape(field.description) %>
|
48
|
+
</div>
|
49
|
+
</div>
|
50
|
+
<% end %>
|
61
51
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
Can be
|
70
|
-
<% end %>
|
71
|
-
set with the environment variable
|
72
|
-
<code><%= show_defined_value(field.env_var, configuration.__value_from_source__(field.name, :env), field.secret?) %></code>
|
73
|
-
<% if source == :env %>
|
74
|
-
</strong>
|
75
|
-
<% end %>
|
76
|
-
</li>
|
52
|
+
<div class="ultra-settings-field-sources">
|
53
|
+
<% if field.env_var && !configuration.class.environment_variables_disabled? %>
|
54
|
+
<div class="ultra-settings-source <%= 'ultra-settings-source-active' if source == :env %>">
|
55
|
+
<span class="ultra-settings-source-type">Environment Variable</span>
|
56
|
+
<code class="ultra-settings-source-value"><%= show_defined_value(field.env_var, configuration.__value_from_source__(field.name, :env), field.secret?) %></code>
|
57
|
+
<% if source == :env %>
|
58
|
+
<span class="ultra-settings-source-indicator">Currently active</span>
|
77
59
|
<% end %>
|
78
|
-
|
79
|
-
|
80
|
-
<% if source == :settings %>
|
81
|
-
<strong>
|
82
|
-
Currently
|
83
|
-
<% else %>
|
84
|
-
Can be
|
85
|
-
<% end %>
|
86
|
-
set with the runtime setting
|
87
|
-
<code><%= show_defined_value(field.runtime_setting, configuration.__value_from_source__(field.name, :settings), field.secret?) %></code>
|
88
|
-
<% if source == :settings %>
|
89
|
-
</strong>
|
90
|
-
<% end %>
|
60
|
+
</div>
|
61
|
+
<% end %>
|
91
62
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
</svg>
|
99
|
-
</a>
|
100
|
-
<% end %>
|
101
|
-
</li>
|
63
|
+
<% if field.runtime_setting && !configuration.class.runtime_settings_disabled? %>
|
64
|
+
<div class="ultra-settings-source <%= 'ultra-settings-source-active' if source == :settings %>">
|
65
|
+
<span class="ultra-settings-source-type">Runtime Setting</span>
|
66
|
+
<code class="ultra-settings-source-value"><%= show_defined_value(field.runtime_setting, configuration.__value_from_source__(field.name, :settings), field.secret?) %></code>
|
67
|
+
<% if source == :settings %>
|
68
|
+
<span class="ultra-settings-source-indicator">Currently active</span>
|
102
69
|
<% end %>
|
103
|
-
<%
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
<% end %>
|
111
|
-
set with the configuration file key
|
112
|
-
<code><%= show_defined_value(field.yaml_key, configuration.__value_from_source__(field.name, :yaml), field.secret?) %></code>
|
113
|
-
<% if source == :yaml %>
|
114
|
-
</strong>
|
115
|
-
<% end %>
|
116
|
-
</li>
|
70
|
+
<% edit_url = UltraSettings.runtime_settings_url(field.runtime_setting, field.type) %>
|
71
|
+
<% if edit_url %>
|
72
|
+
<a href="<%= html_escape(edit_url) %>" class="ultra-settings-edit-link" title="Edit <%= html_escape(field.runtime_setting) %>">
|
73
|
+
<svg class="ultra-settings-edit-icon" width="16" height="16" fill="currentColor">
|
74
|
+
<use href="#edit-icon"/>
|
75
|
+
</svg>
|
76
|
+
</a>
|
117
77
|
<% end %>
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
<strong>
|
128
|
-
Currently set with the
|
129
|
-
<%= show_defined_value("default value", field.default, field.secret?) %>.
|
130
|
-
</strong>
|
131
|
-
<% else %>
|
132
|
-
This field has a <%= show_defined_value("default value", field.default, field.secret?) %>.
|
133
|
-
<% end %>
|
134
|
-
</li>
|
78
|
+
</div>
|
79
|
+
<% end %>
|
80
|
+
|
81
|
+
<% if field.yaml_key && !configuration.class.yaml_config_disabled? %>
|
82
|
+
<div class="ultra-settings-source <%= 'ultra-settings-source-active' if source == :yaml %>">
|
83
|
+
<span class="ultra-settings-source-type">Configuration File</span>
|
84
|
+
<code class="ultra-settings-source-value"><%= show_defined_value(field.yaml_key, configuration.__value_from_source__(field.name, :yaml), field.secret?) %></code>
|
85
|
+
<% if source == :yaml %>
|
86
|
+
<span class="ultra-settings-source-indicator">Currently active</span>
|
135
87
|
<% end %>
|
136
|
-
</
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
88
|
+
</div>
|
89
|
+
<% end %>
|
90
|
+
|
91
|
+
<% if field.default.nil? %>
|
92
|
+
<% if source == :default %>
|
93
|
+
<div class="ultra-settings-source ultra-settings-source-active">
|
94
|
+
<span class="ultra-settings-source-type">Default Value</span>
|
95
|
+
<span class="ultra-settings-source-value"><em>Not set</em></span>
|
96
|
+
<span class="ultra-settings-source-indicator">Currently active</span>
|
97
|
+
</div>
|
98
|
+
<% end %>
|
99
|
+
<% else %>
|
100
|
+
<div class="ultra-settings-source <%= 'ultra-settings-source-active' if source == :default %>">
|
101
|
+
<span class="ultra-settings-source-type">Default Value</span>
|
102
|
+
<code class="ultra-settings-source-value"><%= show_defined_value("", field.default, field.secret?) %></code>
|
103
|
+
<% if source == :default %>
|
104
|
+
<span class="ultra-settings-source-indicator">Currently active</span>
|
105
|
+
<% end %>
|
106
|
+
</div>
|
107
|
+
<% end %>
|
108
|
+
</div>
|
109
|
+
</div>
|
110
|
+
<% end %>
|
111
|
+
</div>
|
112
|
+
</div>
|
data/app/index.html.erb
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
<%= style_tag %>
|
2
|
+
|
3
|
+
<!-- SVG Icons -->
|
4
|
+
<svg style="display: none;" xmlns="http://www.w3.org/2000/svg">
|
5
|
+
<defs>
|
6
|
+
<symbol id="edit-icon" viewBox="0 0 16 16">
|
7
|
+
<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"/>
|
8
|
+
<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"/>
|
9
|
+
</symbol>
|
10
|
+
<symbol id="info-icon" viewBox="0 0 16 16">
|
11
|
+
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"/>
|
12
|
+
<path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0"/>
|
13
|
+
</symbol>
|
14
|
+
</defs>
|
15
|
+
</svg>
|
16
|
+
|
1
17
|
<div class="ultra-settings">
|
2
18
|
<div class="ultra-settings-nav">
|
3
19
|
<form onsubmit="return false" style="margin-bottom: 0.5rem;">
|
data/app/layout.html.erb
CHANGED
@@ -7,8 +7,7 @@
|
|
7
7
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
8
8
|
<meta name="format-detection" content="telephone=no email=no date=no address=no">
|
9
9
|
<style type="text/css">
|
10
|
-
<%=
|
11
|
-
<%= css %>
|
10
|
+
<%= layout_css %>
|
12
11
|
</style>
|
13
12
|
</head>
|
14
13
|
<body>
|
@@ -4,44 +4,34 @@ module UltraSettings
|
|
4
4
|
# This class can render information about all configurations. It is used by the bundled
|
5
5
|
# web UI, but you can use it to embed the configuration information in your own web pages.
|
6
6
|
#
|
7
|
-
# The output will be a simple HTML drop down list that can be used to display an HTML
|
8
|
-
# showing each configuration. You can specify the CSS class for the select element
|
9
|
-
#
|
10
|
-
#
|
11
|
-
# `ultra-settings-table`.
|
7
|
+
# The output will be a simple HTML drop down list that can be used to display an HTML element
|
8
|
+
# showing each configuration. You can specify the CSS class for the select element by passing
|
9
|
+
# the `select_class` option to the `render` method. By default the select element has
|
10
|
+
# the class `ultra-settings-select`.
|
12
11
|
#
|
13
12
|
# @example
|
14
13
|
# <h1>Application Configuration</h1>
|
15
|
-
# <%= UltraSettings::ApplicationView.new.render(select_class: 'form-control'
|
14
|
+
# <%= UltraSettings::ApplicationView.new.render(select_class: 'form-control') %>
|
16
15
|
class ApplicationView
|
17
|
-
|
16
|
+
attr_reader :css
|
18
17
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
23
|
-
|
24
|
-
def javascript
|
25
|
-
@javascript = read_app_file("application.js")
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
|
30
|
-
def read_app_file(path)
|
31
|
-
File.read(File.join(app_dir, path))
|
32
|
-
end
|
33
|
-
|
34
|
-
def app_dir
|
35
|
-
File.expand_path(File.join("..", "..", "app"), __dir__)
|
36
|
-
end
|
18
|
+
def initialize(color_scheme: :light)
|
19
|
+
@css = application_css(color_scheme)
|
20
|
+
@css = @css.html_safe if @css.respond_to?(:html_safe)
|
37
21
|
end
|
38
22
|
|
39
|
-
def render(select_class: "ultra-settings-select", table_class: "
|
40
|
-
html =
|
23
|
+
def render(select_class: "ultra-settings-select", table_class: "")
|
24
|
+
html = ViewHelper.erb_template("index.html.erb").result(binding)
|
41
25
|
html = html.html_safe if html.respond_to?(:html_safe)
|
42
26
|
html
|
43
27
|
end
|
44
28
|
|
29
|
+
def style_tag
|
30
|
+
tag = "<style type=\"text/css\">\n#{css}\n</style>"
|
31
|
+
tag = tag.html_safe if tag.respond_to?(:html_safe)
|
32
|
+
tag
|
33
|
+
end
|
34
|
+
|
45
35
|
def to_s
|
46
36
|
render
|
47
37
|
end
|
@@ -53,7 +43,13 @@ module UltraSettings
|
|
53
43
|
end
|
54
44
|
|
55
45
|
def javascript
|
56
|
-
|
46
|
+
ViewHelper.read_app_file("application.js")
|
47
|
+
end
|
48
|
+
|
49
|
+
def application_css(color_scheme)
|
50
|
+
vars = ViewHelper.erb_template("application_vars.css.erb").result(binding).strip
|
51
|
+
css = ViewHelper.read_app_file("application.css").strip
|
52
|
+
"#{vars}\n#{css}"
|
57
53
|
end
|
58
54
|
end
|
59
55
|
end
|
@@ -1,42 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module UltraSettings
|
4
|
-
# This class can render information about a configuration in
|
4
|
+
# This class can render information about a configuration in a clean card-based layout. It is used by the
|
5
5
|
# bundled web UI, but you can use it to embed the configuration information in your own web pages.
|
6
6
|
#
|
7
|
-
# The output will be
|
8
|
-
#
|
9
|
-
# `ultra-settings-table`.
|
7
|
+
# The output will be HTML with a card-based layout for better readability. The `table_class` option is
|
8
|
+
# still supported for backward compatibility but is no longer used in the new card layout.
|
10
9
|
#
|
11
10
|
# @example
|
12
11
|
# <h1>Service Configuration</h1>
|
13
|
-
# <%= UltraSettings::ConfigurationView.new(ServiceConfiguration.instance).render
|
12
|
+
# <%= UltraSettings::ConfigurationView.new(ServiceConfiguration.instance).render %>
|
14
13
|
class ConfigurationView
|
15
|
-
@template = nil
|
16
|
-
|
17
|
-
class << self
|
18
|
-
def template
|
19
|
-
@template ||= ERB.new(read_app_file("configuration.html.erb"))
|
20
|
-
end
|
21
|
-
|
22
|
-
private
|
23
|
-
|
24
|
-
def read_app_file(path)
|
25
|
-
File.read(File.join(app_dir, path))
|
26
|
-
end
|
27
|
-
|
28
|
-
def app_dir
|
29
|
-
File.expand_path(File.join("..", "..", "app"), __dir__)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
14
|
def initialize(configuration)
|
34
15
|
@configuration = configuration
|
35
16
|
end
|
36
17
|
|
37
|
-
def render(table_class: "
|
18
|
+
def render(table_class: "")
|
38
19
|
configuration = @configuration
|
39
|
-
html =
|
20
|
+
html = ViewHelper.erb_template("configuration.html.erb").result(binding)
|
40
21
|
html = html.html_safe if html.respond_to?(:html_safe)
|
41
22
|
html
|
42
23
|
end
|
data/lib/ultra_settings/field.rb
CHANGED
@@ -99,7 +99,7 @@ module UltraSettings
|
|
99
99
|
value = env[env_var] if env && env_var
|
100
100
|
value = nil if value == ""
|
101
101
|
if value.nil?
|
102
|
-
value = settings
|
102
|
+
value = runtime_setting_value(settings)
|
103
103
|
value = nil if value == ""
|
104
104
|
if value.nil?
|
105
105
|
value = yaml_value(yaml_config)
|
@@ -117,6 +117,18 @@ module UltraSettings
|
|
117
117
|
[value, source]
|
118
118
|
end
|
119
119
|
|
120
|
+
def runtime_setting_value(settings)
|
121
|
+
return nil unless settings && runtime_setting
|
122
|
+
|
123
|
+
if type == :array && settings.respond_to?(:array)
|
124
|
+
if settings.method(:array).parameters.count { |ptype, pname| ptype == :req } == 1
|
125
|
+
return settings.array(runtime_setting)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
settings[runtime_setting]
|
130
|
+
end
|
131
|
+
|
120
132
|
def yaml_value(yaml_config)
|
121
133
|
return nil unless yaml_config && yaml_key
|
122
134
|
|
@@ -9,7 +9,6 @@ module UltraSettings
|
|
9
9
|
config.ultra_settings = ActiveSupport::OrderedOptions.new
|
10
10
|
config.ultra_settings.auto_load_directories ||= [File.join("app", "configurations")]
|
11
11
|
|
12
|
-
# initializer "ultra_settings.before_bootstrap", before: :bootstrap_hook do
|
13
12
|
config.before_configuration do
|
14
13
|
UltraSettings::Configuration.yaml_config_env ||= Rails.env
|
15
14
|
UltraSettings::Configuration.yaml_config_path ||= Rails.root.join("config")
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module UltraSettings
|
4
|
+
# This class is used to represent runtime settings that have not been initialized yet.
|
5
|
+
# You can use this to protect your application from accidentally accessing runtime settings
|
6
|
+
# before they are initialized. Doing this can cquse unexpected behavior if the runtime settings
|
7
|
+
# engine has not yet been initialized. For instance, if your runtime settings enging reads from
|
8
|
+
# a database it would not be available until the database connection is established.
|
9
|
+
#
|
10
|
+
# The intention of this class is to set it a the runtime settings at the beginning of initialization
|
11
|
+
# and then set the actual runtime settings engine after the initialization is complete. It will
|
12
|
+
# act as a guard to prevent invalid runtime settings backed configurations from being used during
|
13
|
+
# initialization.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
#
|
17
|
+
# UltraSettings.runtime_settings = UltraSettings::UninitializedRuntimeSettings
|
18
|
+
# ActiveSupport.on_load(:active_record) do
|
19
|
+
# UltraSettings.runtime_settings = SuperSettings
|
20
|
+
# end
|
21
|
+
class UninitializedRuntimeSettings
|
22
|
+
class Error < StandardError
|
23
|
+
end
|
24
|
+
|
25
|
+
class << self
|
26
|
+
def [](key)
|
27
|
+
raise Error.new("Attempt to call runtime setting #{key} during initialization")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module UltraSettings
|
4
|
+
# Base class for rendering views.
|
5
|
+
module ViewHelper
|
6
|
+
@cache = {}
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def erb_template(path)
|
10
|
+
@cache["erb:#{path}"] ||= ERB.new(read_app_file(path))
|
11
|
+
end
|
12
|
+
|
13
|
+
def read_app_file(path)
|
14
|
+
@cache["file:#{path}"] ||= File.read(File.join(app_dir, path))
|
15
|
+
end
|
16
|
+
|
17
|
+
def app_dir
|
18
|
+
File.expand_path(File.join("..", "..", "app"), __dir__)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -3,15 +3,14 @@
|
|
3
3
|
module UltraSettings
|
4
4
|
# Helper class for rendering the settings information in an HTML page.
|
5
5
|
class WebView
|
6
|
-
attr_reader :
|
6
|
+
attr_reader :layout_css
|
7
7
|
|
8
8
|
# @param color_scheme [Symbol] The color scheme to use in the UI. This can be `:light`,
|
9
9
|
# `:dark`, or `:system`. The default is `:light`.
|
10
10
|
def initialize(color_scheme: :light)
|
11
|
-
color_scheme = (color_scheme || :light).to_sym
|
12
|
-
@layout_template = erb_template("layout.html.erb")
|
13
|
-
@layout_css =
|
14
|
-
@css = application_css(color_scheme)
|
11
|
+
@color_scheme = (color_scheme || :light).to_sym
|
12
|
+
@layout_template = ViewHelper.erb_template("layout.html.erb")
|
13
|
+
@layout_css = scheme_layout_css(@color_scheme)
|
15
14
|
end
|
16
15
|
|
17
16
|
def render_settings
|
@@ -19,32 +18,14 @@ module UltraSettings
|
|
19
18
|
end
|
20
19
|
|
21
20
|
def content
|
22
|
-
UltraSettings::ApplicationView.new.render
|
21
|
+
UltraSettings::ApplicationView.new(color_scheme: @color_scheme).render
|
23
22
|
end
|
24
23
|
|
25
24
|
private
|
26
25
|
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
def read_app_file(path)
|
32
|
-
File.read(File.join(app_dir, path))
|
33
|
-
end
|
34
|
-
|
35
|
-
def app_dir
|
36
|
-
File.expand_path(File.join("..", "..", "app"), __dir__)
|
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")
|
26
|
+
def scheme_layout_css(color_scheme)
|
27
|
+
vars = ViewHelper.erb_template("layout_vars.css.erb").result(binding)
|
28
|
+
css = ViewHelper.read_app_file("layout.css")
|
48
29
|
"#{vars}\n#{css}"
|
49
30
|
end
|
50
31
|
end
|
data/lib/ultra_settings.rb
CHANGED
@@ -13,9 +13,11 @@ require_relative "ultra_settings/coerce"
|
|
13
13
|
require_relative "ultra_settings/config_helper"
|
14
14
|
require_relative "ultra_settings/field"
|
15
15
|
require_relative "ultra_settings/rack_app"
|
16
|
+
require_relative "ultra_settings/view_helper"
|
16
17
|
require_relative "ultra_settings/web_view"
|
17
18
|
require_relative "ultra_settings/application_view"
|
18
19
|
require_relative "ultra_settings/configuration_view"
|
20
|
+
require_relative "ultra_settings/uninitialized_runtime_settings"
|
19
21
|
require_relative "ultra_settings/yaml_config"
|
20
22
|
require_relative "ultra_settings/version"
|
21
23
|
|
@@ -181,11 +183,12 @@ module UltraSettings
|
|
181
183
|
# @param name [String] The name of the setting.
|
182
184
|
# @return [String, nil]
|
183
185
|
# @api private
|
184
|
-
def runtime_settings_url(name)
|
186
|
+
def runtime_settings_url(name, type)
|
185
187
|
url = @runtime_settings_url.to_s
|
186
188
|
return nil if url.empty?
|
187
189
|
|
188
|
-
url.gsub("${name}", URI.encode_www_form_component(name))
|
190
|
+
url = url.gsub("${name}", URI.encode_www_form_component(name.to_s))
|
191
|
+
url.gsub("${type}", URI.encode_www_form_component(type.to_s))
|
189
192
|
end
|
190
193
|
|
191
194
|
def fields_secret_by_default=(value)
|
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.
|
4
|
+
version: 2.6.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:
|
11
|
+
date: 2025-07-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -31,6 +31,7 @@ executables: []
|
|
31
31
|
extensions: []
|
32
32
|
extra_rdoc_files: []
|
33
33
|
files:
|
34
|
+
- ARCHITECTURE.md
|
34
35
|
- CHANGELOG.md
|
35
36
|
- MIT-LICENSE.txt
|
36
37
|
- README.md
|
@@ -52,7 +53,9 @@ files:
|
|
52
53
|
- lib/ultra_settings/field.rb
|
53
54
|
- lib/ultra_settings/rack_app.rb
|
54
55
|
- lib/ultra_settings/railtie.rb
|
56
|
+
- lib/ultra_settings/uninitialized_runtime_settings.rb
|
55
57
|
- lib/ultra_settings/version.rb
|
58
|
+
- lib/ultra_settings/view_helper.rb
|
56
59
|
- lib/ultra_settings/web_view.rb
|
57
60
|
- lib/ultra_settings/yaml_config.rb
|
58
61
|
- ultra_settings.gemspec
|