From 9556f7a5e6d3c09caaf38d5058c2c5ab43eff02b Mon Sep 17 00:00:00 2001
From: Brian Durand <bbdurand@gmail.com>
Date: Tue, 17 Sep 2024 18:24:26 -0700
Subject: [PATCH 1/2] Add ability to embed the complete UI in a template

---
 CHANGELOG.md                                |  8 ++-
 README.md                                   | 16 ++++--
 VERSION                                     |  2 +-
 app/index.html.erb                          | 10 ++--
 lib/ultra_settings.rb                       |  1 +
 lib/ultra_settings/application_view.rb      | 59 +++++++++++++++++++++
 lib/ultra_settings/web_view.rb              |  4 +-
 spec/ultra_settings/application_vew_spec.rb | 28 ++++++++++
 8 files changed, 114 insertions(+), 14 deletions(-)
 create mode 100644 lib/ultra_settings/application_view.rb
 create mode 100644 spec/ultra_settings/application_vew_spec.rb

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 462e6d0..445ba44 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## 1.1.2
+
+### Added
+
+- Added `UltraSettings::ApplicationView` which can be used to embed the web UI application showing the configuration inside your own templates. So now you can more seamlessly integrate the settings UI into your own admin tools.
+
 ## 1.1.1
 
 ### Added
@@ -20,7 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 - Revamped web UI that can now display setting values.
 - Added option to specify fields as a secret in the configuration to prevent exposing sensitive information in the web interface. By default all fields are considered secrets. This can be changed per configuration by setting the `fields_secret_by_default` property to `false`.
-- Added `UltraSettings::ConfigurationView` which can be used to embed the HTML table showing the configuration options and values other admin views. So now you can integrate the settings view into your own admin tools.
+- Added `UltraSettings::ConfigurationView` which can be used to embed the HTML table showing the configuration options and values inside other admin views. So now you can more seamlessly integrate the settings view into your own admin tools.
 - Add `__to_hash__` method to `UltraSettings::Configuration` which can to serialize the current configuration values as a hash. This value can be used for comparing configuration between environments.
 
 ## 1.0.1
diff --git a/README.md b/README.md
index ca3e38d..a4a532f 100644
--- a/README.md
+++ b/README.md
@@ -348,15 +348,23 @@ end, at: "/ultra_settings"
 
 #### Embedding the Settings View in Admin Tools
 
-If you prefer to embed the settings view directly into your own admin tools or dashboard, you can use the `UltraSettings::ConfigurationView` class to render the settings interface within your existing views:
+If you prefer to embed the settings view directly into your own admin tools or dashboard, you can use the `UltraSettings::ApplicationView` class to render the settings interface within your existing views:
 
 ```erb
-<h1>My Service Settings</h1>
+<h1>Configuration</h1>
 
-<%= UltraSettings::ConfigurationView.new(MyServiceConfiguration.instance).render %>
+<%= UltraSettings::ApplicationView.new.render(select_class: "form-control", table_class: "table table-striped") %>
 ```
 
-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 in an HTML table which you can format with your own CSS.
+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 in an HTML table with navigation handled by an HTML select element. You can specify the CSS classes for these elements and use your own stylesheets to customize the appearance.
+
+You can also embed the view for individual configurations within your own views using the `UltraSettings::ConfigurationView` class if you want more customization:
+
+```erb
+<h1>My Service Settings</h1>
+
+<%= UltraSettings::ConfigurationView.new(MyServiceConfiguration.instance).render(table_class: "table table-striped") %>
+```
 
 ### Testing With UltraSettings
 
diff --git a/VERSION b/VERSION
index 524cb55..45a1b3f 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.1.1
+1.1.2
diff --git a/app/index.html.erb b/app/index.html.erb
index 0eb8346..7e667fe 100644
--- a/app/index.html.erb
+++ b/app/index.html.erb
@@ -1,8 +1,8 @@
 <div class="ultra-settings-nav">
   <form onsubmit="return false">
-    <select class="ultra-settings-select" size="1" id="config-selector">
+    <select class="<%= html_escape(select_class) %>" size="1" id="config-selector">
       <% UltraSettings.__configuration_names__.sort.each do |name| %>
-        <option value="config-<%= name %>"><%= name %></option>
+        <option value="config-<%= html_escape(name) %>"><%= html_escape(name) %></option>
       <% end %>
     </select>
   </form>
@@ -11,11 +11,11 @@
 <% UltraSettings.__configuration_names__.sort.each do |name| %>
   <% configuration = UltraSettings.send(name) %>
 
-  <div class="ultra-settings-configuration" id="config-<%= name %>" style="display:none;">
-    <%= UltraSettings::ConfigurationView.new(configuration) %>
+  <div class="ultra-settings-configuration" id="config-<%= html_escape(name) %>" style="display:none;">
+    <%= UltraSettings::ConfigurationView.new(configuration).render(table_class: table_class) %>
   </div>
 <% end %>
 
 <script>
-  <%= @javascript %>
+  <%= javascript %>
 </script>
diff --git a/lib/ultra_settings.rb b/lib/ultra_settings.rb
index cea4f40..f17a237 100644
--- a/lib/ultra_settings.rb
+++ b/lib/ultra_settings.rb
@@ -12,6 +12,7 @@
 require_relative "ultra_settings/field"
 require_relative "ultra_settings/rack_app"
 require_relative "ultra_settings/web_view"
+require_relative "ultra_settings/application_view"
 require_relative "ultra_settings/configuration_view"
 require_relative "ultra_settings/yaml_config"
 require_relative "ultra_settings/version"
diff --git a/lib/ultra_settings/application_view.rb b/lib/ultra_settings/application_view.rb
new file mode 100644
index 0000000..4711213
--- /dev/null
+++ b/lib/ultra_settings/application_view.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+module UltraSettings
+  # This class can render information about all configurations. It is used by the bundled
+  # web UI, but you can use it to embed the configuration information in your own web pages.
+  #
+  # The output will be a simple HTML drop down list that can be used to display an HTML table
+  # showing each configuration. You can specify the CSS class for the select element and the tables
+  # by passing the `select_class` and `table_class` option to the `render` method. By default the
+  # select elewment have the class `ultra-settings-select` and the table will have the class
+  # `ultra-settings-table`.
+  #
+  # @example
+  #  <h1>Application Configuration</h1>
+  #  <%= UltraSettings::ApplicationView.new.render(select_class: 'form-control', table_class: "table table-striped") %>
+  class ApplicationView
+    @template = nil
+
+    class << self
+      def template
+        @template ||= ERB.new(read_app_file("index.html.erb"))
+      end
+
+      def javascript
+        @javascript = read_app_file("application.js")
+      end
+
+      private
+
+      def read_app_file(path)
+        File.read(File.join(app_dir, path))
+      end
+
+      def app_dir
+        File.expand_path(File.join("..", "..", "app"), __dir__)
+      end
+    end
+
+    def render(select_class: "ultra-settings-select", table_class: "ultra-settings-table")
+      html = self.class.template.result(binding)
+      html = html.html_safe if html.respond_to?(:html_safe)
+      html
+    end
+
+    def to_s
+      render
+    end
+
+    private
+
+    def html_escape(value)
+      ERB::Util.html_escape(value)
+    end
+
+    def javascript
+      self.class.javascript
+    end
+  end
+end
diff --git a/lib/ultra_settings/web_view.rb b/lib/ultra_settings/web_view.rb
index f17159a..21cbee6 100644
--- a/lib/ultra_settings/web_view.rb
+++ b/lib/ultra_settings/web_view.rb
@@ -6,11 +6,9 @@ class WebView
     attr_reader :css
 
     def initialize
-      @index_template = erb_template("index.html.erb")
       @layout_template = erb_template("layout.html.erb")
       @layout_css = read_app_file("layout.css")
       @css = read_app_file("application.css")
-      @javascript = read_app_file("application.js")
     end
 
     def render_settings
@@ -18,7 +16,7 @@ def render_settings
     end
 
     def content
-      @index_template.result(binding)
+      UltraSettings::ApplicationView.new.render
     end
 
     private
diff --git a/spec/ultra_settings/application_vew_spec.rb b/spec/ultra_settings/application_vew_spec.rb
new file mode 100644
index 0000000..767809f
--- /dev/null
+++ b/spec/ultra_settings/application_vew_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require_relative "../spec_helper"
+
+describe UltraSettings::ApplicationView do
+  it "renders the confguration as an HTML application" do
+    html = UltraSettings::ApplicationView.new.render
+    expect(html.strip).to match(/<select class="ultra-settings-select" size="1" id="config-selector">.*<\/select>/m)
+    expect(html.strip).to match(/<table class="ultra-settings-table">.*<\/table>/m)
+    expect(html.strip).to match(/<script>.*<\/script>/m)
+  end
+
+  it "can set the select class" do
+    html = UltraSettings::ApplicationView.new.render(select_class: "form-control")
+    expect(html.strip).to match(/<select class="form-control" size="1" id="config-selector">.*<\/select>/m)
+  end
+
+  it "can set the table class" do
+    html = UltraSettings::ApplicationView.new.render(table_class: "table table-striped")
+    expect(html.strip).to match(/<table class="table table-striped">.*<\/table>/m)
+  end
+
+  it "renders valid HTML", env: {TEST_STRING: "<script"} do
+    html = UltraSettings::ApplicationView.new.render
+    doc = Nokogiri::HTML(html)
+    expect(doc.errors).to be_empty
+  end
+end

From 27c5c1d7f6604ddbff3acadd057c9014f99f064a Mon Sep 17 00:00:00 2001
From: Brian Durand <bbdurand@gmail.com>
Date: Tue, 17 Sep 2024 18:34:05 -0700
Subject: [PATCH 2/2] change example class

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index a4a532f..632c439 100644
--- a/README.md
+++ b/README.md
@@ -353,7 +353,7 @@ If you prefer to embed the settings view directly into your own admin tools or d
 ```erb
 <h1>Configuration</h1>
 
-<%= UltraSettings::ApplicationView.new.render(select_class: "form-control", table_class: "table table-striped") %>
+<%= UltraSettings::ApplicationView.new.render(select_class: "form-select", table_class: "table table-striped") %>
 ```
 
 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 in an HTML table with navigation handled by an HTML select element. You can specify the CSS classes for these elements and use your own stylesheets to customize the appearance.