system_settings 0.1.0.pre → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +108 -9
  3. data/app/controllers/system_settings/root_controller.rb +1 -1
  4. data/app/models/system_settings/configurator.rb +18 -12
  5. data/app/models/system_settings/errors/settings_read_error.rb +6 -0
  6. data/app/models/system_settings/integer_list_setting.rb +1 -1
  7. data/app/models/system_settings/list_of_integers_validator.rb +6 -13
  8. data/app/models/system_settings/list_of_strings_validator.rb +36 -0
  9. data/app/models/system_settings/setting.rb +1 -1
  10. data/app/models/system_settings/string_list_setting.rb +2 -1
  11. data/app/models/system_settings/string_setting.rb +1 -0
  12. data/app/models/system_settings/type/integer_list.rb +10 -4
  13. data/app/models/system_settings/type/string_list.rb +2 -1
  14. data/config/locales/system_settings.en.yml +2 -1
  15. data/db/migrate/20181007125347_create_system_settings_settings.rb +1 -1
  16. data/frontend/build/asset-manifest.json +4 -4
  17. data/frontend/build/index.html +1 -1
  18. data/frontend/build/precache-manifest.2450e38293bed26dfe321bf03ff5acbb.js +22 -0
  19. data/frontend/build/service-worker.js +1 -1
  20. data/frontend/build/static/css/main.493112c7.chunk.css +1 -0
  21. data/frontend/build/static/js/2.17170219.chunk.js +1 -0
  22. data/frontend/build/static/js/main.dbe11b19.chunk.js +1 -0
  23. data/lib/system_settings/engine.rb +5 -0
  24. data/lib/system_settings/version.rb +1 -1
  25. data/lib/system_settings.rb +16 -0
  26. data/lib/tasks/system_settings_tasks.rake +18 -4
  27. metadata +17 -55
  28. data/frontend/build/precache-manifest.130ca067521371497dba783c23aa7e16.js +0 -22
  29. data/frontend/build/static/css/main.e262560e.chunk.css +0 -1
  30. data/frontend/build/static/js/2.83a5f4da.chunk.js +0 -1
  31. data/frontend/build/static/js/main.afaa2372.chunk.js +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 856385bd0d4333e0d4044a3afaca3dfad12db9cd3ade70940ab588cc8162ac0e
4
- data.tar.gz: 2ccd7b81d43f94bc33e473eef401ae2cdaa0b6ae212e64e8b0d4826717be16c1
3
+ metadata.gz: 9b0d4b4f84f7a19cd6befcc89649419e39607bb9c0e48a6b201d9c4eb8f07ba0
4
+ data.tar.gz: abc9fae1214a54c4fd6f5baeaec24a3879efa4a895c413a31b7966040a7ca274
5
5
  SHA512:
6
- metadata.gz: 31b69d9e2d3663a773cc6539211a2a53644f33a2aec0a40c1c8da1791a9dd81941dfb38d3e0615a4fcd0090e5b45b080f5ed2891303fc05fb8bae7ed0b804fd4
7
- data.tar.gz: 415a9d5a5968a4ed30be94b6ba4812d803be1c5a2d5ebbe15fbb8d716beacd9caa378669e471b3284ca31bcb42ab7ac90226fd3cd4ad8a639ce4f5dcf2c2f9f0
6
+ metadata.gz: f83ef8531ffbaf9f01c407baa8afd93848d9e1a4ff7671e0c656298f76d1e2d71449b30fa78847f8333efdf791cfb55e93e9d7b96eafafb9e27bd3e4e5fe8264
7
+ data.tar.gz: 7a9c5493d48d403de25deac639c1055cc36cbd3a6129457bdccd99b220883ac808492b3eb39f5c3029fb04e1b687307ff9a7c0a8b1bbb12bb06fd74731452db8
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
- # SystemSettings
2
- Short description and motivation.
1
+ # System Settings
2
+ System Settings is a Rails engine that adds settings functionality.
3
3
 
4
- ## Usage
5
- How to use my plugin.
4
+ Initial setting values can be loaded from file and later edited in a System Settings provided admin panel.
6
5
 
7
- ## Installation
6
+
7
+ ## Getting started
8
8
  Add this line to your application's Gemfile:
9
9
 
10
10
  ```ruby
@@ -16,13 +16,112 @@ And then execute:
16
16
  $ bundle
17
17
  ```
18
18
 
19
- Or install it yourself as:
19
+ Copy migrations:
20
+ ```bash
21
+ $ bin/rails system_settings:install:migrations
22
+ ```
23
+
24
+ And then run the migrations:
20
25
  ```bash
21
- $ gem install system_settings
26
+ $ bin/rails db:migrate
27
+ ```
28
+
29
+ Create settings file where all settings will be defined:
30
+ ```bash
31
+ $ touch config/system_settings.rb
32
+ ```
33
+
34
+ Add your first setting to `config/system_settings.rb`:
35
+ ```ruby
36
+ # String type values
37
+ string :default_mail_from, value: "Example Company <noreply@example.com>", description: "This email will be used for all outgoing emails"
38
+ string :date_format, value: "%Y-%m-%d"
39
+ string :default_locale, value: "en"
40
+
41
+ # Integer type values
42
+ integer :default_records_per_page, value: 25
43
+ integer :remainder_interval_in_hours, value: 48
44
+
45
+ # Array type strings and integers
46
+ string_list :admin_emails, description: "Will receive alerts"
47
+ integer_list :lucky_numbers, description: "Prime numbers are more effective", value: [2, 3, 5, 11]
48
+ ```
49
+
50
+ Load values from `config/system_settings.rb` into database:
51
+ ```bash
52
+ $ ./bin/rails system_settings:load
53
+ ```
54
+
55
+ Add System Settings admin panel to Rails routes:
56
+ ```ruby
57
+ Rails.application.routes.draw do
58
+ mount SystemSettings::Engine, at: "/system_settings"
59
+ # rest of your routes..
60
+ end
61
+ ```
62
+
63
+ Final step. Access settings values anywhere in your code:
64
+ ```ruby
65
+ SystemSettings[:date_format] # => "%Y-%m-%d"
66
+ SystemSettings[:lucky_numbers] # => [2, 3, 5, 11]
67
+
68
+ # You can change setting's value like any other Rails model.
69
+ SystemSettings::Setting.find_by(name: "default_mail_from").update({value: "No-Reply <noreply@example.com>"})
70
+ ```
71
+
72
+
73
+ ## Do not forget!
74
+ Before using System settings in production please protect the `/system_settings` endpoint with routing constraint. You can read more about it in [Rails Guides: Rails Routing from the Outside In](https://guides.rubyonrails.org/routing.html#advanced-constraints)
75
+
76
+ ```ruby
77
+ Rails.application.routes.draw do
78
+ mount SystemSettings::Engine, at: "/system_settings", constraints: AdminRoutingConstraint.new
79
+ # rest of your routes..
80
+ end
22
81
  ```
23
82
 
24
- ## Contributing
25
- Contribution directions go here.
83
+
84
+ ## Few more things
85
+
86
+ When you run `./bin/rails system_settings:load` task it will read `config/system_settings.rb` file and add new entries to the database. If you would like to replace all values with the ones from the file then run `./bin/rails system_settings:reset`
87
+
88
+ System Settings admin panel is precompiled at gem's build time. So it does not require any Javascript runtime and can be used with api-only Rails applications.
89
+
90
+ If you would like to store your settings somewhere else than `config/system_settings.rb` you can use ENV variable `SYSTEM_SETTINGS_PATH` to specify custom path.
91
+
92
+ ## Development
93
+
94
+ Required development dependencies:
95
+ * [Node.js](https://nodejs.org/) - JavaScript runtime
96
+ * [Yarn](https://yarnpkg.com/) - package manager
97
+
98
+ Optional development tools:
99
+ * [overmind](https://github.com/DarthSim/overmind) - Process manager for Procfile-based applications and tmux
100
+ * [direnv](https://direnv.net/) - Unclutter your .profile
101
+
102
+ Required environment variables:
103
+ * `RAILS_VERSION`
104
+ * `SQLITE3_VERSION`
105
+
106
+ As System Settings gem is being developed to be compatible with multiple [Rails](https://github.com/rails/rails) versions,
107
+ you need to set `RAILS_VERSION` and `SQLITE3_VERSION` environment variables when running `bundle install` or any `./bin/rails` command.
108
+ It is recommended to set these up using [direnv](https://direnv.net/) and `.envrc` file.
109
+
110
+
111
+ Getting started with development:
112
+ 1) `RAILS_VERSION=5.2.3 SQLITE3_VERSION=1.4.1 bundle`
113
+ 2) `RAILS_VERSION=5.2.3 SQLITE3_VERSION=1.4.1 ./bin/rails db:create db:migrate`
114
+ 3) `RAILS_VERSION=5.2.3 SQLITE3_VERSION=1.4.1 ./bin/rails test`
115
+ 4) `RAILS_VERSION=5.2.3 SQLITE3_VERSION=1.4.1 ./bin/rails frontend:install`
116
+ 4) `RAILS_VERSION=5.2.3 SQLITE3_VERSION=1.4.1 ./bin/rails app:system_settings:load`
117
+ 4) `RAILS_VERSION=5.2.3 SQLITE3_VERSION=1.4.1 overmind start`
118
+
119
+
120
+ ## Build status
121
+ System Settings is being tested with Rails versions - `5.0`, `5.1`, `5.2`, `6.0`, `rails repo master branch`
122
+
123
+ [![Build Status](https://dev.azure.com/kristsozols/System%20Settings/_apis/build/status/krists.system_settings?branchName=master)](https://dev.azure.com/kristsozols/System%20Settings/_build/latest?definitionId=1&branchName=master)
124
+
26
125
 
27
126
  ## License
28
127
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -3,7 +3,7 @@ require_relative "./application_controller"
3
3
  module SystemSettings
4
4
  class RootController < ApplicationController
5
5
  def index
6
- if File.exists?(SystemSettings::Engine.frontend_build_index_html_path)
6
+ if File.exist?(SystemSettings::Engine.frontend_build_index_html_path)
7
7
  render file: SystemSettings::Engine.frontend_build_index_html_path
8
8
  else
9
9
  render plain: "Frontend application has not been compiled", status: :not_implemented
@@ -1,25 +1,30 @@
1
1
  module SystemSettings
2
2
  class Configurator
3
-
4
3
  class << self
5
4
  def from_file(path)
6
- file_content = File.read(path)
5
+ path_str = path.to_s
6
+ raise SystemSettings::Errors::SettingsReadError, "#{path_str} file does not exist" unless File.exist?(path_str)
7
+ raise SystemSettings::Errors::SettingsReadError, "#{path_str} file not readable" unless File.readable?(path_str)
8
+ file_content = File.read(path_str)
7
9
  new.tap do |obj|
8
- obj.instance_eval(file_content, path, 1)
10
+ obj.instance_eval(file_content, path_str, 1)
9
11
  end
10
12
  end
13
+
14
+ def purge
15
+ new.purge
16
+ end
11
17
  end
12
18
 
13
19
  attr_reader :items
14
20
 
15
21
  def initialize(&block)
16
22
  @items = []
17
- if block_given?
18
- if block.arity == 1
19
- yield self
20
- else
21
- instance_exec(&block)
22
- end
23
+ return unless block_given?
24
+ if block.arity == 1
25
+ yield self
26
+ else
27
+ instance_exec(&block)
23
28
  end
24
29
  end
25
30
 
@@ -28,7 +33,7 @@ module SystemSettings
28
33
  end
29
34
 
30
35
  def string_list(name, value: nil, description: nil, &blk)
31
- add(name, SystemSettings::StringListSetting, value: value, description: description, &blk)
36
+ add(name, SystemSettings::StringListSetting, value: value || [], description: description, &blk)
32
37
  end
33
38
 
34
39
  def integer(name, value: nil, description: nil, &blk)
@@ -36,7 +41,7 @@ module SystemSettings
36
41
  end
37
42
 
38
43
  def integer_list(name, value: nil, description: nil, &blk)
39
- add(name, SystemSettings::IntegerListSetting, value: value, description: description, &blk)
44
+ add(name, SystemSettings::IntegerListSetting, value: value || [], description: description, &blk)
40
45
  end
41
46
 
42
47
  def persist
@@ -51,8 +56,9 @@ module SystemSettings
51
56
  end
52
57
  end
53
58
  end
59
+ true
54
60
  else
55
- $stderr.puts "SystemSettings: Settings table has not been created!"
61
+ warn "SystemSettings: Settings table has not been created!"
56
62
  false
57
63
  end
58
64
  end
@@ -0,0 +1,6 @@
1
+ module SystemSettings
2
+ module Errors
3
+ class SettingsReadError < StandardError
4
+ end
5
+ end
6
+ end
@@ -3,6 +3,6 @@ require_relative "./setting"
3
3
  module SystemSettings
4
4
  class IntegerListSetting < Setting
5
5
  attribute :value, SystemSettings::Type::IntegerList.new
6
- validates :value, list_of_integers: { allow_blank: true }
6
+ validates :value, "system_settings/list_of_integers": true
7
7
  end
8
8
  end
@@ -1,31 +1,24 @@
1
1
  module SystemSettings
2
2
  class ListOfIntegersValidator < ActiveModel::EachValidator
3
- LIST_REGEXP = /\A[+-]?\d+(?:; *[+-]?\d+)*\z/
4
- SINGLE_REGEXP = /\A[+-]?\d+\z/
3
+ LIST_REGEXP = /\A[+-]?\d+(?:; *[+-]?\d+)*\z/.freeze
4
+ SINGLE_REGEXP = /\A[+-]?\d+\z/.freeze
5
5
 
6
6
  def validate_each(record, attr_name, value)
7
7
  came_from_user = :"#{attr_name}_came_from_user?"
8
8
 
9
- if record.respond_to?(came_from_user) && record.public_send(came_from_user)
10
- raw_value = record.read_attribute_before_type_cast(attr_name)
11
- end
9
+ raw_value = record.read_attribute_before_type_cast(attr_name) if record.respond_to?(came_from_user) && record.public_send(came_from_user)
12
10
  raw_value ||= value
13
11
 
14
- if record_attribute_changed_in_place?(record, attr_name)
15
- raw_value = value
16
- end
12
+ raw_value = value if record_attribute_changed_in_place?(record, attr_name)
17
13
 
18
- unless matches_list_of_integers_regexp?(raw_value)
19
- record.errors.add(attr_name, :not_a_list_of_integers)
20
- return
21
- end
14
+ record.errors.add(attr_name, :not_a_list_of_integers) unless matches_list_of_integers_regexp?(raw_value)
22
15
  end
23
16
 
24
17
  private
25
18
 
26
19
  def record_attribute_changed_in_place?(record, attr_name)
27
20
  record.respond_to?(:attribute_changed_in_place?) &&
28
- record.attribute_changed_in_place?(attr_name.to_s)
21
+ record.attribute_changed_in_place?(attr_name.to_s)
29
22
  end
30
23
 
31
24
  def matches_list_of_integers_regexp?(raw_value)
@@ -0,0 +1,36 @@
1
+ module SystemSettings
2
+ class ListOfStringsValidator < ActiveModel::EachValidator
3
+ NON_WHITESPACE_REGEXP = /[^[:space:]]/.freeze
4
+
5
+ def validate_each(record, attr_name, value)
6
+ came_from_user = :"#{attr_name}_came_from_user?"
7
+
8
+ raw_value = record.read_attribute_before_type_cast(attr_name) if record.respond_to?(came_from_user) && record.public_send(came_from_user)
9
+ raw_value ||= value
10
+
11
+ raw_value = value if record_attribute_changed_in_place?(record, attr_name)
12
+
13
+ record.errors.add(attr_name, :not_a_list_of_strings) unless matches_list_of_strings_regexp?(raw_value)
14
+ end
15
+
16
+ private
17
+
18
+ def record_attribute_changed_in_place?(record, attr_name)
19
+ record.respond_to?(:attribute_changed_in_place?) &&
20
+ record.attribute_changed_in_place?(attr_name.to_s)
21
+ end
22
+
23
+ def matches_list_of_strings_regexp?(raw_value)
24
+ case raw_value
25
+ when String
26
+ raw_value.split(SystemSettings::Type::StringList::DELIMITER_REGEXP).all? do |value|
27
+ NON_WHITESPACE_REGEXP.match?(value)
28
+ end
29
+ when Array
30
+ raw_value.all? { |v| NON_WHITESPACE_REGEXP.match?(v.to_s) }
31
+ else
32
+ false
33
+ end
34
+ end
35
+ end
36
+ end
@@ -5,4 +5,4 @@ module SystemSettings
5
5
  validates :type, presence: true
6
6
  validates :name, presence: true, uniqueness: true
7
7
  end
8
- end
8
+ end
@@ -3,5 +3,6 @@ require_relative "./setting"
3
3
  module SystemSettings
4
4
  class StringListSetting < Setting
5
5
  attribute :value, SystemSettings::Type::StringList.new
6
+ validates :value, "system_settings/list_of_strings": true
6
7
  end
7
- end
8
+ end
@@ -3,5 +3,6 @@ require_relative "./setting"
3
3
  module SystemSettings
4
4
  class StringSetting < Setting
5
5
  attribute :value, :string
6
+ validates :value, presence: true
6
7
  end
7
8
  end
@@ -20,11 +20,17 @@ module SystemSettings
20
20
  def cast_value(value)
21
21
  case value
22
22
  when Array
23
- value.map { |v| v.to_i rescue nil }
23
+ value.map do |v|
24
+ v.to_i
25
+ rescue StandardError
26
+ nil
27
+ end
24
28
  when String
25
- value.split(SEPARATOR).map { |v| v.to_i rescue nil }
26
- else
27
- nil
29
+ value.split(SEPARATOR).map do |v|
30
+ v.to_i
31
+ rescue StandardError
32
+ nil
33
+ end
28
34
  end
29
35
  end
30
36
  end
@@ -1,6 +1,7 @@
1
1
  module SystemSettings
2
2
  module Type
3
3
  class StringList < ActiveModel::Type::Value
4
+ DELIMITER_REGEXP = /(?<=[^\\]);/.freeze
4
5
  def type
5
6
  :string_list
6
7
  end
@@ -20,7 +21,7 @@ module SystemSettings
20
21
  when Array
21
22
  value.map { |v| String(v).strip }
22
23
  when String
23
- value.split(/(?<=[^\\]);/).map(&:strip)
24
+ value.split(DELIMITER_REGEXP).map(&:strip).map { |str| str.gsub("\\;", ";") }
24
25
  end
25
26
  end
26
27
  end
@@ -29,4 +29,5 @@ en:
29
29
  system_settings/setting:
30
30
  attributes:
31
31
  value:
32
- not_a_list_of_integers: "not a list of integers"
32
+ not_a_list_of_integers: "not a list of integers"
33
+ not_a_list_of_strings: "not a list of strings"
@@ -1,4 +1,4 @@
1
- class CreateSystemSettingsSettings < ActiveRecord::Migration[5.2]
1
+ class CreateSystemSettingsSettings < ActiveRecord::Migration[5.0]
2
2
  def change
3
3
  create_table :system_settings_settings do |t|
4
4
  t.string :name, null: false, index: { unique: true }
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "files": {
3
- "main.css": "/system_settings/static/css/main.e262560e.chunk.css",
4
- "main.js": "/system_settings/static/js/main.afaa2372.chunk.js",
3
+ "main.css": "/system_settings/static/css/main.493112c7.chunk.css",
4
+ "main.js": "/system_settings/static/js/main.dbe11b19.chunk.js",
5
5
  "runtime~main.js": "/system_settings/static/js/runtime~main.a09e9b82.js",
6
- "static/js/2.83a5f4da.chunk.js": "/system_settings/static/js/2.83a5f4da.chunk.js",
6
+ "static/js/2.17170219.chunk.js": "/system_settings/static/js/2.17170219.chunk.js",
7
7
  "index.html": "/system_settings/index.html",
8
- "precache-manifest.130ca067521371497dba783c23aa7e16.js": "/system_settings/precache-manifest.130ca067521371497dba783c23aa7e16.js",
8
+ "precache-manifest.2450e38293bed26dfe321bf03ff5acbb.js": "/system_settings/precache-manifest.2450e38293bed26dfe321bf03ff5acbb.js",
9
9
  "service-worker.js": "/system_settings/service-worker.js"
10
10
  }
11
11
  }
@@ -1 +1 @@
1
- <!doctype html><html lang="en"><head><meta charset="utf-8"><link rel="shortcut icon" href="/system_settings/favicon.ico"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="theme-color" content="#000000"><title>System Settings</title><link href="/system_settings/static/css/main.e262560e.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script src="/system_settings/static/js/runtime~main.a09e9b82.js"></script><script src="/system_settings/static/js/2.83a5f4da.chunk.js"></script><script src="/system_settings/static/js/main.afaa2372.chunk.js"></script></body></html>
1
+ <!doctype html><html lang="en"><head><meta charset="utf-8"><link rel="shortcut icon" href="/system_settings/favicon.ico"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="theme-color" content="#000000"><title>System Settings</title><link href="/system_settings/static/css/main.493112c7.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script src="/system_settings/static/js/runtime~main.a09e9b82.js"></script><script src="/system_settings/static/js/2.17170219.chunk.js"></script><script src="/system_settings/static/js/main.dbe11b19.chunk.js"></script></body></html>
@@ -0,0 +1,22 @@
1
+ self.__precacheManifest = (self.__precacheManifest || []).concat([
2
+ {
3
+ "revision": "0acf133c6751acd69af511a671c2fd1e",
4
+ "url": "/system_settings/index.html"
5
+ },
6
+ {
7
+ "revision": "e07baceb8e059f51d274",
8
+ "url": "/system_settings/static/css/main.493112c7.chunk.css"
9
+ },
10
+ {
11
+ "revision": "f336fd31562a5938e83a",
12
+ "url": "/system_settings/static/js/2.17170219.chunk.js"
13
+ },
14
+ {
15
+ "revision": "e07baceb8e059f51d274",
16
+ "url": "/system_settings/static/js/main.dbe11b19.chunk.js"
17
+ },
18
+ {
19
+ "revision": "1b52e032205e59a9e84f",
20
+ "url": "/system_settings/static/js/runtime~main.a09e9b82.js"
21
+ }
22
+ ]);
@@ -14,7 +14,7 @@
14
14
  importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.0/workbox-sw.js");
15
15
 
16
16
  importScripts(
17
- "/system_settings/precache-manifest.130ca067521371497dba783c23aa7e16.js"
17
+ "/system_settings/precache-manifest.2450e38293bed26dfe321bf03ff5acbb.js"
18
18
  );
19
19
 
20
20
  self.addEventListener('message', (event) => {
@@ -0,0 +1 @@
1
+ .App_container__BLGGc{min-height:100vh;display:flex;flex-direction:column;position:relative;z-index:auto}.App_header-wrap__3DnwX{position:fixed;width:100%;background:#fff;border-bottom:1px solid #e1e1e1;z-index:1}.App_header__1o2Y8{max-width:1020px;margin:0 auto;display:flex}.App_header-name__2XXxc{font-size:1.5rem;line-height:2rem;font-weight:700;padding:.5rem 0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;text-decoration:none}.App_header-name__2XXxc:active,.App_header-name__2XXxc:focus,.App_header-name__2XXxc:hover{text-decoration:none}.App_header-spacer__3F0KU{margin-top:calc(3rem + 1px);height:3rem}.App_content-wrap__1oPWs{flex-grow:5;box-sizing:border-box;display:flex}.App_content__3dh9P{max-width:1020px;min-width:0;margin:0 auto;display:flex;flex-grow:1;flex-direction:column}.NotFoundPage_container__35vs4{flex-grow:1;align-items:center;justify-content:center;display:flex}.ListPage_table__2qY_8{table-layout:fixed;white-space:nowrap;width:100%;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ListPage_table__2qY_8 td:empty:after{content:"-"}.ListPage_empty-tr__2VxYl{color:#a6a6a6}.ListPage_empty-tr__2VxYl td{text-align:center}.ListPage_name__1-Gs-{width:20%;overflow:hidden;text-overflow:ellipsis}.ListPage_table__2qY_8 tbody .ListPage_description__1Sci0,.ListPage_table__2qY_8 tbody .ListPage_name__1-Gs-,.ListPage_table__2qY_8 tbody .ListPage_value__3X3Vq{-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text}.ListPage_description__1Sci0{width:50%}.ListPage_description__1Sci0,.ListPage_value__3X3Vq{overflow:hidden;text-overflow:ellipsis}.ListPage_actions__2iqTu{width:50px;text-align:right}.PageLoadError_wrap__mpF75{display:flex;align-items:flex-start;justify-content:center}.PageLoadError_container__kcD9o{display:flex;flex-direction:column;background:#ffe5e5;padding:.25rem .5rem;border-radius:4px;color:#b94343;text-align:center}.PageLoadError_error-small__14Fex{font-size:.85em}#nprogress{pointer-events:none}#nprogress .bar{background:#ffc25b;position:fixed;z-index:1031;top:0;left:0;width:100%;height:2px}#nprogress .peg{display:block;position:absolute;right:0;width:100px;height:100%;box-shadow:0 0 10px #ffc25b,0 0 5px #ffc25b;opacity:1;-webkit-transform:rotate(3deg) translateY(-4px);transform:rotate(3deg) translateY(-4px)}#nprogress .spinner{display:block;position:fixed;z-index:1031;top:.9rem;right:.9rem}#nprogress .spinner-icon{width:1.2rem;height:1.2rem;box-sizing:border-box;border-color:#ffc25b transparent transparent #ffc25b;border-style:solid;border-width:2px;border-radius:50%;-webkit-animation:nprogress-spinner .4s linear infinite;animation:nprogress-spinner .4s linear infinite}.nprogress-custom-parent{overflow:hidden;position:relative}.nprogress-custom-parent #nprogress .bar,.nprogress-custom-parent #nprogress .spinner{position:absolute}@-webkit-keyframes nprogress-spinner{0%{-webkit-transform:rotate(0deg)}to{-webkit-transform:rotate(1turn)}}@keyframes nprogress-spinner{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.Value_wrap__3J_8z{white-space:nowrap}.Value_section__cgQmL{max-width:100%;margin-right:.25em}.Value_section__cgQmL:last-child{margin-right:0}.Attribute_wrap__1Zy9J{display:flex;flex-direction:column;margin-bottom:1rem}.Attribute_name__3a-cD{font-size:.8em;color:#737373;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.Attribute_value__IxZCj:empty:after{content:"-"}.ButtonBar_container__21aEr{border-top:1px dotted #f2f2f2;padding-top:1rem;margin-bottom:1rem}.ButtonBar_container__21aEr>*{margin-right:.5rem}.LabeledInput_wrap__2KAyr{position:relative;margin-bottom:1em}.LabeledInput_label__1AyjZ{font-size:.8em;color:#737373;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;display:block}.LabeledInput_input-and-error-wrap__2iJKd{display:flex;align-items:baseline}.LabeledInput_error__2QBpW{flex-grow:0;display:block;color:#ff3656;margin-left:1rem;white-space:nowrap}.LabeledInput_hint__rAeG8{font-size:.8em;display:block}.SettingForm_error-wrap__3SdnI{display:flex;padding:.5rem 0}.SettingForm_error-message__uQK1Y{background:#ffe5e5;padding:.25rem .5rem;border-radius:4px;color:#b94343}.SettingForm_error-small__3owRp{font-size:.85em}.ClassicSpinner_wrap__1LF2_{display:block}html{box-sizing:border-box;overflow-y:scroll;font-size:14px}*,:after,:before{box-sizing:inherit}:root,body{background:#fff}body{color:#233656;font-family:Arial,Helvetica,sans-serif;font-weight:400;font-size:1rem;line-height:1.5rem;margin:0;padding:0;min-height:100vh;position:relative;display:flex;flex-direction:column}table{border-collapse:collapse;border-spacing:0}table td,table th{text-align:left}a{color:#233656;text-decoration:none}a:hover{text-decoration:underline}a.button,button,input[type=button],input[type=submit]{display:inline-block;cursor:pointer;border:none;border-radius:3px;padding:.375rem .75rem;font-size:1rem;line-height:1.5;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;white-space:nowrap}button,input[type=button],input[type=submit]{color:#4d4d4d;background-color:#d9d9d9;-webkit-transition:background-color .1s ease-in-out;transition:background-color .1s ease-in-out;outline:none}button:focus,button:hover,input[type=button]:focus,input[type=button]:hover,input[type=submit]:focus,input[type=submit]:hover{background-color:#b8b8b8}button:active,input[type=button]:active,input[type=submit]:active{background-color:#828282}a.button{color:#4d4d4d;background-color:initial;-webkit-transition:background-color .1s ease-in-out;transition:background-color .1s ease-in-out;outline:none;text-decoration:none}a.button:focus,a.button:hover{background-color:rgba(0,0,0,.15)}a.button:active{background-color:rgba(0,0,0,.4)}a.button.primary,button.primary,input[type=button].primary,input[type=submit].primary{color:#4d4d4d;background-color:#a6a6a6;-webkit-transition:background-color .1s ease-in-out;transition:background-color .1s ease-in-out;outline:none}a.button.primary:focus,a.button.primary:hover,button.primary:focus,button.primary:hover,input[type=button].primary:focus,input[type=button].primary:hover,input[type=submit].primary:focus,input[type=submit].primary:hover{background-color:#8d8d8d}a.button.primary:active,button.primary:active,input[type=button].primary:active,input[type=submit].primary:active{background-color:#646464}input[type=text]{border:none;border-radius:3px;padding:.5em;font-size:1rem;line-height:normal;color:#4d4d4d;background-color:#e6e6e6;-webkit-transition:background-color .1s ease-in-out;transition:background-color .1s ease-in-out;outline:none;box-sizing:initial;width:auto;min-width:12ch;max-width:calc(100% - 2em);will-change:width}input[type=text]:focus{background-color:#c4c4c4}input[type=text]:active{background-color:#bfbfbf}.button-wrap{display:flex;align-items:center}.svg-wrap{display:block}code{border-radius:2px;padding:0 .2rem}.sysname,code{background:#f2f2f2}.sysname{display:inline-block;border-radius:3px;padding:0 .5em;font-family:Courier New,Courier,monospace;font-size:.75em;max-width:100%}#root{z-index:1;position:relative}