diff --git a/app/controllers/config_controller.rb b/app/controllers/config_controller.rb
new file mode 100644
index 00000000..d507e1c9
--- /dev/null
+++ b/app/controllers/config_controller.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class ConfigController < ApplicationController
+ def index
+ end
+
+ def show
+ @definition = Sites.from_enum(params[:id])
+ end
+
+ def modify
+ custom_config = params.fetch(:config, {}).permit!
+ custom_config[:scraper_request_rate_limit] = custom_config[:scraper_request_rate_limit].tr(",", ".").to_f if custom_config[:scraper_request_rate_limit]
+
+ Config.write_custom_config(custom_config)
+ Config.force_reload
+ redirect_back fallback_location: config_index_path
+ end
+end
diff --git a/app/logical/config.rb b/app/logical/config.rb
index e4873f27..f3646327 100644
--- a/app/logical/config.rb
+++ b/app/logical/config.rb
@@ -15,6 +15,15 @@ def custom_config_path
Rails.root.join("config/reverser_custom_config.yml")
end
+ def merge_custom_config(new_values)
+ custom_config.merge(new_values).transform_keys(&:to_s)
+ end
+
+ def write_custom_config(new_values)
+ data = Psych.safe_dump(merge_custom_config(new_values))
+ File.write(custom_config_path, data)
+ end
+
def force_reload
@default_config = nil
@custom_config = nil
@@ -27,7 +36,10 @@ def missing_values
def method_missing(method)
raise NoMethodError, "Unknown config #{method}" unless respond_to_missing?(method)
- if custom_config.key?(method)
+ bool_key = method.to_s.delete_suffix("?").to_sym
+ if custom_config.key?(bool_key) && method.end_with?("?")
+ custom_config[bool_key] == "true"
+ elsif custom_config.key?(method)
custom_config[method]
else
default_config[method]
diff --git a/app/logical/sites/scraper_definition.rb b/app/logical/sites/scraper_definition.rb
index 132a5753..52e91496 100644
--- a/app/logical/sites/scraper_definition.rb
+++ b/app/logical/sites/scraper_definition.rb
@@ -23,6 +23,8 @@ def missing_config_keys
@scraper.required_config_keys.select { |key| Config.send(key).blank? }
end
+ delegate :all_config_keys, to: :@scraper
+
def cached_values
@scraper.cached_methods.filter_map do |method|
key = @scraper.cache_key(method)
diff --git a/app/views/application/_navbar.html.erb b/app/views/application/_navbar.html.erb
index 2a3714eb..2ae30383 100644
--- a/app/views/application/_navbar.html.erb
+++ b/app/views/application/_navbar.html.erb
@@ -8,6 +8,7 @@
<%= nav_link_to "Submission Files", submission_files_path %>
<%= nav_link_to "Archive Importer", new_archive_import_path %>
<%= nav_link_to "Stats", stats_path %>
+ <%= nav_link_to "Config", config_index_path %>
<%= nav_link_to "Selenium", "http://localhost:#{DockerEnv.exposed_vnc_port}" %>
<%= nav_link_to "GoodJob", "/good_job" %>
diff --git a/app/views/config/_secondary_links.html.erb b/app/views/config/_secondary_links.html.erb
new file mode 100644
index 00000000..383c2241
--- /dev/null
+++ b/app/views/config/_secondary_links.html.erb
@@ -0,0 +1 @@
+<%= subnav_link_to "Listing", config_index_path %>
diff --git a/app/views/config/index.html.erb b/app/views/config/index.html.erb
new file mode 100644
index 00000000..5dc8df17
--- /dev/null
+++ b/app/views/config/index.html.erb
@@ -0,0 +1,13 @@
+
General Settings
+
+<%= simple_form_for(:config, url: modify_config_index_path, method: :put) do |f| %>
+ <%= f.input :app_name, input_html: { value: Config.app_name } %>
+ <%= f.input :log_scraper_requests, as: :select, selected: Config.log_scraper_requests? %>
+ <%= f.input :scraper_request_rate_limit, input_html: { value: Config.scraper_request_rate_limit } %>
+ <%= f.input :time_zone, collection: ActiveSupport::TimeZone.all.map { |tz| [tz.to_s, tz.name] }, selected: Config.time_zone %>
+ <%= f.submit %>
+<% end %>
+
+<% Sites.scraper_definitions.each do |definition| %>
+ <%= link_to "#{definition.display_name} settings", config_path(definition.enum_value) %>
+<% end %>
diff --git a/app/views/config/show.html.erb b/app/views/config/show.html.erb
new file mode 100644
index 00000000..fade6492
--- /dev/null
+++ b/app/views/config/show.html.erb
@@ -0,0 +1,9 @@
+Settings for <%= @definition.display_name %>
+<%= link_to @definition.homepage, @definition.homepage %>
+<%= simple_form_for(:config, url: modify_config_index_path, method: :put) do |f| %>
+ <% @definition.all_config_keys.each do |key| %>
+ <%= f.input key, label: key.to_s.delete_prefix("#{@definition.enum_value}_"), input_html: { value: Config.send(key) } %>
+ <% end %>
+ <%= f.input "#{@definition.enum_value}_disabled", as: :select, label: "disabled", selected: Config.send("#{@definition.enum_value}_disabled?").to_s %>
+ <%= f.submit %>
+<% end %>
diff --git a/app/views/stats/index.html.erb b/app/views/stats/index.html.erb
index c438250a..b2489c8b 100644
--- a/app/views/stats/index.html.erb
+++ b/app/views/stats/index.html.erb
@@ -15,7 +15,7 @@
<% @definitions.each do |definition| %>
<% entry = @counts[definition.enum_value] || {} %>
- <%= definition.display_name %> |
+ <%= link_to_if(definition.enum_value != "manual", definition.display_name, config_path(definition.enum_value)) %> |
<%= link_to(entry["artist_count"] || 0, artists_path(search: { site_type: definition.enum_value })) %> |
<%= link_to(entry["url_count"] || 0, artist_urls_path(search: { site_type: definition.enum_value })) %> |
<%= link_to(entry["submission_count"] || 0, submission_files_path(search: { site_type: definition.enum_value })) %> |
diff --git a/config/routes.rb b/config/routes.rb
index 18f372f2..ca5e5c77 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -38,5 +38,10 @@
resources :log_events, only: %i[index show]
resources :archive_imports, only: %i[new create]
resources :stats, only: :index
+ resources :config, controller: "config", only: %i[index show] do
+ collection do
+ put :modify
+ end
+ end
root to: "artists#index"
end
diff --git a/docker-compose.yml b/docker-compose.yml
index 0c5d1ed4..cd13a287 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -79,6 +79,8 @@ services:
tests:
image: reverser
+ environment:
+ EXPOSED_VNC_PORT: "123"
volumes:
- .:/app
entrypoint: bundle exec rails test
diff --git a/test/controllers/config_controller_test.rb b/test/controllers/config_controller_test.rb
new file mode 100644
index 00000000..23b8422e
--- /dev/null
+++ b/test/controllers/config_controller_test.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require "test_helper"
+
+class ConfigControllerTest < ActionDispatch::IntegrationTest
+ test "index renders" do
+ get config_index_path
+ assert_response :success
+ end
+
+ test "show renders" do
+ get config_path("twitter")
+ assert_response :success
+ end
+end
diff --git a/test/logical/config_test.rb b/test/logical/config_test.rb
index 1ff381e3..c9ae9e0d 100644
--- a/test/logical/config_test.rb
+++ b/test/logical/config_test.rb
@@ -4,7 +4,7 @@
class ConfigTest < ActiveSupport::TestCase
setup do
- Config.stubs(:default_config).returns(app_name: "DefaultName")
+ Config.stubs(:default_config).returns(app_name: "DefaultName", bool?: true)
Config.force_reload
end
@@ -19,6 +19,7 @@ def stub_custom_config(**params, &)
f.flush
Config.unstub(:custom_config)
yield
+ Config.force_reload
end
end
@@ -48,4 +49,20 @@ def stub_custom_config(**params, &)
assert_equal("OverwrittenName", Config.app_name)
end
end
+
+ it "merges the config correctly" do
+ stub_custom_config(app_name: "OverwrittenName", other_key: "abc") do
+ assert_equal({ "app_name" => "NewName", "other_key" => "abc" }, Config.merge_custom_config("app_name" => "NewName"))
+ assert_equal({ "app_name" => "NewName", "other_key" => "abc" }, Config.merge_custom_config(app_name: "NewName"))
+ end
+ end
+
+ it "handles booleans" do
+ stub_custom_config(bool: "true") do
+ assert_predicate(Config, :bool?)
+ end
+ stub_custom_config(bool: "false") do
+ assert_not_predicate(Config, :bool?)
+ end
+ end
end