ultra_settings 2.5.0 → 2.6.1
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 +16 -0
- data/README.md +12 -4
- data/VERSION +1 -1
- data/app/application.css +232 -21
- data/app/application_vars.css.erb +79 -14
- data/app/configuration.html.erb +114 -130
- data/app/index.html.erb +2 -0
- data/app/layout.html.erb +1 -2
- data/lib/ultra_settings/application_view.rb +24 -28
- data/lib/ultra_settings/configuration_view.rb +90 -30
- data/lib/ultra_settings/railtie.rb +0 -1
- data/lib/ultra_settings/view_helper.rb +22 -0
- data/lib/ultra_settings/web_view.rb +8 -27
- data/lib/ultra_settings.rb +4 -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: ec11cc8661b191dbb102deec0798f47ef11495feb8675f2f5b83e3e5d752855f
|
4
|
+
data.tar.gz: e3f729687eaad31be948aa8f7b4818f25fb842cec8261058aa1a9f62616639c6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 455632dc476fc7ed73a70d784d97de612db55c1c2650e31b0896727f9747c8e4ddc3a2bfcdf9c2b190180c4f3d157aca427b75abf68c6e5f312b1251d9f24fc5
|
7
|
+
data.tar.gz: 83e7686584d1ea62747d693c17f9f43908d70a5f5b45541d9f129b2e33e92521b0ce0c87d4c8ec10e3416afd186fefee93f281a6bfa0871c8b56e741d1fc44f6
|
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,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.6.1
|
8
|
+
|
9
|
+
### Added
|
10
|
+
|
11
|
+
- Show icons on the web UI that open a dialog with the current value for each data source.
|
12
|
+
|
13
|
+
## 2.6.0
|
14
|
+
|
15
|
+
### Added
|
16
|
+
|
17
|
+
- Added support for passing the type in `UltraSettings.runtime_settings_url` as `${type}` in the URL.
|
18
|
+
|
19
|
+
### Changed
|
20
|
+
|
21
|
+
- 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.
|
22
|
+
|
7
23
|
## 2.5.0
|
8
24
|
|
9
25
|
### Added
|
data/README.md
CHANGED
@@ -211,7 +211,7 @@ You can customize the behavior of runtime setting names with the following optio
|
|
211
211
|
|
212
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`.
|
213
213
|
|
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
|
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.
|
215
215
|
|
216
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.
|
217
217
|
|
@@ -422,17 +422,25 @@ If you prefer to embed the settings view directly into your own admin tools or d
|
|
422
422
|
```erb
|
423
423
|
<h1>Configuration</h1>
|
424
424
|
|
425
|
-
<%= UltraSettings::ApplicationView.new.render(select_class: "form-select"
|
425
|
+
<%= UltraSettings::ApplicationView.new.render(select_class: "form-select") %>
|
426
426
|
```
|
427
427
|
|
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
|
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..
|
429
429
|
|
430
430
|
You can also embed the view for individual configurations within your own views using the `UltraSettings::ConfigurationView` class if you want more customization:
|
431
431
|
|
432
432
|
```erb
|
433
433
|
<h1>My Service Settings</h1>
|
434
434
|
|
435
|
-
<%= 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>
|
436
444
|
```
|
437
445
|
|
438
446
|
### Testing With UltraSettings
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.6.1
|
data/app/application.css
CHANGED
@@ -6,41 +6,216 @@
|
|
6
6
|
margin: 0;
|
7
7
|
}
|
8
8
|
|
9
|
-
.ultra-settings-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
border
|
9
|
+
.ultra-settings-config-file {
|
10
|
+
margin-bottom: 1.5rem;
|
11
|
+
padding: 1rem;
|
12
|
+
background-color: var(--config-file-bg-color);
|
13
|
+
border: 1px solid var(--config-file-border-color);
|
14
|
+
border-radius: 0.375rem;
|
15
|
+
}
|
16
|
+
|
17
|
+
.ultra-settings-config-file-label {
|
18
|
+
font-weight: 600;
|
19
|
+
color: var(--text-color);
|
20
|
+
margin-right: 0.5rem;
|
21
|
+
}
|
22
|
+
|
23
|
+
.ultra-settings-config-file-path {
|
24
|
+
font-family: monospace;
|
25
|
+
font-size: 0.875rem;
|
26
|
+
color: var(--code-color);
|
27
|
+
font-weight: 600;
|
14
28
|
}
|
15
29
|
|
16
|
-
.ultra-settings-
|
17
|
-
|
18
|
-
|
19
|
-
|
30
|
+
.ultra-settings-file-not-found {
|
31
|
+
color: var(--warning-color);
|
32
|
+
font-style: italic;
|
33
|
+
margin-left: 0.5rem;
|
20
34
|
}
|
21
35
|
|
22
|
-
.ultra-settings-
|
23
|
-
|
24
|
-
|
25
|
-
|
36
|
+
.ultra-settings-fields {
|
37
|
+
display: flex;
|
38
|
+
flex-direction: column;
|
39
|
+
gap: 1.75rem;
|
26
40
|
}
|
27
41
|
|
28
|
-
.ultra-settings-
|
29
|
-
|
42
|
+
.ultra-settings-fields svg {
|
43
|
+
vertical-align: middle;
|
30
44
|
}
|
31
45
|
|
32
|
-
.ultra-settings-
|
46
|
+
.ultra-settings-field {
|
47
|
+
border: 1px solid var(--field-border-color);
|
48
|
+
border-radius: 0.5rem;
|
49
|
+
background-color: var(--field-bg-color);
|
50
|
+
overflow: hidden;
|
51
|
+
}
|
52
|
+
|
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);
|
60
|
+
}
|
61
|
+
|
62
|
+
.ultra-settings-field-name {
|
63
|
+
display: flex;
|
64
|
+
align-items: center;
|
65
|
+
gap: 0.5rem;
|
66
|
+
}
|
67
|
+
|
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
|
+
.ultra-settings-field-badge {
|
84
|
+
display: inline-block;
|
85
|
+
padding: 0.125rem 0.5rem;
|
86
|
+
font-size: 0.75rem;
|
87
|
+
font-weight: 500;
|
88
|
+
text-transform: uppercase;
|
89
|
+
letter-spacing: 0.025em;
|
90
|
+
border-radius: 0.25rem;
|
91
|
+
}
|
92
|
+
|
93
|
+
.ultra-settings-badge-secret {
|
94
|
+
background-color: var(--secret-badge-bg-color);
|
95
|
+
color: var(--secret-badge-text-color);
|
96
|
+
}
|
97
|
+
|
98
|
+
.ultra-settings-badge-static {
|
99
|
+
background-color: var(--static-badge-bg-color);
|
100
|
+
color: var(--static-badge-text-color);
|
101
|
+
}
|
102
|
+
|
103
|
+
.ultra-settings-field-value {
|
104
|
+
padding: 1rem;
|
105
|
+
background-color: var(--value-bg-color);
|
106
|
+
border-bottom: 1px solid var(--field-border-color);
|
38
107
|
}
|
39
108
|
|
40
|
-
.ultra-settings-
|
41
|
-
|
109
|
+
code.ultra-settings-field-data-value {
|
110
|
+
font-family: monospace;
|
111
|
+
font-size: 0.875rem;
|
112
|
+
word-break: break-all;
|
113
|
+
color: var(--value-text-color);
|
114
|
+
background-color: var(--value-code-bg-color);
|
115
|
+
padding: 0.25rem 0.5rem;
|
116
|
+
border-radius: 0.25rem;
|
117
|
+
border: 1px solid var(--value-code-border-color);
|
118
|
+
}
|
119
|
+
|
120
|
+
.ultra-settings-nil-value {
|
42
121
|
font-style: italic;
|
43
|
-
|
122
|
+
color: var(--nil-color);
|
123
|
+
}
|
124
|
+
|
125
|
+
.ultra-settings-field-description {
|
126
|
+
padding: 0.75rem 1rem;
|
127
|
+
color: var(--description-color);
|
128
|
+
line-height: 1.5;
|
129
|
+
border-bottom: 1px solid var(--field-border-color);
|
130
|
+
display: flex;
|
131
|
+
align-items: flex-start;
|
132
|
+
gap: 0.5rem;
|
133
|
+
}
|
134
|
+
|
135
|
+
.ultra-settings-description-text {
|
136
|
+
flex: 1;
|
137
|
+
}
|
138
|
+
|
139
|
+
.ultra-settings-field-sources {
|
140
|
+
padding: 0.5rem;
|
141
|
+
}
|
142
|
+
|
143
|
+
.ultra-settings-source {
|
144
|
+
display: flex;
|
145
|
+
align-items: center;
|
146
|
+
justify-content: space-between;
|
147
|
+
padding: 0.5rem 0.75rem;
|
148
|
+
margin-bottom: 0.25rem;
|
149
|
+
border-radius: 0.25rem;
|
150
|
+
background-color: var(--source-bg-color);
|
151
|
+
border: 1px solid var(--source-border-color);
|
152
|
+
transition: all 0.2s ease;
|
153
|
+
}
|
154
|
+
|
155
|
+
.ultra-settings-source-active {
|
156
|
+
background-color: var(--source-active-bg-color);
|
157
|
+
border-color: var(--source-active-border-color);
|
158
|
+
}
|
159
|
+
|
160
|
+
.ultra-settings-source-type {
|
161
|
+
font-size: 0.875rem;
|
162
|
+
font-weight: 500;
|
163
|
+
color: var(--source-type-color);
|
164
|
+
text-transform: uppercase;
|
165
|
+
letter-spacing: 0.025em;
|
166
|
+
min-width: 120px;
|
167
|
+
}
|
168
|
+
|
169
|
+
.ultra-settings-source-value {
|
170
|
+
font-family: monospace;
|
171
|
+
font-size: 0.875rem;
|
172
|
+
color: var(--source-value-color);
|
173
|
+
font-weight: 550;
|
174
|
+
flex: 1;
|
175
|
+
margin: 0 0.75rem;
|
176
|
+
word-break: break-all;
|
177
|
+
}
|
178
|
+
|
179
|
+
.ultra-settings-source-indicator {
|
180
|
+
font-size: 0.75rem;
|
181
|
+
font-weight: 600;
|
182
|
+
color: var(--source-indicator-color);
|
183
|
+
text-transform: uppercase;
|
184
|
+
letter-spacing: 0.025em;
|
185
|
+
}
|
186
|
+
|
187
|
+
.ultra-settings-source-value-dfn {
|
188
|
+
display: inline-block;
|
189
|
+
margin-left: 0.25rem;
|
190
|
+
}
|
191
|
+
|
192
|
+
.ultra-settings-icon-info {
|
193
|
+
color: var(--info-color);
|
194
|
+
cursor: pointer;
|
195
|
+
}
|
196
|
+
|
197
|
+
.ultra-settings-icon-not-set {
|
198
|
+
color: var(--warning-color);
|
199
|
+
cursor: pointer;
|
200
|
+
}
|
201
|
+
|
202
|
+
.ultra-settings-icon-secret {
|
203
|
+
color: var(--disabled-color);
|
204
|
+
cursor: pointer;
|
205
|
+
}
|
206
|
+
|
207
|
+
.ultra-settings-edit-link {
|
208
|
+
color: var(--edit-link-color);
|
209
|
+
text-decoration: none;
|
210
|
+
margin-left: 0.5rem;
|
211
|
+
opacity: 0.7;
|
212
|
+
transition: opacity 0.2s ease;
|
213
|
+
display: inline-flex;
|
214
|
+
align-items: center;
|
215
|
+
}
|
216
|
+
|
217
|
+
.ultra-settings-edit-link:hover {
|
218
|
+
opacity: 1;
|
44
219
|
}
|
45
220
|
|
46
221
|
.ultra-settings-select {
|
@@ -63,3 +238,39 @@
|
|
63
238
|
-moz-appearance: none;
|
64
239
|
appearance: none;
|
65
240
|
}
|
241
|
+
|
242
|
+
.ultra-settings-dialog {
|
243
|
+
min-width: 20rem;
|
244
|
+
padding: 0;
|
245
|
+
border: 1px solid var(--field-border-color);
|
246
|
+
border-radius: 0.375rem;
|
247
|
+
}
|
248
|
+
|
249
|
+
.ultra-settings-dialog-header {
|
250
|
+
padding: 0.5rem;
|
251
|
+
background-color: var(--field-header-bg-color);
|
252
|
+
color: var(--field-header-text-color);
|
253
|
+
font-size: 1rem;
|
254
|
+
display: flex;
|
255
|
+
align-items: top;
|
256
|
+
}
|
257
|
+
|
258
|
+
.ultra-settings-dialog-title {
|
259
|
+
flex: 1;
|
260
|
+
text-align: center;
|
261
|
+
font-weight: 550;
|
262
|
+
}
|
263
|
+
|
264
|
+
.ultra-settings-dialog-close {
|
265
|
+
border: none;
|
266
|
+
background: none;
|
267
|
+
color: var(--field-header-text-color);
|
268
|
+
cursor: pointer;
|
269
|
+
padding: 0.25rem;
|
270
|
+
}
|
271
|
+
|
272
|
+
.ultra-settings-dialog-body {
|
273
|
+
padding: 1rem;
|
274
|
+
background-color: var(--background-color);
|
275
|
+
color: var(--text-color);
|
276
|
+
}
|
@@ -1,13 +1,45 @@
|
|
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: #e8e8e8;
|
14
|
+
--field-header-text-color: #212529;
|
15
|
+
--value-bg-color: #fdfdfe;
|
16
|
+
--value-text-color: #212529;
|
17
|
+
--value-code-bg-color: #f8f8f8;
|
18
|
+
--value-code-border-color: #e8e8e8;
|
19
|
+
--type-color: #6c757d;
|
20
|
+
--description-color: #4a83b5;
|
21
|
+
--nil-color: #6c757d;
|
22
|
+
--warning-color: #b9202f;
|
23
|
+
--info-color: #6ea8fe;
|
24
|
+
--disabled-color: #adb5bd;
|
25
|
+
|
26
|
+
/* Source colors */
|
27
|
+
--source-bg-color: #f8f9fa;
|
28
|
+
--source-border-color: #e9ecef;
|
29
|
+
--source-active-bg-color: #e7f3ff;
|
30
|
+
--source-active-border-color: #0d6efd;
|
31
|
+
--source-type-color: #666666;
|
32
|
+
--source-value-color: #444444;
|
33
|
+
--source-indicator-color: #0d6efd;
|
34
|
+
|
35
|
+
/* Badge colors */
|
36
|
+
--secret-badge-bg-color: #dc3545;
|
37
|
+
--secret-badge-text-color: #ffffff;
|
38
|
+
--static-badge-bg-color: #888888;
|
39
|
+
--static-badge-text-color: #ffffff;
|
40
|
+
|
41
|
+
/* Edit link */
|
42
|
+
--edit-link-color: #0d6efd;
|
11
43
|
}
|
12
44
|
<% end %>
|
13
45
|
|
@@ -15,15 +47,48 @@
|
|
15
47
|
@media (prefers-color-scheme: dark) {
|
16
48
|
<% end %>
|
17
49
|
<% 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);
|
50
|
+
.ultra-settings, .ultra-settings-block {
|
22
51
|
--form-control-color: #eee;
|
23
52
|
--form-control-bg-color: #666;
|
24
53
|
--form-control-border-color: #555;
|
25
|
-
--code-color:
|
26
|
-
--em-color: #
|
54
|
+
--code-color: #fd76a3;
|
55
|
+
--em-color: #adb5bd;
|
56
|
+
|
57
|
+
/* Card layout colors */
|
58
|
+
--config-file-bg-color: #4b4b4b;
|
59
|
+
--config-file-border-color: #444;
|
60
|
+
--field-bg-color: #1e1e1e;
|
61
|
+
--field-border-color: #444;
|
62
|
+
--field-header-bg-color: #2b2b2b;
|
63
|
+
--field-header-text-color: #ced4da;
|
64
|
+
--value-bg-color: #252525;
|
65
|
+
--value-text-color: #e9ecef;
|
66
|
+
--value-code-bg-color: #2b2b2b;
|
67
|
+
--value-code-border-color: #444;
|
68
|
+
--type-color: #adb5bd;
|
69
|
+
--description-color: #a9d9f8;
|
70
|
+
--nil-color: #cdcecfff;
|
71
|
+
--warning-color: #dc3545;
|
72
|
+
--info-color: #6ea8fe;
|
73
|
+
--disabled-color: #adb5bd;
|
74
|
+
|
75
|
+
/* Source colors */
|
76
|
+
--source-bg-color: #2b2b2b;
|
77
|
+
--source-border-color: #444444;
|
78
|
+
--source-active-bg-color: #1a3a52;
|
79
|
+
--source-active-border-color: #0d6efd;
|
80
|
+
--source-type-color: #adb5bd;
|
81
|
+
--source-value-color: #ced4da;
|
82
|
+
--source-indicator-color: #6ea8fe;
|
83
|
+
|
84
|
+
/* Badge colors */
|
85
|
+
--secret-badge-bg-color: #dc3545;
|
86
|
+
--secret-badge-text-color: #ffffff;
|
87
|
+
--static-badge-bg-color: #6c757d;
|
88
|
+
--static-badge-text-color: #ffffff;
|
89
|
+
|
90
|
+
/* Edit link */
|
91
|
+
--edit-link-color: #a7c6f5;
|
27
92
|
}
|
28
93
|
<% end %>
|
29
94
|
<% if color_scheme == :system %>
|
data/app/configuration.html.erb
CHANGED
@@ -1,141 +1,125 @@
|
|
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 class="ultra-settings-field-data-value"><%= html_escape(secret_value(configuration[field.name])) %></code>
|
36
|
+
<% else %>
|
37
|
+
<code class="ultra-settings-field-data-value"><%= html_escape(display_value(configuration[field.name])) %></code>
|
38
|
+
<% end %>
|
39
|
+
</div>
|
61
40
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
41
|
+
<% unless field.description.to_s.empty? %>
|
42
|
+
<div class="ultra-settings-field-description">
|
43
|
+
<%= info_icon %>
|
44
|
+
<div class="ultra-settings-description-text">
|
45
|
+
<%= html_escape(field.description) %>
|
46
|
+
</div>
|
47
|
+
</div>
|
48
|
+
<% end %>
|
49
|
+
|
50
|
+
<div class="ultra-settings-field-sources">
|
51
|
+
<% if field.env_var && !configuration.class.environment_variables_disabled? %>
|
52
|
+
<div class="ultra-settings-source <%= 'ultra-settings-source-active' if source == :env %>">
|
53
|
+
<span class="ultra-settings-source-type">
|
54
|
+
Environment Variable
|
55
|
+
</span>
|
56
|
+
<code class="ultra-settings-source-value">
|
57
|
+
<%= field.env_var %>
|
58
|
+
<%= show_defined_value(field.env_var, configuration.__value_from_source__(field.name, :env), field.secret?) %>
|
59
|
+
</code>
|
60
|
+
<% if source == :env %>
|
61
|
+
<span class="ultra-settings-source-indicator">Currently active</span>
|
77
62
|
<% 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 %>
|
63
|
+
</div>
|
64
|
+
<% end %>
|
91
65
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
</li>
|
66
|
+
<% if field.runtime_setting && !configuration.class.runtime_settings_disabled? %>
|
67
|
+
<div class="ultra-settings-source <%= 'ultra-settings-source-active' if source == :settings %>">
|
68
|
+
<span class="ultra-settings-source-type">Runtime Setting</span>
|
69
|
+
<code class="ultra-settings-source-value">
|
70
|
+
<%= field.runtime_setting %>
|
71
|
+
<%= show_defined_value(field.runtime_setting, configuration.__value_from_source__(field.name, :settings), field.secret?) %>
|
72
|
+
</code>
|
73
|
+
<% if source == :settings %>
|
74
|
+
<span class="ultra-settings-source-indicator">Currently active</span>
|
102
75
|
<% end %>
|
103
|
-
<%
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
<% else %>
|
109
|
-
Can be
|
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>
|
76
|
+
<% edit_url = UltraSettings.runtime_settings_url(field.runtime_setting, field.type) %>
|
77
|
+
<% if edit_url %>
|
78
|
+
<a href="<%= html_escape(edit_url) %>" class="ultra-settings-edit-link" title="Edit <%= html_escape(field.runtime_setting) %>">
|
79
|
+
<%= edit_icon %>
|
80
|
+
</a>
|
117
81
|
<% end %>
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
</strong>
|
131
|
-
<% else %>
|
132
|
-
This field has a <%= show_defined_value("default value", field.default, field.secret?) %>.
|
133
|
-
<% end %>
|
134
|
-
</li>
|
82
|
+
</div>
|
83
|
+
<% end %>
|
84
|
+
|
85
|
+
<% if field.yaml_key && !configuration.class.yaml_config_disabled? %>
|
86
|
+
<div class="ultra-settings-source <%= 'ultra-settings-source-active' if source == :yaml %>">
|
87
|
+
<span class="ultra-settings-source-type">Configuration File</span>
|
88
|
+
<code class="ultra-settings-source-value">
|
89
|
+
<%= field.yaml_key %>
|
90
|
+
<%= show_defined_value(field.yaml_key, configuration.__value_from_source__(field.name, :yaml), field.secret?) %>
|
91
|
+
</code>
|
92
|
+
<% if source == :yaml %>
|
93
|
+
<span class="ultra-settings-source-indicator">Currently active</span>
|
135
94
|
<% end %>
|
136
|
-
</
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
95
|
+
</div>
|
96
|
+
<% end %>
|
97
|
+
|
98
|
+
<% if !field.default.nil? || source == :default %>
|
99
|
+
<div class="ultra-settings-source <%= 'ultra-settings-source-active' if source == :default %>">
|
100
|
+
<span class="ultra-settings-source-type">Default Value</span>
|
101
|
+
<code class="ultra-settings-source-value">
|
102
|
+
<%= show_defined_value("Default Value", field.default, field.secret?) %>
|
103
|
+
</code>
|
104
|
+
<% if source == :default %>
|
105
|
+
<span class="ultra-settings-source-indicator">Currently active</span>
|
106
|
+
<% end %>
|
107
|
+
</div>
|
108
|
+
<% end %>
|
109
|
+
</div>
|
110
|
+
</div>
|
111
|
+
<% end %>
|
112
|
+
</div>
|
113
|
+
|
114
|
+
<dialog class="ultra-settings-dialog" closedby="any">
|
115
|
+
<div class="ultra-settings-dialog-header">
|
116
|
+
<div class="ultra-settings-dialog-title"></div>
|
117
|
+
<button class="ultra-settings-dialog-close" onclick="this.closest('.ultra-settings-dialog').close();">
|
118
|
+
<%= close_icon %>
|
119
|
+
</button>
|
120
|
+
</div>
|
121
|
+
<div class="ultra-settings-dialog-body">
|
122
|
+
<code class="ultra-settings-field-data-value ultra-settings-dialog-value"></code>
|
123
|
+
</div>
|
124
|
+
</dialog>
|
125
|
+
</div>
|
data/app/index.html.erb
CHANGED
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
|
@@ -61,14 +42,29 @@ module UltraSettings
|
|
61
42
|
end
|
62
43
|
|
63
44
|
def show_defined_value(label, value, secret)
|
64
|
-
|
65
|
-
|
45
|
+
val = nil
|
46
|
+
icon = nil
|
47
|
+
css_class = nil
|
48
|
+
|
49
|
+
if value.nil?
|
50
|
+
val = "Not set"
|
51
|
+
icon = not_set_icon
|
52
|
+
css_class = "ultra-settings-icon-not-set"
|
66
53
|
elsif secret
|
67
|
-
|
54
|
+
val = secret_value(value)
|
55
|
+
icon = lock_icon
|
56
|
+
css_class = "ultra-settings-icon-secret"
|
68
57
|
else
|
69
|
-
|
58
|
+
val = display_value(value)
|
59
|
+
icon = eye_icon
|
60
|
+
css_class = "ultra-settings-icon-info"
|
70
61
|
end
|
71
|
-
|
62
|
+
|
63
|
+
<<~HTML
|
64
|
+
<dfn class="#{css_class}" title="#{html_escape(val)}" onclick="#{html_escape(open_dialog_script)}" data-label="#{html_escape(label)}">
|
65
|
+
#{icon}
|
66
|
+
</dfn>
|
67
|
+
HTML
|
72
68
|
end
|
73
69
|
|
74
70
|
def secret_value(value)
|
@@ -87,5 +83,69 @@ module UltraSettings
|
|
87
83
|
end
|
88
84
|
path.relative_path_from(root_path)
|
89
85
|
end
|
86
|
+
|
87
|
+
def info_icon(size = 16)
|
88
|
+
<<~HTML
|
89
|
+
<svg width="#{size}" height="#{size}" fill="currentColor" viewBox="0 0 16 16">
|
90
|
+
<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"/>
|
91
|
+
<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"/>
|
92
|
+
</svg>
|
93
|
+
HTML
|
94
|
+
end
|
95
|
+
|
96
|
+
def not_set_icon(size = 16)
|
97
|
+
<<~HTML
|
98
|
+
<svg width="#{size}" height="#{size}" fill="currentColor" viewBox="0 0 16 16">
|
99
|
+
<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"/>
|
100
|
+
<path d="M11.354 4.646a.5.5 0 0 0-.708 0l-6 6a.5.5 0 0 0 .708.708l6-6a.5.5 0 0 0 0-.708"/>
|
101
|
+
</svg>
|
102
|
+
HTML
|
103
|
+
end
|
104
|
+
|
105
|
+
def lock_icon(size = 16)
|
106
|
+
<<~HTML
|
107
|
+
<svg width="#{size}" height="#{size}" fill="currentColor" viewBox="0 0 16 16">
|
108
|
+
<path fill-rule="evenodd" d="M8 0a4 4 0 0 1 4 4v2.05a2.5 2.5 0 0 1 2 2.45v5a2.5 2.5 0 0 1-2.5 2.5h-7A2.5 2.5 0 0 1 2 13.5v-5a2.5 2.5 0 0 1 2-2.45V4a4 4 0 0 1 4-4m0 1a3 3 0 0 0-3 3v2h6V4a3 3 0 0 0-3-3"/>
|
109
|
+
</svg>
|
110
|
+
HTML
|
111
|
+
end
|
112
|
+
|
113
|
+
def edit_icon(size = 16)
|
114
|
+
<<~HTML
|
115
|
+
<svg width="#{size}" height="#{size}" fill="currentColor" viewBox="0 0 16 16">
|
116
|
+
<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"/>
|
117
|
+
<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"/>
|
118
|
+
</svg>
|
119
|
+
HTML
|
120
|
+
end
|
121
|
+
|
122
|
+
def eye_icon(size = 16)
|
123
|
+
<<~HTML
|
124
|
+
<svg width="#{size}" height="#{size}" fill="currentColor" viewBox="0 0 16 16">
|
125
|
+
<path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8M1.173 8a13 13 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5s3.879 1.168 5.168 2.457A13 13 0 0 1 14.828 8q-.086.13-.195.288c-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5s-3.879-1.168-5.168-2.457A13 13 0 0 1 1.172 8z"/>
|
126
|
+
<path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5M4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0"/>
|
127
|
+
</svg>
|
128
|
+
HTML
|
129
|
+
end
|
130
|
+
|
131
|
+
def close_icon(size = 16)
|
132
|
+
<<~HTML
|
133
|
+
<svg width="#{size}" height="#{size}" fill="currentColor" viewBox="0 0 16 16">
|
134
|
+
<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"/>
|
135
|
+
<path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708"/>
|
136
|
+
</svg>
|
137
|
+
HTML
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
def open_dialog_script
|
143
|
+
<<~JAVASCRIPT.gsub(/\s+/, " ").tr('"', "'")
|
144
|
+
this.closest('.ultra-settings-configuration').querySelector('.ultra-settings-dialog-title').textContent = this.dataset.label;
|
145
|
+
this.closest('.ultra-settings-configuration').querySelector('.ultra-settings-dialog-value').textContent = this.title;
|
146
|
+
this.closest('.ultra-settings-configuration').querySelector('.ultra-settings-dialog').showModal();
|
147
|
+
this.closest('.ultra-settings-configuration').querySelector('.ultra-settings-dialog-close').blur();
|
148
|
+
JAVASCRIPT
|
149
|
+
end
|
90
150
|
end
|
91
151
|
end
|
@@ -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,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,6 +13,7 @@ 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"
|
@@ -182,11 +183,12 @@ module UltraSettings
|
|
182
183
|
# @param name [String] The name of the setting.
|
183
184
|
# @return [String, nil]
|
184
185
|
# @api private
|
185
|
-
def runtime_settings_url(name)
|
186
|
+
def runtime_settings_url(name, type)
|
186
187
|
url = @runtime_settings_url.to_s
|
187
188
|
return nil if url.empty?
|
188
189
|
|
189
|
-
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))
|
190
192
|
end
|
191
193
|
|
192
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.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Durand
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-08-05 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
|
@@ -54,6 +55,7 @@ files:
|
|
54
55
|
- lib/ultra_settings/railtie.rb
|
55
56
|
- lib/ultra_settings/uninitialized_runtime_settings.rb
|
56
57
|
- lib/ultra_settings/version.rb
|
58
|
+
- lib/ultra_settings/view_helper.rb
|
57
59
|
- lib/ultra_settings/web_view.rb
|
58
60
|
- lib/ultra_settings/yaml_config.rb
|
59
61
|
- ultra_settings.gemspec
|