fluentd-ui 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of fluentd-ui might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/ChangeLog.md +5 -0
- data/Gemfile.lock +1 -1
- data/app/controllers/api/settings_controller.rb +46 -16
- data/app/controllers/concerns/setting_concern.rb +1 -1
- data/app/controllers/fluentd/settings/filter_grep_controller.rb +9 -0
- data/app/controllers/fluentd/settings/filter_parser_controller.rb +9 -0
- data/app/controllers/fluentd/settings/filter_record_transformer_controller.rb +14 -0
- data/app/controllers/fluentd/settings/filter_stdout_controller.rb +9 -0
- data/app/javascript/packs/filter_grep_setting.js +39 -0
- data/app/javascript/packs/grep_container.js +53 -0
- data/app/javascript/packs/grep_pattern.js +43 -0
- data/app/javascript/packs/owned_plugin_form.js +9 -7
- data/app/javascript/packs/settings.js +34 -16
- data/app/models/concerns/fluentd/setting/configurable.rb +1 -0
- data/app/models/concerns/fluentd/setting/label.rb +11 -0
- data/app/models/concerns/fluentd/setting/plugin.rb +2 -0
- data/app/models/concerns/fluentd/setting/plugin_config.rb +34 -4
- data/app/models/concerns/fluentd/setting/section_config.rb +10 -2
- data/app/models/fluentd/agent/common.rb +97 -0
- data/app/models/fluentd/setting/config.rb +71 -0
- data/app/models/fluentd/setting/filter_grep.rb +38 -0
- data/app/models/fluentd/setting/filter_parser.rb +34 -0
- data/app/models/fluentd/setting/filter_record_transformer.rb +27 -0
- data/app/models/fluentd/setting/filter_stdout.rb +34 -0
- data/app/models/fluentd/setting/in_forward.rb +1 -0
- data/app/models/fluentd/setting/in_http.rb +1 -1
- data/app/models/fluentd/setting/in_monitor_agent.rb +1 -1
- data/app/models/fluentd/setting/in_syslog.rb +1 -1
- data/app/models/fluentd/setting/out_elasticsearch.rb +1 -0
- data/app/models/fluentd/setting/out_forward.rb +1 -1
- data/app/models/fluentd/setting/out_mongo.rb +1 -0
- data/app/models/fluentd/setting/out_s3.rb +1 -0
- data/app/models/fluentd/setting/out_stdout.rb +1 -1
- data/app/models/fluentd/setting/out_tdlog.rb +1 -1
- data/app/views/api/settings/_element.json.jbuilder +3 -1
- data/app/views/api/settings/index.json.jbuilder +10 -2
- data/app/views/api/settings/show.json.jbuilder +1 -1
- data/app/views/fluentd/settings/filter_grep/_form.html.haml +18 -0
- data/app/views/fluentd/settings/filter_record_transformer/_form.html.haml +20 -0
- data/app/views/fluentd/settings/in_tail/_form.html.haml +1 -0
- data/app/views/fluentd/settings/source_and_output.html.haml +46 -14
- data/app/views/shared/vue/_filter_grep_setting.html.haml +19 -0
- data/app/views/shared/vue/_grep_container.html.haml +32 -0
- data/app/views/shared/vue/_grep_pattern.html.haml +24 -0
- data/app/views/shared/vue/_setting.html.erb +2 -2
- data/config/locales/translation_en.yml +39 -10
- data/config/locales/translation_ja.yml +39 -10
- data/config/routes.rb +6 -0
- data/lib/fluentd-ui/version.rb +1 -1
- data/test/factories/fluentd.rb +4 -4
- data/test/factories/plugins.rb +2 -2
- data/test/factories/user.rb +2 -2
- data/test/fixtures/config/label.conf +15 -0
- data/test/fixtures/config/multi-label.conf +40 -0
- data/test/fixtures/config/simple.conf +12 -0
- data/test/models/fluentd/agent_common_test.rb +288 -0
- data/test/models/fluentd/setting/config_test.rb +98 -0
- data/test/support/configurable_daemon_settings.rb +1 -1
- data/test/system/fluentd/setting/filter_grep_test.rb +96 -0
- data/test/system/fluentd/setting/filter_parser_test.rb +13 -0
- data/test/system/fluentd/setting/filter_record_transformer_test.rb +30 -0
- data/test/system/fluentd/setting/filter_stdout_test.rb +13 -0
- data/test/system/source_and_output_test.rb +168 -19
- data/test/test_helper.rb +4 -0
- metadata +98 -55
- data/.rspec +0 -2
@@ -0,0 +1,98 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class FluentdConfigTest < ActiveSupport::TestCase
|
4
|
+
def create_config(fixture_name)
|
5
|
+
::Fluentd::Setting::Config.new(fixture_path(fixture_name))
|
6
|
+
end
|
7
|
+
|
8
|
+
sub_test_case "#delete_element" do
|
9
|
+
test "delete a source" do
|
10
|
+
config = create_config("config/simple.conf")
|
11
|
+
element = config.elements(name: "source").first
|
12
|
+
config.delete_element("source", nil, element)
|
13
|
+
assert_equal(config.formatted.strip, <<CONFIG.chomp)
|
14
|
+
<filter dummy>
|
15
|
+
@type stdout
|
16
|
+
</filter>
|
17
|
+
|
18
|
+
<match dummy>
|
19
|
+
@type stdout
|
20
|
+
</match>
|
21
|
+
CONFIG
|
22
|
+
end
|
23
|
+
|
24
|
+
test "delete a filter" do
|
25
|
+
config = create_config("config/simple.conf")
|
26
|
+
element = config.elements(name: "filter").first
|
27
|
+
config.delete_element("filter", nil, element)
|
28
|
+
assert_equal(config.formatted.strip, <<CONFIG.chomp)
|
29
|
+
<source>
|
30
|
+
@type dummy
|
31
|
+
tag dummy
|
32
|
+
</source>
|
33
|
+
|
34
|
+
<match dummy>
|
35
|
+
@type stdout
|
36
|
+
</match>
|
37
|
+
CONFIG
|
38
|
+
end
|
39
|
+
|
40
|
+
test "delete a match" do
|
41
|
+
config = create_config("config/simple.conf")
|
42
|
+
element = config.elements(name: "match").first
|
43
|
+
config.delete_element("match", nil, element)
|
44
|
+
assert_equal(config.formatted.strip, <<CONFIG.chomp)
|
45
|
+
<source>
|
46
|
+
@type dummy
|
47
|
+
tag dummy
|
48
|
+
</source>
|
49
|
+
|
50
|
+
<filter dummy>
|
51
|
+
@type stdout
|
52
|
+
</filter>
|
53
|
+
CONFIG
|
54
|
+
end
|
55
|
+
|
56
|
+
test "delete all elements under the label" do
|
57
|
+
config = create_config("config/multi-label.conf")
|
58
|
+
input = config.elements(name: "label", arg: "@INPUT").first
|
59
|
+
input.elements.each do |element|
|
60
|
+
config.delete_element("label", "@INPUT", element)
|
61
|
+
end
|
62
|
+
assert_equal(config.formatted.strip, <<CONFIG.chomp)
|
63
|
+
<source>
|
64
|
+
@type dummy
|
65
|
+
tag dummy1
|
66
|
+
@label @INPUT
|
67
|
+
</source>
|
68
|
+
|
69
|
+
<source>
|
70
|
+
@type dummy
|
71
|
+
tag dummy2
|
72
|
+
@label @INPUT
|
73
|
+
</source>
|
74
|
+
|
75
|
+
<label @MAIN>
|
76
|
+
<filter dummy1>
|
77
|
+
@type stdout
|
78
|
+
</filter>
|
79
|
+
<filter dummy2>
|
80
|
+
@type stdout
|
81
|
+
</filter>
|
82
|
+
<match dummy1>
|
83
|
+
@type stdout
|
84
|
+
</match>
|
85
|
+
<match dummy2>
|
86
|
+
@type stdout
|
87
|
+
</match>
|
88
|
+
</label>
|
89
|
+
CONFIG
|
90
|
+
end
|
91
|
+
|
92
|
+
test "cannot delete specified element" do
|
93
|
+
config = create_config("config/simple.conf")
|
94
|
+
element = ::Fluent::Config::Element.new("source", nil, { :@type => "dummy", :tag => "dummy" }, [])
|
95
|
+
assert_nil(config.delete_element("source", nil, element))
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -15,7 +15,7 @@ module ConfigurableDaemonSettings
|
|
15
15
|
end
|
16
16
|
visit(__send__("daemon_setting_#{@type}_path"))
|
17
17
|
within("form") do
|
18
|
-
fill_in(@form_name.
|
18
|
+
fill_in(@form_name.humanize, with: @form_value)
|
19
19
|
end
|
20
20
|
click_button(I18n.t("fluentd.common.finish"))
|
21
21
|
assert do
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require "application_system_test_case"
|
2
|
+
|
3
|
+
class FilterGrepTest < ApplicationSystemTestCase
|
4
|
+
setup do
|
5
|
+
login_with(FactoryBot.build(:user))
|
6
|
+
@daemon = stub_daemon
|
7
|
+
end
|
8
|
+
|
9
|
+
test "show form" do
|
10
|
+
visit(daemon_setting_filter_grep_path)
|
11
|
+
assert do
|
12
|
+
page.has_css?("input[name=\"setting[label]\"]")
|
13
|
+
end
|
14
|
+
assert do
|
15
|
+
page.has_css?("input#setting_and_0_regexp_0__key")
|
16
|
+
end
|
17
|
+
assert do
|
18
|
+
page.has_css?("input#setting_and_0_regexp_0__pattern")
|
19
|
+
end
|
20
|
+
assert do
|
21
|
+
page.has_css?("input#setting_or_0_regexp_0__key")
|
22
|
+
end
|
23
|
+
assert do
|
24
|
+
page.has_css?("input#setting_or_0_regexp_0__pattern")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
test "append and" do
|
29
|
+
visit(daemon_setting_filter_grep_path)
|
30
|
+
first(".card-header .btn .fa-plus").click
|
31
|
+
assert do
|
32
|
+
page.has_css?("input#setting_and_1_regexp_0__key")
|
33
|
+
end
|
34
|
+
assert do
|
35
|
+
page.has_css?("input#setting_and_1_regexp_0__pattern")
|
36
|
+
end
|
37
|
+
first(".card-header .btn .fa-minus").click
|
38
|
+
assert do
|
39
|
+
!page.has_css?("input#setting_and_1_regexp_0__key")
|
40
|
+
end
|
41
|
+
assert do
|
42
|
+
!page.has_css?("input#setting_and_1_regexp_0__pattern")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
test "append regexp" do
|
47
|
+
visit(daemon_setting_filter_grep_path)
|
48
|
+
first(".card-body .btn .fa-plus").click
|
49
|
+
assert do
|
50
|
+
page.has_css?("input#setting_and_0_regexp_1__key")
|
51
|
+
end
|
52
|
+
assert do
|
53
|
+
page.has_css?("input#setting_and_0_regexp_1__pattern")
|
54
|
+
end
|
55
|
+
first(".card-body .btn .fa-minus").click
|
56
|
+
assert do
|
57
|
+
!page.has_css?("input#setting_and_0_regexp_1__key")
|
58
|
+
end
|
59
|
+
assert do
|
60
|
+
!page.has_css?("input#setting_and_0_regexp_1__pattern")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
test "toggle regexp/exclude" do
|
65
|
+
visit(daemon_setting_filter_grep_path)
|
66
|
+
assert_equal(first(".card-body label").text, "Regexp")
|
67
|
+
first('input[name="setting[and[0]][grep_type]"][value="exclude"]').click()
|
68
|
+
assert_equal(first(".card-body label").text, "Exclude")
|
69
|
+
assert do
|
70
|
+
!page.has_css?("input#setting_and_0_regexp_0__key")
|
71
|
+
end
|
72
|
+
assert do
|
73
|
+
!page.has_css?("input#setting_and_0_regexp_0__pattern")
|
74
|
+
end
|
75
|
+
assert do
|
76
|
+
page.has_css?("input#setting_and_0_exclude_0__key")
|
77
|
+
end
|
78
|
+
assert do
|
79
|
+
page.has_css?("input#setting_and_0_exclude_0__pattern")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
test "update config" do
|
84
|
+
visit(daemon_setting_filter_grep_path)
|
85
|
+
within("form") do
|
86
|
+
fill_in("setting_label", with: "@INPUT")
|
87
|
+
fill_in("setting_pattern", with: "pattern")
|
88
|
+
fill_in("setting_and_0_regexp_0__key", with: "message")
|
89
|
+
fill_in("setting_and_0_regexp_0__pattern", with: "pattern")
|
90
|
+
end
|
91
|
+
click_button(I18n.t("fluentd.common.finish"))
|
92
|
+
assert do
|
93
|
+
@daemon.agent.config.include?("@INPUT")
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "application_system_test_case"
|
2
|
+
|
3
|
+
class FilterParserTest < ApplicationSystemTestCase
|
4
|
+
include ConfigurableDaemonSettings
|
5
|
+
|
6
|
+
setup do
|
7
|
+
login_with(FactoryBot.build(:user))
|
8
|
+
@type = "filter_parser"
|
9
|
+
@form_name = "key_name"
|
10
|
+
@form_value = "message"
|
11
|
+
@daemon = stub_daemon
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "application_system_test_case"
|
2
|
+
|
3
|
+
class FilterRecordTransformerTest < ApplicationSystemTestCase
|
4
|
+
setup do
|
5
|
+
login_with(FactoryBot.build(:user))
|
6
|
+
@daemon = stub_daemon
|
7
|
+
end
|
8
|
+
|
9
|
+
test "show form" do
|
10
|
+
visit(daemon_setting_filter_record_transformer_path)
|
11
|
+
assert do
|
12
|
+
page.has_css?("textarea[name=\"setting[record]\"]")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
test "update config" do
|
17
|
+
value = "key value"
|
18
|
+
assert do
|
19
|
+
!@daemon.agent.config.include?(value)
|
20
|
+
end
|
21
|
+
visit(daemon_setting_filter_record_transformer_path)
|
22
|
+
within("form") do
|
23
|
+
fill_in("Record", with: value)
|
24
|
+
end
|
25
|
+
click_button(I18n.t("fluentd.common.finish"))
|
26
|
+
assert do
|
27
|
+
@daemon.agent.config.include?(value)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "application_system_test_case"
|
2
|
+
|
3
|
+
class FilterStdoutTest < ApplicationSystemTestCase
|
4
|
+
include ConfigurableDaemonSettings
|
5
|
+
|
6
|
+
setup do
|
7
|
+
login_with(FactoryBot.build(:user))
|
8
|
+
@type = "filter_stdout"
|
9
|
+
@form_name = "pattern"
|
10
|
+
@form_value = "stdout.**"
|
11
|
+
@daemon = stub_daemon
|
12
|
+
end
|
13
|
+
end
|
@@ -50,14 +50,21 @@ class SourceAndOutputTest < ApplicationSystemTestCase
|
|
50
50
|
end
|
51
51
|
|
52
52
|
test "elements" do
|
53
|
-
|
54
|
-
|
53
|
+
within(".input .card") do
|
54
|
+
assert do
|
55
|
+
!has_content?(I18n.t("fluentd.settings.source_and_output.setting_empty"))
|
56
|
+
end
|
55
57
|
end
|
56
|
-
|
57
|
-
|
58
|
+
assert do
|
59
|
+
has_css?(".input .card .card-header")
|
58
60
|
end
|
59
61
|
assert do
|
60
|
-
|
62
|
+
has_css?(".output .card .card-header")
|
63
|
+
end
|
64
|
+
within(".filter .empty") do
|
65
|
+
assert do
|
66
|
+
has_content?(I18n.t("fluentd.settings.source_and_output.setting_empty"))
|
67
|
+
end
|
61
68
|
end
|
62
69
|
end
|
63
70
|
|
@@ -79,20 +86,9 @@ class SourceAndOutputTest < ApplicationSystemTestCase
|
|
79
86
|
end
|
80
87
|
|
81
88
|
test "display plugin name" do
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
within ".output" do
|
89
|
-
assert do
|
90
|
-
page.has_content?("stdout")
|
91
|
-
end
|
92
|
-
assert do
|
93
|
-
page.has_content?("s3")
|
94
|
-
end
|
95
|
-
end
|
89
|
+
assert_equal(first(".input .card .card-header").text, "forward")
|
90
|
+
assert_equal(all(".output .card .card-header").map(&:text),
|
91
|
+
["stdout (debug.*)", "s3 (s3.*)"])
|
96
92
|
end
|
97
93
|
|
98
94
|
sub_test_case "edit, update, delete" do
|
@@ -181,4 +177,157 @@ class SourceAndOutputTest < ApplicationSystemTestCase
|
|
181
177
|
end
|
182
178
|
end
|
183
179
|
end
|
180
|
+
|
181
|
+
sub_test_case "filter" do
|
182
|
+
setup do
|
183
|
+
config = <<-CONFIG.strip_heredoc
|
184
|
+
<source>
|
185
|
+
@type dummy
|
186
|
+
tag debug.*
|
187
|
+
</source>
|
188
|
+
|
189
|
+
<filter debug.*>
|
190
|
+
@type stdout
|
191
|
+
</filter>
|
192
|
+
|
193
|
+
<match debug.*>
|
194
|
+
# http://docs.fluentd.org/articles/out_stdout
|
195
|
+
type stdout
|
196
|
+
</match>
|
197
|
+
CONFIG
|
198
|
+
@daemon.agent.config_write(config)
|
199
|
+
visit(source_and_output_daemon_setting_path)
|
200
|
+
end
|
201
|
+
|
202
|
+
test "elements" do
|
203
|
+
assert_equal(first(".filter .card .card-header").text, "stdout (debug.*)")
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
sub_test_case "label" do
|
208
|
+
setup do
|
209
|
+
@config = <<-CONFIG.strip_heredoc
|
210
|
+
<source>
|
211
|
+
@type dummy
|
212
|
+
tag debug.*
|
213
|
+
@label @INPUT
|
214
|
+
</source>
|
215
|
+
|
216
|
+
<label @INPUT>
|
217
|
+
<filter debug.*>
|
218
|
+
@type grep
|
219
|
+
<regexp>
|
220
|
+
key message
|
221
|
+
pattern /debug.+/
|
222
|
+
</regexp>
|
223
|
+
</filter>
|
224
|
+
|
225
|
+
<match debug.*>
|
226
|
+
@type stdout
|
227
|
+
</match>
|
228
|
+
</label>
|
229
|
+
CONFIG
|
230
|
+
@daemon.agent.config_write(@config)
|
231
|
+
visit(source_and_output_daemon_setting_path)
|
232
|
+
end
|
233
|
+
|
234
|
+
test "elements under @INPUT" do
|
235
|
+
assert do
|
236
|
+
all(".input h5").map(&:text).include?("@INPUT")
|
237
|
+
end
|
238
|
+
assert_equal(first(".input .card .card-header").text, "dummy")
|
239
|
+
assert do
|
240
|
+
all(".filter h5").map(&:text).include?("@INPUT")
|
241
|
+
end
|
242
|
+
assert_equal(first(".filter .card .card-header").text, "grep (debug.*)")
|
243
|
+
assert do
|
244
|
+
all(".output h5").map(&:text).include?("@INPUT")
|
245
|
+
end
|
246
|
+
assert_equal(first(".output .card .card-header").text, "stdout (debug.*)")
|
247
|
+
end
|
248
|
+
|
249
|
+
test "click delete button" do
|
250
|
+
assert do
|
251
|
+
find(".filter .card-header").click
|
252
|
+
end
|
253
|
+
page.accept_confirm do
|
254
|
+
find(".filter .btn", text: I18n.t("terms.destroy")).click
|
255
|
+
end
|
256
|
+
within(".filter .empty") do
|
257
|
+
assert do
|
258
|
+
has_content?(I18n.t("fluentd.settings.source_and_output.setting_empty"))
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
test "click edit and cancel" do
|
264
|
+
config = <<-CONFIG.strip_heredoc
|
265
|
+
<filter debug.*>
|
266
|
+
@type grep
|
267
|
+
<regexp>
|
268
|
+
key message
|
269
|
+
pattern /debug.+/
|
270
|
+
</regexp>
|
271
|
+
</filter>
|
272
|
+
CONFIG
|
273
|
+
new_config = <<-CONFIG.strip_heredoc
|
274
|
+
<filter debug.*>
|
275
|
+
@type grep
|
276
|
+
<regexp>
|
277
|
+
key message
|
278
|
+
pattern /debug2.+/
|
279
|
+
</regexp>
|
280
|
+
</filter>
|
281
|
+
CONFIG
|
282
|
+
assert do
|
283
|
+
find(".filter .card-header").click
|
284
|
+
end
|
285
|
+
find(".filter .btn", text: I18n.t("terms.edit")).click
|
286
|
+
original_contents = page.evaluate_script(%Q!document.querySelector(".CodeMirror").CodeMirror.getValue()!)
|
287
|
+
assert_equal(original_contents, config)
|
288
|
+
page.execute_script(<<-JS)
|
289
|
+
var cm = document.querySelector('.CodeMirror').CodeMirror;
|
290
|
+
cm.setValue(#{new_config.to_json});
|
291
|
+
JS
|
292
|
+
find(".filter .btn", text: I18n.t("terms.cancel")).click
|
293
|
+
contents = page.evaluate_script("document.querySelector('.filter pre').textContent")
|
294
|
+
assert_equal(contents, config)
|
295
|
+
assert_equal(@daemon.agent.config.strip, @config.strip)
|
296
|
+
end
|
297
|
+
|
298
|
+
test "click edit and save" do
|
299
|
+
config = <<-CONFIG.strip_heredoc
|
300
|
+
<filter debug.*>
|
301
|
+
@type grep
|
302
|
+
<regexp>
|
303
|
+
key message
|
304
|
+
pattern /debug.+/
|
305
|
+
</regexp>
|
306
|
+
</filter>
|
307
|
+
CONFIG
|
308
|
+
new_config = <<-CONFIG.strip_heredoc
|
309
|
+
<filter debug.*>
|
310
|
+
@type grep
|
311
|
+
<regexp>
|
312
|
+
key message
|
313
|
+
pattern /debug2.+/
|
314
|
+
</regexp>
|
315
|
+
</filter>
|
316
|
+
CONFIG
|
317
|
+
assert do
|
318
|
+
find(".filter .card-header").click
|
319
|
+
end
|
320
|
+
find(".filter .btn", text: I18n.t("terms.edit")).click
|
321
|
+
original_contents = page.evaluate_script(%Q!document.querySelector(".CodeMirror").CodeMirror.getValue()!)
|
322
|
+
assert_equal(original_contents, config)
|
323
|
+
page.execute_script(<<-JS)
|
324
|
+
var cm = document.querySelector('.CodeMirror').CodeMirror;
|
325
|
+
cm.setValue(#{new_config.to_json});
|
326
|
+
JS
|
327
|
+
find(".filter .btn", text: I18n.t("terms.save")).click
|
328
|
+
sleep(1)
|
329
|
+
contents = page.evaluate_script("document.querySelector('.filter pre').textContent")
|
330
|
+
assert_equal(new_config, contents)
|
331
|
+
end
|
332
|
+
end
|
184
333
|
end
|