super_settings 1.0.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -2
  3. data/README.md +121 -16
  4. data/VERSION +1 -1
  5. data/app/helpers/super_settings/settings_helper.rb +13 -3
  6. data/app/views/layouts/super_settings/settings.html.erb +1 -1
  7. data/config/routes.rb +1 -1
  8. data/db/migrate/20210414004553_create_super_settings.rb +1 -7
  9. data/lib/super_settings/application/api.js +4 -1
  10. data/lib/super_settings/application/helper.rb +56 -17
  11. data/lib/super_settings/application/images/arrow-down-short.svg +3 -0
  12. data/lib/super_settings/application/images/arrow-up-short.svg +3 -0
  13. data/lib/super_settings/application/images/info-circle.svg +4 -0
  14. data/lib/super_settings/application/images/pencil-square.svg +4 -0
  15. data/lib/super_settings/application/images/plus.svg +3 -1
  16. data/lib/super_settings/application/images/trash3.svg +3 -0
  17. data/lib/super_settings/application/images/x-circle.svg +4 -0
  18. data/lib/super_settings/application/index.html.erb +54 -37
  19. data/lib/super_settings/application/layout.html.erb +5 -2
  20. data/lib/super_settings/application/layout_styles.css +7 -151
  21. data/lib/super_settings/application/layout_vars.css.erb +21 -0
  22. data/lib/super_settings/application/scripts.js +100 -21
  23. data/lib/super_settings/application/style_vars.css.erb +62 -0
  24. data/lib/super_settings/application/styles.css +183 -14
  25. data/lib/super_settings/application.rb +18 -11
  26. data/lib/super_settings/attributes.rb +1 -8
  27. data/lib/super_settings/configuration.rb +9 -0
  28. data/lib/super_settings/context/current.rb +33 -0
  29. data/lib/super_settings/context.rb +3 -0
  30. data/lib/super_settings/controller_actions.rb +2 -2
  31. data/lib/super_settings/engine.rb +1 -3
  32. data/lib/super_settings/history_item.rb +1 -1
  33. data/lib/super_settings/http_client.rb +165 -0
  34. data/lib/super_settings/local_cache.rb +0 -15
  35. data/lib/super_settings/rack_application.rb +3 -3
  36. data/lib/super_settings/rest_api.rb +5 -4
  37. data/lib/super_settings/setting.rb +14 -3
  38. data/lib/super_settings/storage/active_record_storage/models.rb +28 -0
  39. data/lib/super_settings/storage/active_record_storage.rb +10 -20
  40. data/lib/super_settings/storage/history_attributes.rb +31 -0
  41. data/lib/super_settings/storage/http_storage.rb +60 -184
  42. data/lib/super_settings/storage/json_storage.rb +201 -0
  43. data/lib/super_settings/storage/mongodb_storage.rb +238 -0
  44. data/lib/super_settings/storage/redis_storage.rb +50 -111
  45. data/lib/super_settings/storage/s3_storage.rb +165 -0
  46. data/lib/super_settings/storage/storage_attributes.rb +64 -0
  47. data/lib/super_settings/storage/test_storage.rb +3 -5
  48. data/lib/super_settings/storage/transaction.rb +67 -0
  49. data/lib/super_settings/storage.rb +17 -8
  50. data/lib/super_settings/time_precision.rb +36 -0
  51. data/lib/super_settings.rb +48 -13
  52. data/super_settings.gemspec +11 -2
  53. metadata +30 -12
  54. data/lib/super_settings/application/images/edit.svg +0 -1
  55. data/lib/super_settings/application/images/info.svg +0 -1
  56. data/lib/super_settings/application/images/slash.svg +0 -1
  57. data/lib/super_settings/application/images/trash.svg +0 -1
  58. /data/{MIT-LICENSE → MIT-LICENSE.txt} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f58b7aa08a4a6082168ff9fa1487f79ff84bf9374b9463d9ba3ebe4e188b6a47
4
- data.tar.gz: b4bf73f87885acacc091e3f455a8a33a8b0baa770dd00db42a1fac6b468e5427
3
+ metadata.gz: 83d20aa4f55a73597f12a1fed76db8d01bd81ec128b5208ed60ff8acc82a273b
4
+ data.tar.gz: c2bf00fe10c179d9ddc6849fbc6b2be2eb511b8132463a143e202ea42996581b
5
5
  SHA512:
6
- metadata.gz: 973a1ac38488a2cdc4d4cc639f9057d8a1cfd24e216cd7b8cfe4b034af5085b3e0245999b918f3aac7a95416d47fcba46f099d8312afaa2406f63bd5a17f1d25
7
- data.tar.gz: e1922327a1c8965dd72d8b11826f398b7dd0cd4f3a3f27be7cc3aa5a81f44da925a159ae2321a122267a9605dd7fbb5fa6af909d4daeeae3cf371087b4df7a7f
6
+ metadata.gz: 6fec2ccebbc1f84a7bb1878c4b6ea5f9fb5b36eacb0fddc3fcc2520ce3e738d895bf540be762a225aab970cc102287f5598b99588f4ad82494b726306b3f4601
7
+ data.tar.gz: 4d9d0641b7b47a845c26f6e72fccdd996c81b5e5f25c88d93b9148ca1c1767fe4c4d97681b558af0490028cbac3ac9e1ac447271b971829f2e8f3ea2a11311fd
data/CHANGELOG.md CHANGED
@@ -4,12 +4,44 @@ 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
- ## [1.0.1]
7
+ ## 2.0.0
8
+
9
+ ### Added
10
+
11
+ - Added controls for sorting settings in the web UI by keys or last modified time.
12
+ - Isolated of CSS classes in the web UI to prevent conflicts with other CSS libraries.
13
+ - Dark mode support in web UI.
14
+ - Added ability to embed the web UI in a view to allow tighter integration with your application's UI.
15
+ - Added storage adapter for storing settings in an S3 object.
16
+ - Added storage adapter for storing settings in MongoDB.
17
+ - HTTP storage adapter now uses keep-alive connections to improve performance.
18
+
19
+ ### Fixed
20
+
21
+ - Changing a key now works as expected. Previously, a new setting was created with the new key and the old setting was left unchanged. Now, the old setting is properly marked as deleted.
22
+ - Consistently handle converting floating point number to timestamps in Redis storage.
23
+
24
+ ### Removed
25
+
26
+ - Rails 4.2, 5.0, and 5.1 support has been removed.
27
+ - Removed support for Ruby 2.5.
28
+
29
+ ## 1.0.2
30
+
31
+ ### Added
32
+
33
+ - Added SuperSetting.rand method that can return a consistent random number inside of a context block.
34
+
35
+ ### Changed
36
+
37
+ - Lazy load non-required classes.
38
+
39
+ ## 1.0.1
8
40
 
9
41
  ### Added
10
42
  - Optimize object shapes for the Ruby interpreter by declaring instance variables in constructors.
11
43
 
12
- ## [1.0.0]
44
+ ## 1.0.0
13
45
 
14
46
  ### Added
15
47
  - Everything!
data/README.md CHANGED
@@ -1,28 +1,28 @@
1
1
  # SuperSettings
2
2
 
3
3
  [![Continuous Integration](https://github.com/bdurand/super_settings/actions/workflows/continuous_integration.yml/badge.svg)](https://github.com/bdurand/super_settings/actions/workflows/continuous_integration.yml)
4
- [![Regression Test](https://github.com/bdurand/super_settings/actions/workflows/regression_test.yml/badge.svg)](https://github.com/bdurand/super_settings/actions/workflows/regression_test.yml)
5
4
  [![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
5
+ [![Gem Version](https://badge.fury.io/rb/super_settings.svg)](https://badge.fury.io/rb/super_settings)
6
6
 
7
- This gem provides a framework for maintaining runtime application settings. Settings are persisted in a database but cached in memory for quick, efficient access. The settings are designed so they can be updated dynamically without requiring code deployment or restarting processes. The code scales very well and can easily handle very high throughput environments.
7
+ SuperSettings is a Ruby gem that provides a scalable framework for maintaining runtime application settings. Settings are persisted in a database but cached in memory for quick, efficient access. This design ensures settings can be updated dynamically without requiring code deployment or process restarts, making it ideal for high-throughput environments.
8
8
 
9
- As applications grow, they tend to accumulate many configuration options. Often these end up in environment variables, hard coded in YAML files, or sprinkled through various data models as additional columns. All of these methods of configuration have their place and are completely appropriate for different purposes (i.e. for storing application secrets, configuration required during application startup, etc.).
9
+ As applications grow, they often accumulate a large number of configuration options. Common approaches such as environment variables, YAML files, or additional database columns all have their place:
10
10
 
11
- However, these methods don't work as well for runtime settings that you may want to change while your application is running.
11
+ - **Environment variables** are suitable for environment-specific configuration, but require restarts for changes to take effect and can only store string values.
12
+ - **YAML files** offer support for complex data structures and can be bundled with application code but typically require a new release to update.
13
+ - **Database columns** - These are great for settings tied to data models; however, they don’t apply well outside of the data model, and you need to build custom tools to manage them in your application.
12
14
 
13
- - **Environment variables** - These are great for environment-specific configuration and they can be a good place to store sensitive data. However, they can be difficult to manage. All values must be stored as strings, and application processes need to be restarted for changes to take effect.
15
+ Key features include:
14
16
 
15
- - **YAML files** - These are great for more complex configurations because they support data structures and they can be shipped with your application code. However, changing them usually requires a new release of the application.
17
+ - **Caching:** A thread-safe, in-memory caching mechanism ensures high-performance access to settings, while minimizing database load. Cache refreshes are highly efficient and configurable.
18
+ - **Dynamic Updates:** Settings can be changed on the fly, with automatic logging of changes to maintain an audit trail for compliance.
19
+ - **Data Types and Validation:** Settings can be defined with specific data types (string, integer, float, boolean, datetime, or array) to ensure validity.
20
+ - **Documentation:** Each setting can include a description to explain its purpose and usage.
21
+ - **Web UI and REST API:** Manage settings via a built-in web interface or REST API.
22
+ - **Custom Callbacks:** Execute custom logic whenever a setting changes, enabling more sophisticated behavior for logging and compliance.
23
+ - **Pluggable Data Storage:** SuperSettings supports multiple storage engines, including ActiveRecord, Redis, and S3. You can also chain it to another application running SuperSettings via the HTTP storage engine to allow microservices to share settings.
16
24
 
17
- - **Database columns** - These are great for settings tied to data models, however, they don't apply very well outside the data model, and you need to build the tools for managing them into your application.
18
-
19
- SuperSettings provides a simple interface for accessing settings backed by a thread-safe caching mechanism, which provides in-memory performance while significantly limiting any load on the database. You can tune how frequently the cache is refreshed and each refresh call is tuned to be highly efficient.
20
-
21
- There is also an out of the box Web UI and REST API for managing settings. You can specify data types for your settings (string, integer, float, boolean, datetime, or array) to ensure that values will be valid. You can also supply documentation for each setting so that it's clear what each one does and how it is used.
22
-
23
- Changes to settings are stored whenever a setting is changed to give you an audit trail if you need it for compliance reasons. In addition, you can provide your own callbacks to execute whenever a setting is changed.
24
-
25
- There is a companion gem [ultra_settings](https://github.com/bdurand/ultra_settings) that can be used to integrate SuperSettings into a combined configuration system alongside YAML files and environment variables.
25
+ For projects that require a combination of runtime settings, environment variables, and YAML files, SuperSettings integrates seamlessly with [ultra_settings](https://github.com/bdurand/ultra_settings), creating a flexible and powerful configuration system.
26
26
 
27
27
  ## Usage
28
28
 
@@ -102,6 +102,16 @@ SuperSettings.context do
102
102
  end
103
103
  ```
104
104
 
105
+ You can also use the `SuperSettings.rand` method inside a context block to return a consistent random number. This can be useful for things like feature flags that you want to turn on for only a percentage of requests:
106
+
107
+ ```ruby
108
+ def enabled?
109
+ SuperSettings.float("feature_rollout_percent") <= SuperSettings.rand
110
+ end
111
+ ```
112
+
113
+ Now the value of `enabled?` will always return the same value inside of a context block. It will still be random if it is enabled for each context block.
114
+
105
115
  It's a good idea to add a `context` block around your main unit of work:
106
116
 
107
117
  - Rack application: add `SuperSettings::Context::RackMiddleware` to your middleware stack
@@ -137,10 +147,11 @@ This gem abstracts out the storage engine and can support multiple storage mecha
137
147
  * `SuperSettings::Storage::ActiveRecordStorage` - Stores the settings in a relational database using ActiveRecord. This is the default storage engine for Rails applications.
138
148
  * `SuperSettings::Storage::RedisStorage` - Stores the settings in a Redis database using the [redis](https://github.com/redis/redis-rb) gem.
139
149
  * `SuperSettings::Storage::HttpStorage` - Uses the SuperSettings REST API running on another server. This is useful in a microservices architecture so you can have a central settings server used by all the services.
150
+ * `SuperSettings::Storage::S3Storage` - Stores the settings in JSON in an S3 object. This is useful for applications running on AWS that want to store settings in a central location since it does not require a dedicated database. It is possible to read the settings directly from S3 from another application.
140
151
 
141
152
  Additional storage engines can be built by creating a class that includes `SuperSettings::Storage` and implements the unimplemented methods in that module.
142
153
 
143
- The storage engine is defined by setting `SuperSettings::Setting.storage` to a storage class. Note that each storage class may also require additional configuration. For instance, the Redis storage class requires you to provide a connection to a Redis database. If you are running a Rails application, then the storage engine will be set to ActiveRecord by default. Otherwise, you will need to define the storage class somewhere in your application's initialization.
154
+ The storage engine is defined by setting `SuperSettings::Setting.storage` to a storage class. Note that each storage class may also require additional configuration. For instance, the Redis storage class requires you to provide a connection to a Redis database. If you are running a Rails application, then the storage engine will be set to ActiveRecord by default. Otherwise, you will need to define the storage class somewhere in your application's initialization. See the storage class documentation for more information.
144
155
 
145
156
  ### Web UI
146
157
 
@@ -231,6 +242,9 @@ SuperSettings.configure do |config|
231
242
  # Set the superclass to use for the controller. Defaults to using `ApplicationController`.
232
243
  config.controller.superclass = Admin::BaseController
233
244
 
245
+ # Set the color scheme to use for the Web UI. Options are :light (default), :dark, or :system.
246
+ config.controller.color_scheme = :dark
247
+
234
248
  # Add additional code to the controller. In this case we are adding code to ensure only
235
249
  # admins can access the functionality and changing the layout to use one defined by the application.
236
250
  config.controller.enhance do
@@ -278,6 +292,54 @@ SuperSettings.configure do |config|
278
292
  end
279
293
  ```
280
294
 
295
+ #### Using your own controller
296
+
297
+ You can embed the SuperSettings web UI into your own templates. This gives you the option to more tightly integrate it with your application's navigation and look and feel.
298
+
299
+ First disable the web UI in the configuration since you won't need it.
300
+
301
+ ```ruby
302
+ # config/initializers/super_settings.rb
303
+
304
+ SuperSettings.configure do |config|
305
+ # Disable the built in web UI since you won't be using it.
306
+ config.controller.web_ui_enabled = false
307
+ end
308
+ ```
309
+
310
+ Create your controller that will render the SuperSettings web UI.
311
+
312
+ ```ruby
313
+ class AppSettingsController < ApplicationController
314
+ def index
315
+ end
316
+ end
317
+ ```
318
+
319
+ Mount the engine routes in your `config/routes.rb` file.
320
+
321
+ ```ruby
322
+ # config/routes.rb
323
+
324
+ Rails.application.routes.draw do
325
+ mount SuperSettings::Engine => "/settings"
326
+
327
+ controller :app_settings do
328
+ get "/app_settings", action: :index
329
+ end
330
+ end
331
+ ```
332
+
333
+ Create a view that embeds the SuperSettings web UI using the `SuperSettings::Application` class. You need to specify the path you mounted the engine at as the base URL for the REST API.
334
+
335
+ ```erb
336
+ # app/views/app_settings/index.html.erb
337
+
338
+ <h1>Application Settings</h1>
339
+
340
+ <%= SuperSettings::Application.new(api_base_url: "/settings").render %>
341
+ ```
342
+
281
343
  ## Installation
282
344
 
283
345
  Add this line to your application's Gemfile:
@@ -302,6 +364,49 @@ Open a pull request on GitHub.
302
364
 
303
365
  Please use the [standardrb](https://github.com/testdouble/standard) syntax and lint your code with `standardrb --fix` before submitting.
304
366
 
367
+ You can run a local Rails development using ActiveRecord storage with
368
+
369
+ ```bash
370
+ # Initialize the database (one time only)
371
+ bin/rails bin/rails super_settings:install:migrations
372
+ bin/rails db:migrate
373
+
374
+ # Start the server
375
+ bin/rails s
376
+ ```
377
+
378
+ You can also bring up a local rack server with
379
+
380
+ ```bash
381
+ bundle exec rackup
382
+ ```
383
+
384
+ By default this will use Redis for storage using the default Redis URL. You can change the storage engine with the `STORAGE` environment variable.
385
+
386
+ - `redis` - Use the Redis storage engine. The Redis URL can be set with the `REDIS_URL` environment variable.
387
+ - `http` - Use the HTTP storage engine. The URL for the REST API can be set with the `REST_API_URL` environment variable.
388
+ - `s3` - Use the S3 storage engine. The S3 URL can be set with the `S3_URL` environment variable.
389
+ - `mongodb` - Use the MongoDB storage engine. The MongoDB URL can be set with the `MONGODB_URL` environment variable.
390
+
391
+ You can bring up all of these storage engines locally with the included docker-compose configuration.
392
+
393
+ ```bash
394
+ docker-compose up
395
+ ```
396
+
397
+ This will work out of the box with the defaults for the storage engines when running the rack server.:
398
+
399
+ - `REDIS_URL` - `redis://localhost:6379/0`
400
+ - `REST_API_URL` - `http://localhost:3000/settings` (this is the default URL for the Rails application)
401
+ - `S3_URL` - `s3://accesskey:secretkey@region-1/settings/settings.json` (the S3 endpoint will be set to `http://localhost:9000`)
402
+ - `MONGODB_URL` - `mongodb://localhost:27017/super_settings`
403
+
404
+ Finally, you can run the application in dark mode by setting the `COLOR_SCHEME` environment variable.
405
+
406
+ ```bash
407
+ COLOR_SCHEME=dark bundle exec rackup
408
+ ```
409
+
305
410
  ## License
306
411
 
307
412
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.1
1
+ 2.0.0
@@ -5,19 +5,20 @@ module SuperSettings
5
5
  # Render the styles.css as an inline <style> tag.
6
6
  def super_settings_layout_style_tag
7
7
  application_dir = File.expand_path(File.join("..", "..", "..", "lib", "super_settings", "application"), __dir__)
8
+ css = render(file: File.join(application_dir, "layout_styles.css"))
8
9
  content_tag(:style, type: "text/css") do
9
- render(file: File.join(application_dir, "layout_styles.css")).html_safe
10
+ (layout_css_vars + css).html_safe
10
11
  end
11
12
  end
12
13
 
13
14
  # Return the application name set by the configuration or a default value.
14
15
  def super_settings_application_name
15
- Configuration.instance.controller.application_name || "Application"
16
+ SuperSettings.configuration.controller.application_name || "Application"
16
17
  end
17
18
 
18
19
  # Render the header for the web pages using values set in the configuration.
19
20
  def super_settings_application_header
20
- config = Configuration.instance.controller
21
+ config = SuperSettings.configuration.controller
21
22
  content = "#{super_settings_application_name} Settings"
22
23
  if Coerce.present?(config.application_logo)
23
24
  content = image_tag(config.application_logo, alt: "").concat(content)
@@ -28,5 +29,14 @@ module SuperSettings
28
29
  content
29
30
  end
30
31
  end
32
+
33
+ private
34
+
35
+ def layout_css_vars
36
+ application_dir = File.expand_path(File.join("..", "..", "..", "lib", "super_settings", "application"), __dir__)
37
+ erb = ERB.new(File.read(File.join(application_dir, "layout_vars.css.erb")))
38
+ color_scheme = SuperSettings.configuration.controller.color_scheme
39
+ erb.result(binding).html_safe
40
+ end
31
41
  end
32
42
  end
@@ -13,7 +13,7 @@
13
13
  <%= super_settings_application_header %>
14
14
  </h1>
15
15
  </header>
16
- <div class="container">
16
+ <div class="super-settings-container">
17
17
  <%= yield %>
18
18
  </div>
19
19
  </body>
data/config/routes.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  SuperSettings::Engine.routes.draw do
4
4
  controller :settings do
5
- if SuperSettings::Configuration.instance.controller.web_ui_enabled?
5
+ if SuperSettings.configuration.controller.web_ui_enabled?
6
6
  get "/", action: :root, as: :root
7
7
  end
8
8
  get "/settings", action: :index
@@ -1,12 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Needed for the super_settings gem to maintain backward compatibility with Rails 4.2
4
- migration_class = ActiveRecord::Migration
5
- if migration_class.respond_to?(:[])
6
- migration_class = migration_class[4.2]
7
- end
8
-
9
- class CreateSuperSettings < migration_class
3
+ class CreateSuperSettings < ActiveRecord::Migration[5.0]
10
4
  def up
11
5
  create_table :super_settings do |t|
12
6
  t.string :key, null: false, limit: 190, index: {unique: true}
@@ -9,7 +9,10 @@
9
9
  (function() {
10
10
  // Get the URL for making an API call to the specified action and id.
11
11
  function apiURL(action, params) {
12
- let url = window.location.pathname;
12
+ let url = document.querySelector(".super-settings[data-api-base-url]")?.dataset?.apiBaseUrl;
13
+ if (!url) {
14
+ url = window.location.pathname;
15
+ }
13
16
  if (url.endsWith("/")) {
14
17
  url = url.substring(0, url.length - 1);
15
18
  }
@@ -5,13 +5,14 @@ module SuperSettings
5
5
  # are mixed in to the Application class so they are accessible from the ERB templates.
6
6
  module Helper
7
7
  ICON_SVG = Dir.glob(File.join(__dir__, "images", "*.svg")).each_with_object({}) do |file, cache|
8
- cache[File.basename(file, ".svg")] = File.read(file).chomp
8
+ svg = File.read(file).chomp
9
+ cache[File.basename(file, ".svg")] = svg
9
10
  end.freeze
10
11
 
11
12
  ICON_BUTTON_STYLE = {
12
13
  cursor: "pointer",
13
- width: "1.5rem",
14
- height: "1.5rem",
14
+ width: "1.35rem",
15
+ height: "1.35rem",
15
16
  "min-width": "20px",
16
17
  "min-height": "20px",
17
18
  "margin-top": "0.25rem",
@@ -21,8 +22,7 @@ module SuperSettings
21
22
  DEFAULT_ICON_STYLE = {
22
23
  width: "1rem",
23
24
  height: "1rem",
24
- display: "inline-block",
25
- "vertical-align": "middle"
25
+ display: "inline-block"
26
26
  }.freeze
27
27
 
28
28
  # Render the scripts.js file as an inline <script> tag.
@@ -41,6 +41,7 @@ module SuperSettings
41
41
  def style_tag
42
42
  <<~HTML
43
43
  <style type="text/css">
44
+ #{render_partial("style_vars.css.erb")}
44
45
  #{File.read(File.join(__dir__, "styles.css"))}
45
46
  </style>
46
47
  HTML
@@ -50,39 +51,61 @@ module SuperSettings
50
51
  def layout_style_tag
51
52
  <<~HTML
52
53
  <style type="text/css">
54
+ #{render_partial("layout_vars.css.erb")}
53
55
  #{File.read(File.join(__dir__, "layout_styles.css"))}
54
56
  </style>
55
57
  HTML
56
58
  end
57
59
 
60
+ # Render an ERB template.
61
+ #
62
+ # @param erb_file [String] the path to the ERB file to render
63
+ # @return [String] the rendered HTML
64
+ def render_partial(erb_file)
65
+ template = ERB.new(File.read(File.expand_path(erb_file, __dir__)))
66
+ template.result(binding)
67
+ end
68
+
69
+ # Escape text for use in HTML.
70
+ #
71
+ # @param text [String] the text to escape
72
+ # @return [String] the escaped text
73
+ def html_escape(text)
74
+ ERB::Util.html_escape(text)
75
+ end
76
+
58
77
  # Render an image tag for one of the SVG images in the images directory. If the :color option
59
78
  # is specified, it will be applied to the SVG image.
60
79
  def icon_image(name, options = {})
61
80
  svg = ICON_SVG[name.to_s]
62
- if Coerce.present?(options[:color])
63
- svg = svg.gsub("currentColor", options[:color])
81
+ style = (options[:style] || {})
82
+ css = DEFAULT_ICON_STYLE.merge(style).map { |name, value| "#{name}: #{value}" }.join("; ")
83
+ options = options.merge(style: css, class: "super-settings-icon")
84
+ if options[:data].is_a?(Hash)
85
+ options[:data].each do |key, value|
86
+ options["data-#{key}"] = value
87
+ end
88
+ options.delete(:data)
64
89
  end
65
- css = DEFAULT_ICON_STYLE.merge(options[:style] || {}).map { |name, value| "#{name}:#{value}" }.join("; ")
66
- options = {alt: ""}.merge(options).merge(src: "data:image/svg+xml;utf8,#{svg}", style: css)
67
- tag(:img, options)
90
+ content_tag(:span, svg, options)
68
91
  end
69
92
 
70
93
  # Render an icon image as a link tag.
71
94
  def icon_button(icon, title:, color:, js_class:, url: nil, disabled: false, style: {}, link_style: nil)
72
95
  url = "#" if Coerce.blank?(url)
73
- image = icon_image(icon, alt: title, color: color, style: ICON_BUTTON_STYLE.merge(style))
74
- content_tag(:a, image, href: url, class: js_class, disabled: disabled, style: link_style)
96
+ image = icon_image(icon, alt: title, style: ICON_BUTTON_STYLE.merge(style).merge(color: color))
97
+ content_tag(:a, image, href: url, class: js_class, disabled: disabled, style: link_style, title: title)
75
98
  end
76
99
 
77
100
  # Return the application name set by the configuration or a default value.
78
101
  def application_name
79
- ERB::Util.html_escape(Configuration.instance.controller.application_name || "Application")
102
+ html_escape(SuperSettings.configuration.controller.application_name || "Application")
80
103
  end
81
104
 
82
105
  # Render the header for the web pages using values set in the configuration.
83
106
  def application_header
84
- config = Configuration.instance.controller
85
- content = ERB::Util.html_escape("#{application_name} Settings")
107
+ config = SuperSettings.configuration.controller
108
+ content = html_escape("#{application_name} Settings")
86
109
  if Coerce.present?(config.application_logo)
87
110
  content = tag(:img, src: config.application_logo, alt: "") + content
88
111
  end
@@ -107,14 +130,30 @@ module SuperSettings
107
130
  def html_attributes(options)
108
131
  html_options = []
109
132
  options.each do |name, value|
110
- html_options << "#{name}=\"#{ERB::Util.html_escape(value.to_s)}\""
133
+ html_options << "#{name}=\"#{html_escape(value.to_s)}\""
111
134
  end
112
135
  html_options.join(" ")
113
136
  end
114
137
 
115
- # Add additional HTML code to the <head> element on the page.
138
+ # Additional HTML code that should go into the <head> element on the page.
139
+ #
140
+ # @return [String]
116
141
  def add_to_head
117
142
  @add_to_head if defined?(@add_to_head)
118
143
  end
144
+
145
+ # The base URL for the REST API.
146
+ #
147
+ # @return [String]
148
+ def api_base_url
149
+ @api_base_url if defined?(@api_base_url)
150
+ end
151
+
152
+ # Whether to use dark mode for the application UI.
153
+ #
154
+ # @return [Boolean, nil]
155
+ def color_scheme
156
+ @color_scheme if defined?(@color_scheme)
157
+ end
119
158
  end
120
159
  end
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-down-short" viewBox="0 0 16 16">
2
+ <path fill-rule="evenodd" d="M8 4a.5.5 0 0 1 .5.5v5.793l2.146-2.147a.5.5 0 0 1 .708.708l-3 3a.5.5 0 0 1-.708 0l-3-3a.5.5 0 1 1 .708-.708L7.5 10.293V4.5A.5.5 0 0 1 8 4"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-up-short" viewBox="0 0 16 16">
2
+ <path fill-rule="evenodd" d="M8 12a.5.5 0 0 0 .5-.5V5.707l2.146 2.147a.5.5 0 0 0 .708-.708l-3-3a.5.5 0 0 0-.708 0l-3 3a.5.5 0 1 0 .708.708L7.5 5.707V11.5a.5.5 0 0 0 .5.5"/>
3
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-info-circle" viewBox="0 0 16 16">
2
+ <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"/>
3
+ <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"/>
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pencil-square" viewBox="0 0 16 16">
2
+ <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"/>
3
+ <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"/>
4
+ </svg>
@@ -1 +1,3 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plus" viewBox="0 0 16 16">
2
+ <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash3" viewBox="0 0 16 16">
2
+ <path d="M6.5 1h3a.5.5 0 0 1 .5.5v1H6v-1a.5.5 0 0 1 .5-.5M11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3A1.5 1.5 0 0 0 5 1.5v1H1.5a.5.5 0 0 0 0 1h.538l.853 10.66A2 2 0 0 0 4.885 16h6.23a2 2 0 0 0 1.994-1.84l.853-10.66h.538a.5.5 0 0 0 0-1zm1.958 1-.846 10.58a1 1 0 0 1-.997.92h-6.23a1 1 0 0 1-.997-.92L3.042 3.5zm-7.487 1a.5.5 0 0 1 .528.47l.5 8.5a.5.5 0 0 1-.998.06L5 5.03a.5.5 0 0 1 .47-.53Zm5.058 0a.5.5 0 0 1 .47.53l-.5 8.5a.5.5 0 1 1-.998-.06l.5-8.5a.5.5 0 0 1 .528-.47M8 4.5a.5.5 0 0 1 .5.5v8.5a.5.5 0 0 1-1 0V5a.5.5 0 0 1 .5-.5"/>
3
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-x-circle" viewBox="0 0 16 16">
2
+ <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"/>
3
+ <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"/>
4
+ </svg>