reel-eye 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +32 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +170 -0
- data/Rakefile +20 -0
- data/bin/eye +322 -0
- data/bin/loader_eye +58 -0
- data/examples/notify.eye +18 -0
- data/examples/process_thin.rb +29 -0
- data/examples/processes/em.rb +57 -0
- data/examples/processes/forking.rb +20 -0
- data/examples/processes/sample.rb +144 -0
- data/examples/processes/thin.ru +12 -0
- data/examples/puma.eye +34 -0
- data/examples/rbenv.eye +11 -0
- data/examples/sidekiq.eye +23 -0
- data/examples/test.eye +81 -0
- data/examples/thin-farm.eye +29 -0
- data/examples/unicorn.eye +31 -0
- data/eye.gemspec +42 -0
- data/lib/eye.rb +28 -0
- data/lib/eye/application.rb +74 -0
- data/lib/eye/checker.rb +138 -0
- data/lib/eye/checker/cpu.rb +27 -0
- data/lib/eye/checker/file_ctime.rb +25 -0
- data/lib/eye/checker/file_size.rb +34 -0
- data/lib/eye/checker/http.rb +98 -0
- data/lib/eye/checker/memory.rb +27 -0
- data/lib/eye/checker/socket.rb +152 -0
- data/lib/eye/child_process.rb +101 -0
- data/lib/eye/client.rb +32 -0
- data/lib/eye/config.rb +88 -0
- data/lib/eye/control.rb +2 -0
- data/lib/eye/controller.rb +53 -0
- data/lib/eye/controller/commands.rb +73 -0
- data/lib/eye/controller/helpers.rb +61 -0
- data/lib/eye/controller/load.rb +214 -0
- data/lib/eye/controller/options.rb +48 -0
- data/lib/eye/controller/send_command.rb +115 -0
- data/lib/eye/controller/show_history.rb +62 -0
- data/lib/eye/controller/status.rb +131 -0
- data/lib/eye/dsl.rb +48 -0
- data/lib/eye/dsl/application_opts.rb +33 -0
- data/lib/eye/dsl/chain.rb +12 -0
- data/lib/eye/dsl/child_process_opts.rb +8 -0
- data/lib/eye/dsl/config_opts.rb +48 -0
- data/lib/eye/dsl/group_opts.rb +27 -0
- data/lib/eye/dsl/helpers.rb +12 -0
- data/lib/eye/dsl/main.rb +40 -0
- data/lib/eye/dsl/opts.rb +140 -0
- data/lib/eye/dsl/process_opts.rb +21 -0
- data/lib/eye/dsl/pure_opts.rb +110 -0
- data/lib/eye/dsl/validation.rb +59 -0
- data/lib/eye/group.rb +134 -0
- data/lib/eye/group/chain.rb +81 -0
- data/lib/eye/http.rb +31 -0
- data/lib/eye/http/router.rb +25 -0
- data/lib/eye/loader.rb +23 -0
- data/lib/eye/logger.rb +80 -0
- data/lib/eye/notify.rb +86 -0
- data/lib/eye/notify/jabber.rb +30 -0
- data/lib/eye/notify/mail.rb +44 -0
- data/lib/eye/process.rb +86 -0
- data/lib/eye/process/child.rb +58 -0
- data/lib/eye/process/commands.rb +256 -0
- data/lib/eye/process/config.rb +70 -0
- data/lib/eye/process/controller.rb +76 -0
- data/lib/eye/process/data.rb +47 -0
- data/lib/eye/process/monitor.rb +95 -0
- data/lib/eye/process/notify.rb +32 -0
- data/lib/eye/process/scheduler.rb +78 -0
- data/lib/eye/process/states.rb +86 -0
- data/lib/eye/process/states_history.rb +66 -0
- data/lib/eye/process/system.rb +97 -0
- data/lib/eye/process/trigger.rb +54 -0
- data/lib/eye/process/validate.rb +23 -0
- data/lib/eye/process/watchers.rb +69 -0
- data/lib/eye/reason.rb +20 -0
- data/lib/eye/server.rb +52 -0
- data/lib/eye/settings.rb +46 -0
- data/lib/eye/system.rb +154 -0
- data/lib/eye/system_resources.rb +86 -0
- data/lib/eye/trigger.rb +53 -0
- data/lib/eye/trigger/flapping.rb +28 -0
- data/lib/eye/utils.rb +14 -0
- data/lib/eye/utils/alive_array.rb +31 -0
- data/lib/eye/utils/celluloid_chain.rb +70 -0
- data/lib/eye/utils/leak_19.rb +7 -0
- data/lib/eye/utils/tail.rb +20 -0
- metadata +390 -0
@@ -0,0 +1,73 @@
|
|
1
|
+
module Eye::Controller::Commands
|
2
|
+
|
3
|
+
# Main method, answer for the client command
|
4
|
+
def command(cmd, *args)
|
5
|
+
debug "client command: #{cmd} #{args * ', '}"
|
6
|
+
|
7
|
+
start_at = Time.now
|
8
|
+
cmd = cmd.to_sym
|
9
|
+
|
10
|
+
res = case cmd
|
11
|
+
when :start, :stop, :restart, :unmonitor, :monitor
|
12
|
+
send_command(cmd, *args)
|
13
|
+
when :delete
|
14
|
+
exclusive{ send_command(cmd, *args) }
|
15
|
+
when :signal
|
16
|
+
signal(*args)
|
17
|
+
when :break_chain
|
18
|
+
break_chain(*args)
|
19
|
+
when :load
|
20
|
+
exclusive{ load(*args) }
|
21
|
+
when :info
|
22
|
+
info_string(*args)
|
23
|
+
when :xinfo
|
24
|
+
info_string_debug(*args)
|
25
|
+
when :oinfo
|
26
|
+
info_string_short
|
27
|
+
when :history
|
28
|
+
history_string(*args)
|
29
|
+
when :quit
|
30
|
+
quit
|
31
|
+
when :check
|
32
|
+
check(*args)
|
33
|
+
when :explain
|
34
|
+
explain(*args)
|
35
|
+
when :match
|
36
|
+
match(*args)
|
37
|
+
when :ping
|
38
|
+
:pong
|
39
|
+
when :logger_dev
|
40
|
+
Eye::Logger.dev
|
41
|
+
|
42
|
+
# object commands, for api
|
43
|
+
when :raw_info
|
44
|
+
info_data(*args)
|
45
|
+
when :raw_history
|
46
|
+
history_data(*args)
|
47
|
+
|
48
|
+
else
|
49
|
+
:unknown_command
|
50
|
+
end
|
51
|
+
|
52
|
+
GC.start
|
53
|
+
info "client command: #{cmd} #{args * ', '} (#{Time.now - start_at}s)"
|
54
|
+
|
55
|
+
res
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def quit
|
61
|
+
info 'exiting...'
|
62
|
+
delete
|
63
|
+
sleep 1
|
64
|
+
Eye::System.send_signal($$) # soft terminate
|
65
|
+
sleep 2
|
66
|
+
Eye::System.send_signal($$, 9)
|
67
|
+
end
|
68
|
+
|
69
|
+
def delete
|
70
|
+
send_command(:delete)
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Eye::Controller::Helpers
|
2
|
+
|
3
|
+
def set_proc_line
|
4
|
+
str = Eye::PROCLINE
|
5
|
+
str += " (#{@applications.map(&:name) * ', '})" if @applications.present?
|
6
|
+
$0 = str
|
7
|
+
end
|
8
|
+
|
9
|
+
def process_by_name(name)
|
10
|
+
all_processes.detect{|c| c.name == name}
|
11
|
+
end
|
12
|
+
|
13
|
+
def group_by_name(name)
|
14
|
+
all_groups.detect{|c| c.name == name}
|
15
|
+
end
|
16
|
+
|
17
|
+
def application_by_name(name)
|
18
|
+
@applications.detect{|c| c.name == name}
|
19
|
+
end
|
20
|
+
|
21
|
+
def all_processes
|
22
|
+
processes = []
|
23
|
+
all_groups.each do |gr|
|
24
|
+
processes += gr.processes.to_a
|
25
|
+
end
|
26
|
+
|
27
|
+
processes
|
28
|
+
end
|
29
|
+
|
30
|
+
def all_groups
|
31
|
+
groups = []
|
32
|
+
@applications.each do |app|
|
33
|
+
groups += app.groups.to_a
|
34
|
+
end
|
35
|
+
|
36
|
+
groups
|
37
|
+
end
|
38
|
+
|
39
|
+
# {'app_name' => {'group_name' => {'process_name' => 'pid_file'}}}
|
40
|
+
def short_tree
|
41
|
+
res = {}
|
42
|
+
@applications.each do |app|
|
43
|
+
res2 = {}
|
44
|
+
|
45
|
+
app.groups.each do |group|
|
46
|
+
res3 = {}
|
47
|
+
|
48
|
+
group.processes.each do |process|
|
49
|
+
res3[process.name] = process[:pid_file_ex]
|
50
|
+
end
|
51
|
+
|
52
|
+
res2[group.name] = res3
|
53
|
+
end
|
54
|
+
|
55
|
+
res[app.name] = res2
|
56
|
+
end
|
57
|
+
|
58
|
+
res
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
module Eye::Controller::Load
|
2
|
+
|
3
|
+
def check(filename)
|
4
|
+
{ filename => catch_load_error(filename) { parse_config(filename).to_h } }
|
5
|
+
end
|
6
|
+
|
7
|
+
def explain(filename)
|
8
|
+
{ filename => catch_load_error(filename) { parse_config(filename).to_h } }
|
9
|
+
end
|
10
|
+
|
11
|
+
def load(*obj_strs)
|
12
|
+
info "load: #{obj_strs}"
|
13
|
+
|
14
|
+
res = Hash.new
|
15
|
+
|
16
|
+
globbing(*obj_strs).each do |filename|
|
17
|
+
res[filename] = catch_load_error(filename) do
|
18
|
+
cfg = parse_config(filename)
|
19
|
+
load_config(filename, cfg)
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
set_proc_line
|
25
|
+
|
26
|
+
res
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# regexp for clean backtrace to show for user
|
32
|
+
BT_REGX = %r[/lib/eye/|lib/celluloid|internal:prelude|logger.rb:|active_support/core_ext|shellwords.rb].freeze
|
33
|
+
|
34
|
+
def catch_load_error(filename = nil, &block)
|
35
|
+
{ :error => false, :config => yield }
|
36
|
+
|
37
|
+
rescue Eye::Dsl::Error, Exception, NoMethodError => ex
|
38
|
+
error "load: config error <#{filename}>: #{ex.message}"
|
39
|
+
|
40
|
+
# filter backtrace for user output
|
41
|
+
bt = (ex.backtrace || [])
|
42
|
+
bt = bt.reject{|line| line.to_s =~ BT_REGX }
|
43
|
+
error bt.join("\n")
|
44
|
+
|
45
|
+
res = { :error => true, :message => ex.message }
|
46
|
+
res.merge!(:backtrace => bt) if bt.present?
|
47
|
+
res
|
48
|
+
end
|
49
|
+
|
50
|
+
def globbing(*obj_strs)
|
51
|
+
res = []
|
52
|
+
return res if obj_strs.empty?
|
53
|
+
|
54
|
+
obj_strs.each do |filename|
|
55
|
+
mask = if File.directory?(filename)
|
56
|
+
File.join filename, '{*.eye}'
|
57
|
+
else
|
58
|
+
filename
|
59
|
+
end
|
60
|
+
|
61
|
+
debug "load: globbing mask #{mask}"
|
62
|
+
|
63
|
+
sub = []
|
64
|
+
Dir[mask].each do |config_path|
|
65
|
+
sub << config_path
|
66
|
+
end
|
67
|
+
sub = [mask] if sub.empty?
|
68
|
+
|
69
|
+
res += sub
|
70
|
+
end
|
71
|
+
|
72
|
+
res
|
73
|
+
end
|
74
|
+
|
75
|
+
# return: result, config
|
76
|
+
def parse_config(filename)
|
77
|
+
raise Eye::Dsl::Error, "config file '#{filename}' not found!" unless File.exists?(filename)
|
78
|
+
debug "parse #{filename}"
|
79
|
+
|
80
|
+
cfg = Eye::Dsl.parse(nil, filename)
|
81
|
+
@current_config.merge(cfg).validate! # just validate summary config here
|
82
|
+
cfg
|
83
|
+
end
|
84
|
+
|
85
|
+
# !!! exclusive operation
|
86
|
+
def load_config(filename, config)
|
87
|
+
info "load #{filename}"
|
88
|
+
new_cfg = @current_config.merge(config)
|
89
|
+
new_cfg.validate!
|
90
|
+
|
91
|
+
load_options(new_cfg.settings)
|
92
|
+
create_objects(new_cfg.applications, config.application_names)
|
93
|
+
@current_config = new_cfg
|
94
|
+
end
|
95
|
+
|
96
|
+
# load global config options
|
97
|
+
def load_options(opts)
|
98
|
+
return if opts.blank?
|
99
|
+
|
100
|
+
opts.each do |key, value|
|
101
|
+
method = "set_opt_#{key}"
|
102
|
+
send(method, value) if value && respond_to?(method)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# create objects as diff, from configs
|
107
|
+
def create_objects(apps_config, changed_apps = [])
|
108
|
+
debug 'create objects'
|
109
|
+
|
110
|
+
apps_config.each do |app_name, app_cfg|
|
111
|
+
update_or_create_application(app_name, app_cfg.clone) if changed_apps.include?(app_name)
|
112
|
+
end
|
113
|
+
|
114
|
+
# sorting applications
|
115
|
+
@applications.sort_by!(&:name)
|
116
|
+
end
|
117
|
+
|
118
|
+
def update_or_create_application(app_name, app_config)
|
119
|
+
@old_groups = {}
|
120
|
+
@old_processes = {}
|
121
|
+
|
122
|
+
app = @applications.detect{|c| c.name == app_name}
|
123
|
+
|
124
|
+
if app
|
125
|
+
app.groups.each do |group|
|
126
|
+
@old_groups[group.name] = group
|
127
|
+
group.processes.each do |proc|
|
128
|
+
@old_processes[proc.name] = proc
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
@applications.delete(app)
|
133
|
+
|
134
|
+
debug "update app #{app_name}"
|
135
|
+
else
|
136
|
+
debug "create app #{app_name}"
|
137
|
+
end
|
138
|
+
|
139
|
+
app = Eye::Application.new(app_name, app_config)
|
140
|
+
@applications << app
|
141
|
+
@added_groups, @added_processes = [], []
|
142
|
+
|
143
|
+
new_groups = app_config.delete(:groups) || {}
|
144
|
+
new_groups.each do |group_name, group_cfg|
|
145
|
+
group = update_or_create_group(group_name, group_cfg.clone)
|
146
|
+
app.add_group(group)
|
147
|
+
group.resort_processes
|
148
|
+
end
|
149
|
+
|
150
|
+
# now, need to clear @old_groups, and @old_processes
|
151
|
+
@old_groups.each{|_, group| group.clear; group.send_command(:delete) }
|
152
|
+
@old_processes.each{|_, process| process.send_command(:delete) if process.alive? }
|
153
|
+
|
154
|
+
# schedule monitoring for new groups, processes
|
155
|
+
added_fully_groups = []
|
156
|
+
@added_groups.each do |group|
|
157
|
+
if group.processes.size > 0 && (group.processes.pure - @added_processes).size == 0
|
158
|
+
added_fully_groups << group
|
159
|
+
@added_processes -= group.processes.pure
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
added_fully_groups.each{|group| group.send_command :monitor }
|
164
|
+
@added_processes.each{|process| process.send_command :monitor }
|
165
|
+
|
166
|
+
# remove links to prevent memory leaks
|
167
|
+
@old_groups = nil
|
168
|
+
@old_processes = nil
|
169
|
+
@added_groups = nil
|
170
|
+
@added_processes = nil
|
171
|
+
|
172
|
+
app.resort_groups
|
173
|
+
|
174
|
+
app
|
175
|
+
end
|
176
|
+
|
177
|
+
def update_or_create_group(group_name, group_config)
|
178
|
+
group = if @old_groups[group_name]
|
179
|
+
debug "update group #{group_name}"
|
180
|
+
group = @old_groups.delete(group_name)
|
181
|
+
group.schedule :update_config, group_config, Eye::Reason::User.new(:'load config')
|
182
|
+
group.clear
|
183
|
+
group
|
184
|
+
else
|
185
|
+
debug "create group #{group_name}"
|
186
|
+
gr = Eye::Group.new(group_name, group_config)
|
187
|
+
@added_groups << gr
|
188
|
+
gr
|
189
|
+
end
|
190
|
+
|
191
|
+
processes = group_config.delete(:processes) || {}
|
192
|
+
processes.each do |process_name, process_cfg|
|
193
|
+
process = update_or_create_process(process_name, process_cfg.clone)
|
194
|
+
group.add_process(process)
|
195
|
+
end
|
196
|
+
|
197
|
+
group
|
198
|
+
end
|
199
|
+
|
200
|
+
def update_or_create_process(process_name, process_cfg)
|
201
|
+
if @old_processes[process_name]
|
202
|
+
debug "update process #{process_name}"
|
203
|
+
process = @old_processes.delete(process_name)
|
204
|
+
process.schedule :update_config, process_cfg, Eye::Reason::User.new(:'load config')
|
205
|
+
process
|
206
|
+
else
|
207
|
+
debug "create process #{process_name}"
|
208
|
+
process = Eye::Process.new(process_cfg)
|
209
|
+
@added_processes << process
|
210
|
+
process
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Eye::Controller::Options
|
2
|
+
|
3
|
+
def set_opt_logger(logger)
|
4
|
+
# do not apply logger, if in stdout state
|
5
|
+
if !%w{stdout stderr}.include?(Eye::Logger.dev)
|
6
|
+
if logger.blank?
|
7
|
+
Eye::Logger.link_logger(nil)
|
8
|
+
else
|
9
|
+
Eye::Logger.link_logger(logger)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def set_opt_logger_level(level)
|
15
|
+
Eye::Logger.log_level = level
|
16
|
+
end
|
17
|
+
|
18
|
+
def set_opt_http(params = {})
|
19
|
+
if params[:enable]
|
20
|
+
if @http
|
21
|
+
if params[:host] != @http.host || params[:host].to_i != @http.host
|
22
|
+
stop_http
|
23
|
+
start_http(params[:host], params[:port])
|
24
|
+
end
|
25
|
+
else
|
26
|
+
start_http(params[:host], params[:port])
|
27
|
+
end
|
28
|
+
else
|
29
|
+
stop_http if @http
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def stop_http
|
36
|
+
if @http
|
37
|
+
@http.stop
|
38
|
+
@http = nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def start_http(host, port)
|
43
|
+
require 'eye/http' # ruby 2.0 autoload bug
|
44
|
+
@http = Eye::Http.new(host, port)
|
45
|
+
@http.start
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module Eye::Controller::SendCommand
|
2
|
+
|
3
|
+
def send_command(command, *obj_strs)
|
4
|
+
matched_objects(*obj_strs) do |obj|
|
5
|
+
if command.to_sym == :delete
|
6
|
+
remove_object_from_tree(obj)
|
7
|
+
set_proc_line # to sync proc line if was delete application
|
8
|
+
end
|
9
|
+
|
10
|
+
obj.send_command(command)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def match(*obj_strs)
|
15
|
+
matched_objects(*obj_strs)
|
16
|
+
end
|
17
|
+
|
18
|
+
def signal(sig, *obj_strs)
|
19
|
+
matched_objects(*obj_strs) do |obj|
|
20
|
+
obj.send_command :signal, sig || 0
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def break_chain(*obj_strs)
|
25
|
+
matched_objects(*obj_strs) do |obj|
|
26
|
+
obj.send_command(:break_chain)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def matched_objects(*obj_strs, &block)
|
33
|
+
objs = find_objects(*obj_strs)
|
34
|
+
res = objs.map(&:full_name)
|
35
|
+
objs.each{|obj| block[obj] } if block
|
36
|
+
res
|
37
|
+
end
|
38
|
+
|
39
|
+
def remove_object_from_tree(obj)
|
40
|
+
klass = obj.class
|
41
|
+
|
42
|
+
if klass == Eye::Application
|
43
|
+
@applications.delete(obj)
|
44
|
+
@current_config.delete_app(obj.name)
|
45
|
+
end
|
46
|
+
|
47
|
+
if klass == Eye::Group
|
48
|
+
@applications.each{|app| app.groups.delete(obj) }
|
49
|
+
@current_config.delete_group(obj.name)
|
50
|
+
end
|
51
|
+
|
52
|
+
if klass == Eye::Process
|
53
|
+
@applications.each{|app| app.groups.each{|gr| gr.processes.delete(obj) }}
|
54
|
+
@current_config.delete_process(obj.name)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# find object to action, restart ... (app, group or process)
|
59
|
+
# nil if not found
|
60
|
+
def find_objects(*obj_strs)
|
61
|
+
return [] if obj_strs.blank?
|
62
|
+
return @applications.dup if obj_strs.size == 1 && (obj_strs[0].strip == 'all' || obj_strs[0].strip == '*')
|
63
|
+
|
64
|
+
res = []
|
65
|
+
obj_strs.map{|c| c.split(",")}.flatten.each do |mask|
|
66
|
+
res += find_objects_by_mask(mask)
|
67
|
+
end
|
68
|
+
|
69
|
+
if res.size > 1
|
70
|
+
# remove inherited targets
|
71
|
+
|
72
|
+
final = []
|
73
|
+
res.each do |obj|
|
74
|
+
sub_object = res.any?{|a| a.sub_object?(obj) }
|
75
|
+
final << obj unless sub_object
|
76
|
+
end
|
77
|
+
|
78
|
+
res = final
|
79
|
+
end
|
80
|
+
|
81
|
+
res.present? ? Eye::Utils::AliveArray.new(res) : []
|
82
|
+
end
|
83
|
+
|
84
|
+
def find_objects_by_mask(mask)
|
85
|
+
mask.strip!
|
86
|
+
|
87
|
+
res = []
|
88
|
+
str = Regexp.escape(mask).gsub('\*', '.*?')
|
89
|
+
r = %r{\A#{str}}
|
90
|
+
|
91
|
+
# find app
|
92
|
+
res = @applications.select{|a| a.name =~ r || a.full_name =~ r }
|
93
|
+
|
94
|
+
# find group
|
95
|
+
@applications.each do |a|
|
96
|
+
res += a.groups.select{|gr| gr.name =~ r || gr.full_name =~ r }
|
97
|
+
end
|
98
|
+
|
99
|
+
# find process
|
100
|
+
@applications.each do |a|
|
101
|
+
a.groups.each do |gr|
|
102
|
+
gr.processes.each do |p|
|
103
|
+
res << p if p.name =~ r || p.full_name =~ r
|
104
|
+
|
105
|
+
if p.childs.present?
|
106
|
+
res += p.childs.values.select{|ch| ch.alive? && (ch.name =~ r || ch.full_name =~ r) }
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
res
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|