super_settings 1.0.2 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +110 -16
- data/VERSION +1 -1
- data/app/helpers/super_settings/settings_helper.rb +13 -3
- data/app/views/layouts/super_settings/settings.html.erb +1 -1
- data/config/routes.rb +1 -1
- data/db/migrate/20210414004553_create_super_settings.rb +1 -7
- data/lib/super_settings/application/api.js +4 -1
- data/lib/super_settings/application/helper.rb +56 -17
- data/lib/super_settings/application/images/arrow-down-short.svg +3 -0
- data/lib/super_settings/application/images/arrow-up-short.svg +3 -0
- data/lib/super_settings/application/images/info-circle.svg +4 -0
- data/lib/super_settings/application/images/pencil-square.svg +4 -0
- data/lib/super_settings/application/images/plus.svg +3 -1
- data/lib/super_settings/application/images/trash3.svg +3 -0
- data/lib/super_settings/application/images/x-circle.svg +4 -0
- data/lib/super_settings/application/index.html.erb +54 -37
- data/lib/super_settings/application/layout.html.erb +5 -2
- data/lib/super_settings/application/layout_styles.css +7 -151
- data/lib/super_settings/application/layout_vars.css.erb +21 -0
- data/lib/super_settings/application/scripts.js +100 -21
- data/lib/super_settings/application/style_vars.css.erb +62 -0
- data/lib/super_settings/application/styles.css +183 -14
- data/lib/super_settings/application.rb +18 -11
- data/lib/super_settings/attributes.rb +1 -8
- data/lib/super_settings/configuration.rb +9 -0
- data/lib/super_settings/controller_actions.rb +2 -2
- data/lib/super_settings/engine.rb +1 -1
- data/lib/super_settings/history_item.rb +1 -1
- data/lib/super_settings/http_client.rb +165 -0
- data/lib/super_settings/rack_application.rb +3 -3
- data/lib/super_settings/rest_api.rb +5 -4
- data/lib/super_settings/setting.rb +13 -2
- data/lib/super_settings/storage/active_record_storage.rb +7 -0
- data/lib/super_settings/storage/history_attributes.rb +31 -0
- data/lib/super_settings/storage/http_storage.rb +60 -184
- data/lib/super_settings/storage/json_storage.rb +201 -0
- data/lib/super_settings/storage/mongodb_storage.rb +238 -0
- data/lib/super_settings/storage/redis_storage.rb +49 -111
- data/lib/super_settings/storage/s3_storage.rb +165 -0
- data/lib/super_settings/storage/storage_attributes.rb +64 -0
- data/lib/super_settings/storage/test_storage.rb +3 -5
- data/lib/super_settings/storage/transaction.rb +67 -0
- data/lib/super_settings/storage.rb +13 -6
- data/lib/super_settings/time_precision.rb +36 -0
- data/lib/super_settings.rb +11 -0
- data/super_settings.gemspec +4 -2
- metadata +22 -9
- data/lib/super_settings/application/images/edit.svg +0 -1
- data/lib/super_settings/application/images/info.svg +0 -1
- data/lib/super_settings/application/images/slash.svg +0 -1
- data/lib/super_settings/application/images/trash.svg +0 -1
@@ -0,0 +1,62 @@
|
|
1
|
+
.super-settings {
|
2
|
+
--primary-color-h: 216;
|
3
|
+
--primary-color-s: 98%;
|
4
|
+
--primary-color-l: 52%;
|
5
|
+
--primary-color-hsl: var(--primary-color-h), var(--primary-color-s), var(--primary-color-l);
|
6
|
+
--primary-color: hsl(var(--primary-color-hsl));
|
7
|
+
--primary-contrast-color: #fff;
|
8
|
+
--primary-hover-color: hsl(var(--primary-color-h), var(--primary-color-s), calc(var(--primary-color-l) - 10%));
|
9
|
+
--primary-border-color: hsl(var(--primary-color-h), var(--primary-color-s), calc(var(--primary-color-l) + 10%));
|
10
|
+
}
|
11
|
+
|
12
|
+
<% unless color_scheme == :dark %>
|
13
|
+
.super-settings {
|
14
|
+
--edit-bg-color: #f2fdf2;
|
15
|
+
--deleted-row-color: darkred;
|
16
|
+
--deleted-row-bg-color: #ffd1d8;
|
17
|
+
--history-key-color: royalblue;
|
18
|
+
--table-header-bg-color: #fff;
|
19
|
+
--table-border-color: #dee2e6;
|
20
|
+
--alt-row-color: rgba(0, 0, 0, .05);
|
21
|
+
--form-control-color: #495057;
|
22
|
+
--form-control-bg-color: #fff;
|
23
|
+
--form-control-border-color: #ced4da;
|
24
|
+
--form-control-placeholder-color: #bbb;
|
25
|
+
--modal-bg-color: #fff;
|
26
|
+
--modal-transparency: 0.4;
|
27
|
+
--modal-control-color: #000;
|
28
|
+
--success-color: green;
|
29
|
+
--danger-color: firebrick;
|
30
|
+
--muted-color: #666;
|
31
|
+
--unselected-color: #666;
|
32
|
+
}
|
33
|
+
<% end %>
|
34
|
+
|
35
|
+
<% if color_scheme == :system %>
|
36
|
+
@media (prefers-color-scheme: dark) {
|
37
|
+
<% end %>
|
38
|
+
<% if color_scheme == :system || color_scheme == :dark %>
|
39
|
+
.super-settings {
|
40
|
+
--edit-bg-color: #8dc875;
|
41
|
+
--deleted-row-color: #e0b1b8;
|
42
|
+
--deleted-row-bg-color: #7a3636;
|
43
|
+
--history-key-color: #a7d6f4;
|
44
|
+
--table-header-bg-color: #333;
|
45
|
+
--table-border-color: #555;
|
46
|
+
--alt-row-color: rgba(0, 0, 0, .30);
|
47
|
+
--form-control-color: #eee;
|
48
|
+
--form-control-bg-color: #666;
|
49
|
+
--form-control-border-color: #555;
|
50
|
+
--form-control-placeholder-color: #aaa;
|
51
|
+
--modal-bg-color: #333;
|
52
|
+
--modal-transparency: 0.75;
|
53
|
+
--modal-control-color: #fff;
|
54
|
+
--success-color: #00ff00;
|
55
|
+
--danger-color: #ff0000;
|
56
|
+
--muted-color: #999;
|
57
|
+
--unselected-color: #ccc;
|
58
|
+
}
|
59
|
+
<% end %>
|
60
|
+
<% if color_scheme == :system %>
|
61
|
+
}
|
62
|
+
<% end %>
|
@@ -1,3 +1,10 @@
|
|
1
|
+
.super-settings-container {
|
2
|
+
padding-left: 15px;
|
3
|
+
padding-right: 15px;
|
4
|
+
margin-left: auto;
|
5
|
+
margin-right: auto;
|
6
|
+
}
|
7
|
+
|
1
8
|
#settings-table td p {
|
2
9
|
margin-top: 0;
|
3
10
|
margin-bottom: 0.5rem;
|
@@ -11,13 +18,9 @@
|
|
11
18
|
width: 100%;
|
12
19
|
}
|
13
20
|
|
14
|
-
.super-settings-edit-row {
|
15
|
-
background-color: #f2fdf2 !important;
|
16
|
-
}
|
17
|
-
|
18
21
|
#settings-table tr[data-deleted] td {
|
19
|
-
background-color:
|
20
|
-
color:
|
22
|
+
background-color: var(--deleted-row-bg-color) !important;
|
23
|
+
color: var(--deleted-row-color);
|
21
24
|
text-decoration: line-through;
|
22
25
|
}
|
23
26
|
|
@@ -25,16 +28,49 @@
|
|
25
28
|
display: none;
|
26
29
|
}
|
27
30
|
|
31
|
+
.super-settings-icon {
|
32
|
+
display: inline-block;
|
33
|
+
}
|
34
|
+
|
35
|
+
.super-settings-icon svg {
|
36
|
+
width: 100%;
|
37
|
+
height: 100%;
|
38
|
+
vertical-align: inherit;
|
39
|
+
}
|
40
|
+
|
41
|
+
.super-settings-btn-no-chrome {
|
42
|
+
display: inline-block;
|
43
|
+
vertical-align: middle;
|
44
|
+
background-color: transparent;
|
45
|
+
border: 0;
|
46
|
+
padding: 0;
|
47
|
+
cursor: pointer;
|
48
|
+
}
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
.super-settings-sort-control {
|
53
|
+
color: var(--unselected-color);
|
54
|
+
}
|
55
|
+
|
56
|
+
.super-settings-sort-control svg {
|
57
|
+
vertical-align: middle;
|
58
|
+
}
|
59
|
+
|
60
|
+
.super-settings-edit-row {
|
61
|
+
background-color: var(--edit-bg-color) !important;
|
62
|
+
}
|
63
|
+
|
28
64
|
.super-settings-key {
|
29
65
|
overflow-wrap: break-word;
|
30
66
|
max-width: 30rem;
|
31
|
-
min-width:
|
67
|
+
min-width: 8rem;
|
32
68
|
}
|
33
69
|
|
34
70
|
.super-settings-value {
|
35
71
|
overflow-wrap: break-word;
|
36
72
|
max-width: 30rem;
|
37
|
-
min-width:
|
73
|
+
min-width: 8rem;
|
38
74
|
}
|
39
75
|
|
40
76
|
.super-settings-value-type {
|
@@ -59,7 +95,34 @@
|
|
59
95
|
|
60
96
|
.super-settings-history-key {
|
61
97
|
font-weight: normal;
|
62
|
-
color:
|
98
|
+
color: var(--history-key-color);
|
99
|
+
}
|
100
|
+
|
101
|
+
.super-settings-table {
|
102
|
+
width: 100%;
|
103
|
+
max-width: 100%;
|
104
|
+
margin-bottom: 1rem;
|
105
|
+
border-collapse: collapse;
|
106
|
+
}
|
107
|
+
|
108
|
+
.super-settings-table thead th {
|
109
|
+
vertical-align: bottom;
|
110
|
+
border-bottom: 2px solid var(--table-border-color);
|
111
|
+
white-space: nowrap;
|
112
|
+
}
|
113
|
+
|
114
|
+
.super-settings-table td, .super-settings-table th {
|
115
|
+
padding: 0.75rem;
|
116
|
+
vertical-align: top;
|
117
|
+
border-top: 1px solid var(--table-border-color);
|
118
|
+
}
|
119
|
+
|
120
|
+
.super-settings-table-striped tbody tr:nth-of-type(odd) {
|
121
|
+
background-color: var(--alt-row-color);
|
122
|
+
}
|
123
|
+
|
124
|
+
.super-settings-align-center {
|
125
|
+
text-align: center;
|
63
126
|
}
|
64
127
|
|
65
128
|
.super-settings-modal {
|
@@ -72,12 +135,12 @@
|
|
72
135
|
width: 100%;
|
73
136
|
height: 100%;
|
74
137
|
overflow: auto;
|
75
|
-
background-color: rgba(0,0,0,
|
138
|
+
background-color: rgba(0, 0, 0, var(--modal-transparency));
|
76
139
|
}
|
77
140
|
|
78
141
|
.super-settings-modal-dialog {
|
79
142
|
margin: auto;
|
80
|
-
background-color:
|
143
|
+
background-color: var(--modal-bg-color);
|
81
144
|
position: relative;
|
82
145
|
outline: 0;
|
83
146
|
padding: 2em;
|
@@ -99,7 +162,7 @@
|
|
99
162
|
right: 0;
|
100
163
|
border: 0;
|
101
164
|
padding: 1ex;
|
102
|
-
|
165
|
+
color: var(--modal-control-color);
|
103
166
|
font-size: 1.5rem;
|
104
167
|
font-weight: 500;
|
105
168
|
}
|
@@ -122,6 +185,112 @@
|
|
122
185
|
.super-settings-sticky-top {
|
123
186
|
position: sticky;
|
124
187
|
top: 0;
|
125
|
-
padding: 1rem
|
126
|
-
background-color:
|
188
|
+
padding: 1rem;
|
189
|
+
background-color: var(--table-header-bg-color);
|
190
|
+
}
|
191
|
+
|
192
|
+
.super-settings-form-control {
|
193
|
+
font-family: inherit;
|
194
|
+
margin: 0;
|
195
|
+
overflow: visible;
|
196
|
+
display: block;
|
197
|
+
width: 100%;
|
198
|
+
padding: .375rem .75rem;
|
199
|
+
font-size: 1rem;
|
200
|
+
line-height: 1.5;
|
201
|
+
color: var(--form-control-color);
|
202
|
+
background-color: var(--form-control-bg-color);
|
203
|
+
background-clip: padding-box;
|
204
|
+
border: 1px solid var(--form-control-border-color);
|
205
|
+
border-radius: .25rem;
|
206
|
+
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
|
207
|
+
}
|
208
|
+
|
209
|
+
.super-settings-form-control::placeholder {
|
210
|
+
color: var(--form-control-placeholder-color);
|
211
|
+
}
|
212
|
+
|
213
|
+
select.super-settings-form-control:not([size]):not([multiple]) {
|
214
|
+
height: calc(2.25rem + 2px);
|
215
|
+
}
|
216
|
+
|
217
|
+
.super-settings-form-check {
|
218
|
+
margin-top: .5rem;
|
219
|
+
display: inline-block;
|
220
|
+
}
|
221
|
+
|
222
|
+
.super-settings-form-check input[type=checkbox] {
|
223
|
+
vertical-align: middle;
|
224
|
+
}
|
225
|
+
|
226
|
+
.super-settings-form-inline {
|
227
|
+
display: inline-block;
|
228
|
+
}
|
229
|
+
|
230
|
+
.super-settings-form-inline .super-settings-form-control {
|
231
|
+
display: inline-block;
|
232
|
+
width: auto;
|
233
|
+
vertical-align: middle;
|
234
|
+
}
|
235
|
+
|
236
|
+
.super-settings-form-control textarea {
|
237
|
+
font-family: inherit;
|
238
|
+
margin: 0;
|
239
|
+
overflow: auto;
|
240
|
+
resize: vertical;
|
241
|
+
}
|
242
|
+
|
243
|
+
.super-settings-text-success {
|
244
|
+
color: var(--success-color) !important;
|
245
|
+
}
|
246
|
+
|
247
|
+
.super-settings-text-danger {
|
248
|
+
color: var(--danger-color) !important;
|
249
|
+
}
|
250
|
+
|
251
|
+
.super-settings-text-muted {
|
252
|
+
color: var(--muted-color) !important;
|
253
|
+
}
|
254
|
+
|
255
|
+
.super-settings-btn {
|
256
|
+
display: inline-block;
|
257
|
+
vertical-align: middle;
|
258
|
+
padding: 0.375rem 0.75rem;
|
259
|
+
border-radius: 0.375rem;
|
260
|
+
border: 1px solid #dcdcdc;
|
261
|
+
background-color:#f9f9f9;
|
262
|
+
text-decoration: none;
|
263
|
+
text-align: center;
|
264
|
+
font-family: Arial, sans-serif;
|
265
|
+
font-size: 1rem;
|
266
|
+
font-weight: 400;
|
267
|
+
line-height: 1.5;
|
268
|
+
user-select: none;
|
269
|
+
cursor: pointer;
|
270
|
+
transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out, box-shadow .15s ease-in-out;
|
271
|
+
}
|
272
|
+
|
273
|
+
.super-settings-btn:hover:not(:disabled) {
|
274
|
+
background-color:#e9e9e9;
|
275
|
+
}
|
276
|
+
.super-settings-btn:active {
|
277
|
+
position:relative;
|
278
|
+
top:1px;
|
279
|
+
}
|
280
|
+
.super-settings-btn:disabled {
|
281
|
+
opacity: 0.8;
|
282
|
+
cursor: not-allowed;
|
283
|
+
}
|
284
|
+
|
285
|
+
.super-settings-btn-primary {
|
286
|
+
background-color: var(--primary-color);
|
287
|
+
border-color: var(--primary-border-color);
|
288
|
+
color: var(--primary-contrast-color);
|
289
|
+
}
|
290
|
+
.super-settings-btn-primary:hover:not(:disabled) {
|
291
|
+
background-color: var(--primary-hover-color);
|
292
|
+
}
|
293
|
+
|
294
|
+
.super-settings-btn-primary:disabled {
|
295
|
+
opacity: 0.5;
|
127
296
|
}
|
@@ -10,31 +10,38 @@ module SuperSettings
|
|
10
10
|
# @param layout [String, Symbol] path to an ERB template to use as the layout around the application UI. You can
|
11
11
|
# pass the symbol +:default+ to use the default layout that ships with the gem.
|
12
12
|
# @param add_to_head [String] HTML code to add to the <head> element on the page.
|
13
|
-
|
13
|
+
# @param api_base_url [String] the base URL for the REST API.
|
14
|
+
# @param color_scheme [Symbol] whether to use dark mode for the application UI. If +nil+, the user's system
|
15
|
+
# preference will be used.
|
16
|
+
def initialize(layout: nil, add_to_head: nil, api_base_url: nil, color_scheme: nil)
|
14
17
|
if layout
|
15
18
|
layout = File.expand_path(File.join("application", "layout.html.erb"), __dir__) if layout == :default
|
16
|
-
@layout = ERB.new(File.read(layout))
|
19
|
+
@layout = ERB.new(File.read(layout)) if layout
|
17
20
|
@add_to_head = add_to_head
|
21
|
+
else
|
22
|
+
@layout = nil
|
23
|
+
@add_to_head = nil
|
18
24
|
end
|
25
|
+
|
26
|
+
@api_base_url = api_base_url
|
27
|
+
@color_scheme = color_scheme&.to_sym
|
19
28
|
end
|
20
29
|
|
21
|
-
# Render the
|
30
|
+
# Render the web UI application HTML.
|
22
31
|
#
|
23
32
|
# @return [void]
|
24
|
-
def render
|
25
|
-
template = ERB.new(File.read(File.expand_path(File.join("application",
|
33
|
+
def render
|
34
|
+
template = ERB.new(File.read(File.expand_path(File.join("application", "index.html.erb"), __dir__)))
|
26
35
|
html = template.result(binding)
|
27
|
-
if @layout
|
28
|
-
|
29
|
-
|
30
|
-
html
|
31
|
-
end
|
36
|
+
html = render_layout { html } if @layout
|
37
|
+
html = html.html_safe if html.respond_to?(:html_safe)
|
38
|
+
html
|
32
39
|
end
|
33
40
|
|
34
41
|
private
|
35
42
|
|
36
43
|
def render_layout
|
37
|
-
@layout
|
44
|
+
@layout&.result(binding)
|
38
45
|
end
|
39
46
|
end
|
40
47
|
end
|
@@ -4,20 +4,13 @@ module SuperSettings
|
|
4
4
|
# Interface to expose mass setting attributes on an object. Setting attributes with a
|
5
5
|
# hash will simply call the attribute writers for each key in the hash.
|
6
6
|
module Attributes
|
7
|
-
class UnknownAttributeError < StandardError
|
8
|
-
end
|
9
|
-
|
10
7
|
def initialize(attributes = nil)
|
11
8
|
self.attributes = attributes if attributes
|
12
9
|
end
|
13
10
|
|
14
11
|
def attributes=(values)
|
15
12
|
values.each do |name, value|
|
16
|
-
if respond_to?(:"#{name}=", true)
|
17
|
-
send(:"#{name}=", value)
|
18
|
-
else
|
19
|
-
raise UnknownAttributeError.new("unknown attribute #{name.to_s.inspect} for #{self.class}")
|
20
|
-
end
|
13
|
+
send(:"#{name}=", value) if respond_to?(:"#{name}=", true)
|
21
14
|
end
|
22
15
|
end
|
23
16
|
end
|
@@ -25,6 +25,7 @@ module SuperSettings
|
|
25
25
|
def initialize
|
26
26
|
@superclass = nil
|
27
27
|
@web_ui_enabled = true
|
28
|
+
@color_scheme = false
|
28
29
|
@changed_by_block = nil
|
29
30
|
end
|
30
31
|
|
@@ -63,6 +64,14 @@ module SuperSettings
|
|
63
64
|
!!@web_ui_enabled
|
64
65
|
end
|
65
66
|
|
67
|
+
# Set dark mode for the web UI. Possible values are :light, :dark, or :system.
|
68
|
+
# The default value is :light.
|
69
|
+
attr_writer :color_scheme
|
70
|
+
|
71
|
+
def color_scheme
|
72
|
+
(@color_scheme ||= :light).to_sym
|
73
|
+
end
|
74
|
+
|
66
75
|
# Enhance the controller. You can define methods or call controller class methods like
|
67
76
|
# +before_action+, etc. in the block. These will be applied to the engine controller.
|
68
77
|
# This is essentially the same a monkeypatching the controller class.
|
@@ -19,7 +19,7 @@ module SuperSettings
|
|
19
19
|
|
20
20
|
# Render the HTML application for managing settings.
|
21
21
|
def root
|
22
|
-
html = SuperSettings::Application.new.render
|
22
|
+
html = SuperSettings::Application.new.render
|
23
23
|
render html: html.html_safe, layout: true
|
24
24
|
end
|
25
25
|
|
@@ -40,7 +40,7 @@ module SuperSettings
|
|
40
40
|
|
41
41
|
# API endpoint for updating settings. See SuperSettings::RestAPI for details.
|
42
42
|
def update
|
43
|
-
changed_by =
|
43
|
+
changed_by = SuperSettings.configuration.controller.changed_by(self)
|
44
44
|
result = SuperSettings::RestAPI.update(params[:settings], changed_by)
|
45
45
|
if result[:success]
|
46
46
|
render json: result
|
@@ -27,7 +27,7 @@ module SuperSettings
|
|
27
27
|
|
28
28
|
config.after_initialize do
|
29
29
|
# Call the deferred initialization block.
|
30
|
-
configuration =
|
30
|
+
configuration = SuperSettings.configuration
|
31
31
|
configuration.call
|
32
32
|
|
33
33
|
SuperSettings.refresh_interval = configuration.refresh_interval unless configuration.refresh_interval.nil?
|
@@ -27,7 +27,7 @@ module SuperSettings
|
|
27
27
|
def changed_by_display
|
28
28
|
return changed_by if changed_by.nil?
|
29
29
|
|
30
|
-
display_proc =
|
30
|
+
display_proc = SuperSettings.configuration.model.changed_by_display
|
31
31
|
if display_proc && !changed_by.nil?
|
32
32
|
display_proc.call(changed_by) || changed_by
|
33
33
|
else
|
@@ -0,0 +1,165 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "net/http"
|
4
|
+
require "uri"
|
5
|
+
|
6
|
+
module SuperSettings
|
7
|
+
# This is a simple HTTP client that is used to communicate with the REST API. It
|
8
|
+
# will keep the connection alive and reuse it on subsequent requests.
|
9
|
+
class HttpClient
|
10
|
+
DEFAULT_HEADERS = {"Accept" => "application/json"}.freeze
|
11
|
+
DEFAULT_TIMEOUT = 5.0
|
12
|
+
KEEP_ALIVE_TIMEOUT = 60
|
13
|
+
|
14
|
+
class Error < StandardError
|
15
|
+
end
|
16
|
+
|
17
|
+
class NotFoundError < Error
|
18
|
+
end
|
19
|
+
|
20
|
+
class InvalidRecordError < Error
|
21
|
+
attr_reader :errors
|
22
|
+
|
23
|
+
def initialize(message, errors:)
|
24
|
+
super(message)
|
25
|
+
@errors = errors
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(base_url, headers: nil, params: nil, timeout: nil, user: nil, password: nil)
|
30
|
+
base_url = "#{base_url}/" unless base_url.end_with?("/")
|
31
|
+
@base_uri = URI(base_url)
|
32
|
+
@base_uri.query = query_string(params) if params
|
33
|
+
@headers = headers ? DEFAULT_HEADERS.merge(headers) : DEFAULT_HEADERS
|
34
|
+
@timeout = timeout || DEFAULT_TIMEOUT
|
35
|
+
@user = user
|
36
|
+
@password = password
|
37
|
+
@mutex = Mutex.new
|
38
|
+
@connections = []
|
39
|
+
end
|
40
|
+
|
41
|
+
def get(path, params = nil)
|
42
|
+
request = Net::HTTP::Get.new(request_uri(path, params))
|
43
|
+
send_request(request)
|
44
|
+
end
|
45
|
+
|
46
|
+
def post(path, params = nil)
|
47
|
+
request = Net::HTTP::Post.new(request_uri(path))
|
48
|
+
request.body = JSON.dump(params) if params
|
49
|
+
send_request(request)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def send_request(request)
|
55
|
+
set_headers(request)
|
56
|
+
response_payload = nil
|
57
|
+
attempts = 0
|
58
|
+
|
59
|
+
with_connection do |http|
|
60
|
+
http.start unless http.started?
|
61
|
+
response = http.request(request)
|
62
|
+
|
63
|
+
begin
|
64
|
+
response.value # raises exception unless response is a success
|
65
|
+
response_payload = JSON.parse(response.body)
|
66
|
+
rescue Net::ProtocolError
|
67
|
+
if [404, 410].include?(response.code.to_i)
|
68
|
+
raise NotFoundError.new("#{response.code} #{response.message}")
|
69
|
+
elsif response.code.to_i == 422
|
70
|
+
raise InvalidRecordError.new("#{response.code} #{response.message}", errors: JSON.parse(response.body)["errors"])
|
71
|
+
else
|
72
|
+
raise Error.new("#{response.code} #{response.message}")
|
73
|
+
end
|
74
|
+
rescue JSON::JSONError => e
|
75
|
+
raise Error.new(e.message)
|
76
|
+
end
|
77
|
+
rescue IOError, Errno::ECONNRESET => connection_error
|
78
|
+
attempts += 1
|
79
|
+
retry if attempts <= 1
|
80
|
+
raise connection_error
|
81
|
+
end
|
82
|
+
|
83
|
+
response_payload
|
84
|
+
end
|
85
|
+
|
86
|
+
def with_connection(&block)
|
87
|
+
http = pop_connection
|
88
|
+
begin
|
89
|
+
response = yield(http)
|
90
|
+
return_connection(http)
|
91
|
+
response
|
92
|
+
rescue => e
|
93
|
+
begin
|
94
|
+
http.finish if http.started?
|
95
|
+
rescue IOError
|
96
|
+
end
|
97
|
+
raise e
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def pop_connection
|
102
|
+
http = nil
|
103
|
+
@mutex.synchronize do
|
104
|
+
http = @connections.pop
|
105
|
+
end
|
106
|
+
http = nil unless http&.started?
|
107
|
+
http ||= new_connection
|
108
|
+
http
|
109
|
+
end
|
110
|
+
|
111
|
+
def return_connection(http)
|
112
|
+
@mutex.synchronize do
|
113
|
+
if @connections.empty?
|
114
|
+
@connections.push(http)
|
115
|
+
http = nil
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
if http
|
120
|
+
begin
|
121
|
+
http.finish if http.started?
|
122
|
+
rescue IOError
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def new_connection
|
128
|
+
http = Net::HTTP.new(@base_uri.host, @base_uri.port || @base_uri.inferred_port)
|
129
|
+
http.use_ssl = @base_uri.scheme == "https"
|
130
|
+
http.open_timeout = @timeout
|
131
|
+
http.read_timeout = @timeout
|
132
|
+
http.write_timeout = @timeout
|
133
|
+
http.keep_alive_timeout = KEEP_ALIVE_TIMEOUT
|
134
|
+
http
|
135
|
+
end
|
136
|
+
|
137
|
+
def set_headers(request)
|
138
|
+
@headers.each do |name, value|
|
139
|
+
name = name.to_s
|
140
|
+
values = Array(value)
|
141
|
+
request[name] = values[0].to_s
|
142
|
+
values[1, values.length].each do |val|
|
143
|
+
request.add_field(name, val.to_s)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def request_uri(path, params = nil)
|
149
|
+
uri = URI.join(@base_uri, path.delete_prefix("/"))
|
150
|
+
if (params && !params.empty?) || (@base_uri.query && !@base_uri.query.empty?)
|
151
|
+
uri.query = [uri.query, query_string(params)].join("&")
|
152
|
+
end
|
153
|
+
uri
|
154
|
+
end
|
155
|
+
|
156
|
+
def query_string(params)
|
157
|
+
q = []
|
158
|
+
q << @base_uri.query unless @base_uri.query.to_s.empty?
|
159
|
+
params&.each do |name, value|
|
160
|
+
q << "#{URI.encode_www_form_component(name.to_s)}=#{URI.encode_www_form_component(value.to_s)}"
|
161
|
+
end
|
162
|
+
q.join("&")
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -122,7 +122,7 @@ module SuperSettings
|
|
122
122
|
#
|
123
123
|
# @return [Boolean]
|
124
124
|
def web_ui_enabled?
|
125
|
-
|
125
|
+
SuperSettings.configuration.controller.web_ui_enabled?
|
126
126
|
end
|
127
127
|
|
128
128
|
private
|
@@ -159,7 +159,7 @@ module SuperSettings
|
|
159
159
|
|
160
160
|
def handle_root_request(request)
|
161
161
|
response = check_authorization(request, write_required: true) do |user|
|
162
|
-
[200, {"content-type" => "text/html; charset=utf-8", "cache-control" => "no-cache"}, [Application.new(:default, add_to_head(request)
|
162
|
+
[200, {"content-type" => "text/html; charset=utf-8", "cache-control" => "no-cache"}, [Application.new(layout: :default, add_to_head: add_to_head(request), color_scheme: SuperSettings.configuration.controller.color_scheme).render]]
|
163
163
|
end
|
164
164
|
|
165
165
|
if [401, 403].include?(response.first)
|
@@ -236,7 +236,7 @@ module SuperSettings
|
|
236
236
|
|
237
237
|
def post_params(request)
|
238
238
|
if request.content_type.to_s.match?(/\Aapplication\/json/i) && request.body
|
239
|
-
request.params.merge(JSON.parse(request.body.
|
239
|
+
request.params.merge(JSON.parse(request.body.read))
|
240
240
|
else
|
241
241
|
request.params
|
242
242
|
end
|
@@ -23,7 +23,7 @@ module SuperSettings
|
|
23
23
|
# ...
|
24
24
|
# ]
|
25
25
|
def index
|
26
|
-
settings = Setting.active.sort_by(&:key)
|
26
|
+
settings = Setting.active.reject(&:deleted?).sort_by(&:key)
|
27
27
|
{settings: settings.collect(&:as_json)}
|
28
28
|
end
|
29
29
|
|
@@ -46,7 +46,8 @@ module SuperSettings
|
|
46
46
|
# updated_at: iso8601 string
|
47
47
|
# }
|
48
48
|
def show(key)
|
49
|
-
Setting.find_by_key(key)
|
49
|
+
setting = Setting.find_by_key(key)
|
50
|
+
setting.as_json if setting && !setting.deleted?
|
50
51
|
end
|
51
52
|
|
52
53
|
# The update operation uses a transaction to atomically update all settings.
|
@@ -156,7 +157,7 @@ module SuperSettings
|
|
156
157
|
#
|
157
158
|
# @example
|
158
159
|
# GET /last_updated_at
|
159
|
-
#
|
160
|
+
#
|
160
161
|
# The response payload is:
|
161
162
|
# {
|
162
163
|
# last_updated_at: iso8601 string
|
@@ -188,7 +189,7 @@ module SuperSettings
|
|
188
189
|
# ]
|
189
190
|
def updated_since(time)
|
190
191
|
time = Coerce.time(time)
|
191
|
-
settings = Setting.updated_since(time)
|
192
|
+
settings = Setting.updated_since(time).reject(&:deleted?)
|
192
193
|
{settings: settings.collect(&:as_json)}
|
193
194
|
end
|
194
195
|
end
|