orchestrator 1.0.1
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.
- checksums.yaml +7 -0
- data/LICENSE.md +158 -0
- data/README.md +13 -0
- data/Rakefile +7 -0
- data/app/controllers/orchestrator/api/dependencies_controller.rb +109 -0
- data/app/controllers/orchestrator/api/modules_controller.rb +183 -0
- data/app/controllers/orchestrator/api/systems_controller.rb +294 -0
- data/app/controllers/orchestrator/api/zones_controller.rb +62 -0
- data/app/controllers/orchestrator/api_controller.rb +13 -0
- data/app/controllers/orchestrator/base.rb +59 -0
- data/app/controllers/orchestrator/persistence_controller.rb +29 -0
- data/app/models/orchestrator/access_log.rb +35 -0
- data/app/models/orchestrator/control_system.rb +160 -0
- data/app/models/orchestrator/dependency.rb +87 -0
- data/app/models/orchestrator/mod/by_dependency/map.js +6 -0
- data/app/models/orchestrator/mod/by_module_type/map.js +6 -0
- data/app/models/orchestrator/module.rb +127 -0
- data/app/models/orchestrator/sys/by_modules/map.js +9 -0
- data/app/models/orchestrator/sys/by_zones/map.js +9 -0
- data/app/models/orchestrator/zone.rb +47 -0
- data/app/models/orchestrator/zone/all/map.js +6 -0
- data/config/routes.rb +43 -0
- data/lib/generators/module/USAGE +8 -0
- data/lib/generators/module/module_generator.rb +52 -0
- data/lib/orchestrator.rb +52 -0
- data/lib/orchestrator/control.rb +303 -0
- data/lib/orchestrator/core/mixin.rb +123 -0
- data/lib/orchestrator/core/module_manager.rb +258 -0
- data/lib/orchestrator/core/request_proxy.rb +109 -0
- data/lib/orchestrator/core/requests_proxy.rb +47 -0
- data/lib/orchestrator/core/schedule_proxy.rb +49 -0
- data/lib/orchestrator/core/system_proxy.rb +153 -0
- data/lib/orchestrator/datagram_server.rb +114 -0
- data/lib/orchestrator/dependency_manager.rb +131 -0
- data/lib/orchestrator/device/command_queue.rb +213 -0
- data/lib/orchestrator/device/manager.rb +83 -0
- data/lib/orchestrator/device/mixin.rb +35 -0
- data/lib/orchestrator/device/processor.rb +441 -0
- data/lib/orchestrator/device/transport_makebreak.rb +221 -0
- data/lib/orchestrator/device/transport_tcp.rb +139 -0
- data/lib/orchestrator/device/transport_udp.rb +89 -0
- data/lib/orchestrator/engine.rb +70 -0
- data/lib/orchestrator/errors.rb +23 -0
- data/lib/orchestrator/logger.rb +115 -0
- data/lib/orchestrator/logic/manager.rb +18 -0
- data/lib/orchestrator/logic/mixin.rb +11 -0
- data/lib/orchestrator/service/manager.rb +63 -0
- data/lib/orchestrator/service/mixin.rb +56 -0
- data/lib/orchestrator/service/transport_http.rb +55 -0
- data/lib/orchestrator/status.rb +229 -0
- data/lib/orchestrator/system.rb +108 -0
- data/lib/orchestrator/utilities/constants.rb +41 -0
- data/lib/orchestrator/utilities/transcoder.rb +57 -0
- data/lib/orchestrator/version.rb +3 -0
- data/lib/orchestrator/websocket_manager.rb +425 -0
- data/orchestrator.gemspec +35 -0
- data/spec/orchestrator/queue_spec.rb +200 -0
- metadata +271 -0
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
|
4
|
+
module Orchestrator
|
5
|
+
class System
|
6
|
+
@@systems = ThreadSafe::Cache.new
|
7
|
+
@@critical = Mutex.new
|
8
|
+
|
9
|
+
def self.get(id)
|
10
|
+
name = id.to_sym
|
11
|
+
system = @@systems[name]
|
12
|
+
if system.nil?
|
13
|
+
system = self.load(name)
|
14
|
+
end
|
15
|
+
return system
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.expire(id)
|
19
|
+
@@systems.delete(id.to_sym)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.clear_cache
|
23
|
+
@@critical.synchronize {
|
24
|
+
@@systems = ThreadSafe::Cache.new
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
attr_reader :zones, :config
|
30
|
+
|
31
|
+
|
32
|
+
def initialize(control_system)
|
33
|
+
@config = control_system
|
34
|
+
@controller = ::Orchestrator::Control.instance
|
35
|
+
|
36
|
+
@modules = {}
|
37
|
+
@config.modules.each &method(:index_module)
|
38
|
+
|
39
|
+
# Build an ordered zone cache for setting lookup
|
40
|
+
zones = ::Orchestrator::Control.instance.zones
|
41
|
+
@zones = []
|
42
|
+
@config.zones.each do |zone_id|
|
43
|
+
zone = zones[zone_id]
|
44
|
+
@zones << zone unless zone.nil?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def get(mod, index)
|
49
|
+
mods = @modules[mod]
|
50
|
+
if mods
|
51
|
+
mods[index]
|
52
|
+
else
|
53
|
+
nil # As subscriptions can be made to modules that don't exist
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def all(mod)
|
58
|
+
@modules[mod] || []
|
59
|
+
end
|
60
|
+
|
61
|
+
def count(name)
|
62
|
+
mod = @modules[name.to_sym]
|
63
|
+
mod.nil? ? 0 : mod.length
|
64
|
+
end
|
65
|
+
|
66
|
+
def modules
|
67
|
+
@modules.keys
|
68
|
+
end
|
69
|
+
|
70
|
+
def settings
|
71
|
+
@config.settings
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
protected
|
76
|
+
|
77
|
+
|
78
|
+
# looks for the system in the database
|
79
|
+
def self.load(id)
|
80
|
+
@@critical.synchronize {
|
81
|
+
system = @@systems[id]
|
82
|
+
return system unless system.nil?
|
83
|
+
|
84
|
+
sys = ControlSystem.find_by_id(id.to_s)
|
85
|
+
if sys.nil?
|
86
|
+
return nil
|
87
|
+
else
|
88
|
+
system = System.new(sys)
|
89
|
+
@@systems[id] = system
|
90
|
+
end
|
91
|
+
return system
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
def index_module(mod_id)
|
96
|
+
manager = @controller.loaded?(mod_id)
|
97
|
+
if manager
|
98
|
+
mod_name = if manager.settings.custom_name.nil?
|
99
|
+
manager.settings.dependency.module_name.to_sym
|
100
|
+
else
|
101
|
+
manager.settings.custom_name.to_sym
|
102
|
+
end
|
103
|
+
@modules[mod_name] ||= []
|
104
|
+
@modules[mod_name] << manager
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
|
4
|
+
module Orchestrator
|
5
|
+
module Constants
|
6
|
+
On = true # On is active
|
7
|
+
Off = false # Off is inactive
|
8
|
+
Down = true # Down is usually active (projector screen for instance)
|
9
|
+
Up = false # Up is usually inactive
|
10
|
+
Open = true
|
11
|
+
Close = false
|
12
|
+
Short = false
|
13
|
+
|
14
|
+
On_vars = Set.new([1, true, 'true', 'True',
|
15
|
+
:on, :On, 'on', 'On',
|
16
|
+
:yes, :Yes, 'yes', 'Yes',
|
17
|
+
'down', 'Down', :down, :Down,
|
18
|
+
'open', 'Open', :open, :Open])
|
19
|
+
Off_vars = Set.new([0, false, 'false', 'False',
|
20
|
+
:off, :Off, 'off', 'Off',
|
21
|
+
:no, :No, 'no', 'No',
|
22
|
+
'up', 'Up', :up, :Up,
|
23
|
+
'close', 'Close', :close, :Close,
|
24
|
+
'short', 'Short', :short, :Short])
|
25
|
+
|
26
|
+
|
27
|
+
def in_range(num, max, min = 0)
|
28
|
+
num = min if num < min
|
29
|
+
num = max if num > max
|
30
|
+
num
|
31
|
+
end
|
32
|
+
|
33
|
+
def is_affirmative?(val)
|
34
|
+
On_vars.include?(val)
|
35
|
+
end
|
36
|
+
|
37
|
+
def is_negatory?(val)
|
38
|
+
Off_vars.include?(val)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Orchestrator
|
2
|
+
module Transcoder
|
3
|
+
# Converts a hex encoded string into a binary string
|
4
|
+
#
|
5
|
+
# @param data [String] a hex encoded string
|
6
|
+
# @return [String]
|
7
|
+
def hex_to_byte(data)
|
8
|
+
# Removes invalid characters
|
9
|
+
data = data.gsub(/(0x|[^0-9A-Fa-f])*/, "")
|
10
|
+
|
11
|
+
# Ensure we have an even number of characters
|
12
|
+
data.prepend('0') if data.length % 2 > 0
|
13
|
+
|
14
|
+
# Breaks string into an array of characters
|
15
|
+
output = ""
|
16
|
+
data.scan(/.{2}/) { |byte| output << byte.hex}
|
17
|
+
return output
|
18
|
+
end
|
19
|
+
|
20
|
+
# Converts a binary string into a hex encoded string
|
21
|
+
#
|
22
|
+
# @param data [String] a binary string
|
23
|
+
# @return [String]
|
24
|
+
def byte_to_hex(data)
|
25
|
+
output = ""
|
26
|
+
data.each_byte { |c|
|
27
|
+
s = c.to_s(16)
|
28
|
+
s.prepend('0') if s.length % 2 > 0
|
29
|
+
output << s
|
30
|
+
}
|
31
|
+
return output
|
32
|
+
end
|
33
|
+
|
34
|
+
# Converts a string into an array of bytes
|
35
|
+
#
|
36
|
+
# @param data [String] data to be converted to bytes
|
37
|
+
# @return [Array]
|
38
|
+
def str_to_array(data)
|
39
|
+
data.bytes.to_a
|
40
|
+
end
|
41
|
+
|
42
|
+
# Converts a byte array into a binary string
|
43
|
+
#
|
44
|
+
# @param data [Array] an array of bytes
|
45
|
+
# @return [String]
|
46
|
+
def array_to_str(data)
|
47
|
+
data.pack('c*')
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
# Makes the functions private when included
|
52
|
+
module_function :hex_to_byte
|
53
|
+
module_function :byte_to_hex
|
54
|
+
module_function :str_to_array
|
55
|
+
module_function :array_to_str
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,425 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
|
5
|
+
module Orchestrator
|
6
|
+
class WebsocketManager
|
7
|
+
def initialize(ws, user)
|
8
|
+
@ws = ws
|
9
|
+
@user = user
|
10
|
+
@loop = ws.loop
|
11
|
+
|
12
|
+
@bindings = ::ThreadSafe::Cache.new
|
13
|
+
@stattrak = @loop.observer
|
14
|
+
@notify_update = method(:notify_update)
|
15
|
+
|
16
|
+
@logger = ::Orchestrator::Logger.new(@loop, user)
|
17
|
+
|
18
|
+
@ws.progress method(:on_message)
|
19
|
+
@ws.finally method(:on_shutdown)
|
20
|
+
#@ws.on_open method(:on_open)
|
21
|
+
|
22
|
+
@accessed = ::Set.new
|
23
|
+
@access_log = ::Orchestrator::AccessLog.new
|
24
|
+
@access_log.user_id = @user.id
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
DECODE_OPTIONS = {
|
29
|
+
symbolize_names: true
|
30
|
+
}.freeze
|
31
|
+
|
32
|
+
PARAMS = [:id, :cmd, :sys, :mod, :index, :name, {args: [].freeze}.freeze].freeze
|
33
|
+
REQUIRED = [:id, :cmd, :sys, :mod, :name].freeze
|
34
|
+
COMMANDS = Set.new([:exec, :bind, :unbind, :debug, :ignore])
|
35
|
+
|
36
|
+
ERRORS = {
|
37
|
+
parse_error: 0,
|
38
|
+
bad_request: 1,
|
39
|
+
access_denied: 2,
|
40
|
+
request_failed: 3,
|
41
|
+
unknown_command: 4,
|
42
|
+
|
43
|
+
system_not_found: 5,
|
44
|
+
module_not_found: 6,
|
45
|
+
unexpected_failure: 7
|
46
|
+
}.freeze
|
47
|
+
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
|
52
|
+
def on_message(data, ws)
|
53
|
+
if data == 'ping'
|
54
|
+
@ws.text('pong')
|
55
|
+
return
|
56
|
+
end
|
57
|
+
|
58
|
+
begin
|
59
|
+
raw_parameters = ::JSON.parse(data, DECODE_OPTIONS)
|
60
|
+
parameters = ::ActionController::Parameters.new(raw_parameters)
|
61
|
+
params = parameters.permit(PARAMS).tap do |whitelist|
|
62
|
+
whitelist[:args] = parameters[:args]
|
63
|
+
end
|
64
|
+
rescue => e
|
65
|
+
@logger.print_error(e, 'error parsing websocket request')
|
66
|
+
error_response(nil, ERRORS[:parse_error], e.message)
|
67
|
+
return
|
68
|
+
end
|
69
|
+
|
70
|
+
if check_requirements(params)
|
71
|
+
# Perform the security check in a nonblocking fashion
|
72
|
+
# (Database access is probably required)
|
73
|
+
result = @loop.work do
|
74
|
+
sys = params[:sys]
|
75
|
+
params[:sys] = ::Orchestrator::ControlSystem.bucket.get("sysname-#{sys.downcase}", {quiet: true}) || sys
|
76
|
+
Rails.configuration.orchestrator.check_access.call(params[:sys], @user)
|
77
|
+
end
|
78
|
+
|
79
|
+
# The result should be an access level if these are implemented
|
80
|
+
result.then do |access|
|
81
|
+
begin
|
82
|
+
cmd = params[:cmd].to_sym
|
83
|
+
if COMMANDS.include?(cmd)
|
84
|
+
@accessed << params[:sys] # Log the access
|
85
|
+
self.__send__(cmd, params) # Execute the request
|
86
|
+
else
|
87
|
+
@access_log.suspected = true
|
88
|
+
@logger.warn("websocket requested unknown command '#{params[:cmd]}'")
|
89
|
+
error_response(params[:id], ERRORS[:unknown_command], "unknown command: #{params[:cmd]}")
|
90
|
+
end
|
91
|
+
rescue => e
|
92
|
+
@logger.print_error(e, "websocket request failed: #{data}")
|
93
|
+
error_response(params[:id], ERRORS[:request_failed], e.message)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Raise an error if access is not granted
|
98
|
+
result.catch do |err|
|
99
|
+
@access_log.suspected = true
|
100
|
+
@logger.print_error(e, 'security check failed for websocket request')
|
101
|
+
error_response(params[:id], ERRORS[:access_denied], e.message)
|
102
|
+
end
|
103
|
+
else
|
104
|
+
# log user information here (possible probing attempt)
|
105
|
+
@access_log.suspected = true
|
106
|
+
reason = 'required parameters were missing from the request'
|
107
|
+
@logger.warn(reason)
|
108
|
+
error_response(params[:id], ERRORS[:bad_request], reason)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def check_requirements(params)
|
113
|
+
REQUIRED.each do |key|
|
114
|
+
return false if params[key].nil?
|
115
|
+
end
|
116
|
+
true
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
def exec(params)
|
121
|
+
id = params[:id]
|
122
|
+
sys = params[:sys]
|
123
|
+
mod = params[:mod].to_sym
|
124
|
+
name = params[:name].to_sym
|
125
|
+
index_s = params[:index] || 1
|
126
|
+
index = index_s.to_i
|
127
|
+
|
128
|
+
args = params[:args] || []
|
129
|
+
|
130
|
+
@loop.work do
|
131
|
+
do_exec(id, sys, mod, index, name, args)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def do_exec(id, sys, mod, index, name, args)
|
136
|
+
system = ::Orchestrator::System.get(sys)
|
137
|
+
|
138
|
+
if system
|
139
|
+
mod_man = system.get(mod, index - 1)
|
140
|
+
if mod_man
|
141
|
+
req = Core::RequestProxy.new(@loop, mod_man)
|
142
|
+
result = req.send(name, *args)
|
143
|
+
result.then(proc { |res|
|
144
|
+
output = nil
|
145
|
+
begin
|
146
|
+
::JSON.generate([res])
|
147
|
+
output = res
|
148
|
+
rescue => e
|
149
|
+
# respond with nil if object cannot be converted
|
150
|
+
# TODO:: need a better way of dealing with this
|
151
|
+
# ALSO in systems controller
|
152
|
+
end
|
153
|
+
@ws.text(::JSON.generate({
|
154
|
+
id: id,
|
155
|
+
type: :success,
|
156
|
+
value: output
|
157
|
+
}))
|
158
|
+
}, proc { |err|
|
159
|
+
# Request proxy will log the error
|
160
|
+
error_response(id, ERRORS[:request_failed], err.message)
|
161
|
+
})
|
162
|
+
else
|
163
|
+
@logger.debug("websocket exec could not find module: {sys: #{sys}, mod: #{mod}, index: #{index}, name: #{name}}")
|
164
|
+
error_response(id, ERRORS[:module_not_found], "could not find module: #{mod}")
|
165
|
+
end
|
166
|
+
else
|
167
|
+
@logger.debug("websocket exec could not find system: {sys: #{sys}, mod: #{mod}, index: #{index}, name: #{name}}")
|
168
|
+
error_response(id, ERRORS[:system_not_found], "could not find system: #{sys}")
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
def unbind(params)
|
174
|
+
id = params[:id]
|
175
|
+
sys = params[:sys]
|
176
|
+
mod = params[:mod]
|
177
|
+
name = params[:name]
|
178
|
+
index_s = params[:index] || 1
|
179
|
+
index = index_s.to_i
|
180
|
+
|
181
|
+
lookup = :"#{sys}_#{mod}_#{index}_#{name}"
|
182
|
+
binding = @bindings.delete(lookup)
|
183
|
+
do_unbind(binding) if binding
|
184
|
+
|
185
|
+
@ws.text(::JSON.generate({
|
186
|
+
id: id,
|
187
|
+
type: :success
|
188
|
+
}))
|
189
|
+
end
|
190
|
+
|
191
|
+
def do_unbind(binding)
|
192
|
+
@stattrak.unsubscribe(binding)
|
193
|
+
end
|
194
|
+
|
195
|
+
|
196
|
+
def bind(params)
|
197
|
+
id = params[:id]
|
198
|
+
sys = params[:sys]
|
199
|
+
mod = params[:mod].to_sym
|
200
|
+
name = params[:name].to_sym
|
201
|
+
index_s = params[:index] || 1
|
202
|
+
index = index_s.to_i
|
203
|
+
|
204
|
+
# perform binding on the thread pool
|
205
|
+
@loop.work(proc {
|
206
|
+
check_binding(id, sys, mod, index, name)
|
207
|
+
}).catch do |err|
|
208
|
+
@logger.print_error(err, "websocket request failed: #{params}")
|
209
|
+
error_response(id, ERRORS[:unexpected_failure], err.message)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# Called from a worker thread
|
214
|
+
def check_binding(id, sys, mod, index, name)
|
215
|
+
system = ::Orchestrator::System.get(sys)
|
216
|
+
|
217
|
+
if system
|
218
|
+
lookup = :"#{sys}_#{mod}_#{index}_#{name}"
|
219
|
+
binding = @bindings[lookup]
|
220
|
+
|
221
|
+
if binding.nil?
|
222
|
+
try_bind(id, sys, system, mod, index, name, lookup)
|
223
|
+
else
|
224
|
+
# binding already made - return success
|
225
|
+
@ws.text(::JSON.generate({
|
226
|
+
id: id,
|
227
|
+
type: :success,
|
228
|
+
meta: {
|
229
|
+
sys: sys,
|
230
|
+
mod: mod,
|
231
|
+
index: index,
|
232
|
+
name: name
|
233
|
+
}
|
234
|
+
}))
|
235
|
+
end
|
236
|
+
else
|
237
|
+
@logger.debug("websocket binding could not find system: {sys: #{sys}, mod: #{mod}, index: #{index}, name: #{name}}")
|
238
|
+
error_response(id, ERRORS[:system_not_found], "could not find system: #{sys}")
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def try_bind(id, sys, system, mod_name, index, name, lookup)
|
243
|
+
options = {
|
244
|
+
sys_id: sys,
|
245
|
+
sys_name: system.config.name,
|
246
|
+
mod_name: mod_name,
|
247
|
+
index: index,
|
248
|
+
status: name,
|
249
|
+
callback: @notify_update,
|
250
|
+
on_thread: @loop
|
251
|
+
}
|
252
|
+
|
253
|
+
# if the module exists, subscribe on the correct thread
|
254
|
+
# use a bit of promise magic as required
|
255
|
+
mod_man = system.get(mod_name, index - 1)
|
256
|
+
defer = @loop.defer
|
257
|
+
|
258
|
+
# Ensure browser sees this before the first status update
|
259
|
+
# At this point subscription will be successful
|
260
|
+
@bindings[lookup] = defer.promise
|
261
|
+
@ws.text(::JSON.generate({
|
262
|
+
id: id,
|
263
|
+
type: :success,
|
264
|
+
meta: {
|
265
|
+
sys: sys,
|
266
|
+
mod: mod_name,
|
267
|
+
index: index,
|
268
|
+
name: name
|
269
|
+
}
|
270
|
+
}))
|
271
|
+
|
272
|
+
if mod_man
|
273
|
+
options[:mod_id] = mod_man.settings.id.to_sym
|
274
|
+
options[:mod] = mod_man
|
275
|
+
thread = mod_man.thread
|
276
|
+
thread.schedule do
|
277
|
+
defer.resolve (
|
278
|
+
thread.observer.subscribe(options)
|
279
|
+
)
|
280
|
+
end
|
281
|
+
else
|
282
|
+
@loop.schedule do
|
283
|
+
defer.resolve @stattrak.subscribe(options)
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
def notify_update(update)
|
289
|
+
output = nil
|
290
|
+
begin
|
291
|
+
::JSON.generate([update.value])
|
292
|
+
output = update.value
|
293
|
+
rescue => e
|
294
|
+
# respond with nil if object cannot be converted
|
295
|
+
# TODO:: need a better way of dealing with this
|
296
|
+
end
|
297
|
+
@ws.text(::JSON.generate({
|
298
|
+
type: :notify,
|
299
|
+
value: output,
|
300
|
+
meta: {
|
301
|
+
sys: update.sys_id,
|
302
|
+
mod: update.mod_name,
|
303
|
+
index: update.index,
|
304
|
+
name: update.status
|
305
|
+
}
|
306
|
+
}))
|
307
|
+
end
|
308
|
+
|
309
|
+
|
310
|
+
def debug(params)
|
311
|
+
id = params[:id]
|
312
|
+
sys = params[:sys]
|
313
|
+
mod_s = params[:mod]
|
314
|
+
mod = mod_s.to_sym if mod_s
|
315
|
+
|
316
|
+
if @debug.nil?
|
317
|
+
@debug = @loop.defer
|
318
|
+
@inspecting = Set.new # modules
|
319
|
+
@debug.promise.progress method(:debug_update)
|
320
|
+
end
|
321
|
+
|
322
|
+
# Set mod to get module level errors
|
323
|
+
if mod && !@inspecting.include?(mod)
|
324
|
+
mod_man = ::Orchestrator::Control.instance.loaded?(mod)
|
325
|
+
if mod_man
|
326
|
+
log = mod_man.logger
|
327
|
+
log.add @debug
|
328
|
+
log.level = :debug
|
329
|
+
@inspecting.add mod
|
330
|
+
|
331
|
+
# Set sys to get errors occurring outside of the modules
|
332
|
+
if !@inspecting.include?(:self)
|
333
|
+
@logger.add @debug
|
334
|
+
@logger.level = :debug
|
335
|
+
@inspecting.add :self
|
336
|
+
end
|
337
|
+
|
338
|
+
@ws.text(::JSON.generate({
|
339
|
+
id: id,
|
340
|
+
type: :success
|
341
|
+
}))
|
342
|
+
else
|
343
|
+
@logger.info("websocket debug could not find module: #{mod}")
|
344
|
+
error_response(id, ERRORS[:module_not_found], "could not find module: #{mod}")
|
345
|
+
end
|
346
|
+
else
|
347
|
+
@ws.text(::JSON.generate({
|
348
|
+
id: id,
|
349
|
+
type: :success
|
350
|
+
}))
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
def debug_update(klass, id, level, msg)
|
355
|
+
@ws.text(::JSON.generate({
|
356
|
+
type: :debug,
|
357
|
+
mod: id,
|
358
|
+
klass: klass,
|
359
|
+
level: level,
|
360
|
+
msg: msg
|
361
|
+
}))
|
362
|
+
end
|
363
|
+
|
364
|
+
|
365
|
+
def ignore(params)
|
366
|
+
id = params[:id]
|
367
|
+
sys = params[:sys]
|
368
|
+
mod_s = params[:mod]
|
369
|
+
mod = mod_s.to_sym if mod_s
|
370
|
+
|
371
|
+
if @debug.nil?
|
372
|
+
@debug = @loop.defer
|
373
|
+
@inspecting = Set.new # modules
|
374
|
+
@debug.promise.progress method(:debug_update)
|
375
|
+
end
|
376
|
+
|
377
|
+
# Remove module level errors
|
378
|
+
if mod && @inspecting.include?(mod)
|
379
|
+
mod_man = ::Orchestrator::Control.instance.loaded?(mod)
|
380
|
+
if mod_man
|
381
|
+
mod_man.logger.delete @debug
|
382
|
+
@inspecting.delete mod
|
383
|
+
|
384
|
+
# Stop logging all together if no more modules being watched
|
385
|
+
if @inspecting.empty?
|
386
|
+
@logger.delete @debug
|
387
|
+
@inspecting.delete :self
|
388
|
+
end
|
389
|
+
|
390
|
+
@ws.text(::JSON.generate({
|
391
|
+
id: id,
|
392
|
+
type: :success
|
393
|
+
}))
|
394
|
+
else
|
395
|
+
@logger.info("websocket ignore could not find module: #{mod}")
|
396
|
+
error_response(id, ERRORS[:module_not_found], "could not find module: #{mod}")
|
397
|
+
end
|
398
|
+
else
|
399
|
+
@ws.text(::JSON.generate({
|
400
|
+
id: id,
|
401
|
+
type: :success
|
402
|
+
}))
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
|
407
|
+
def error_response(id, code, message)
|
408
|
+
@ws.text(::JSON.generate({
|
409
|
+
id: id,
|
410
|
+
type: :error,
|
411
|
+
code: code,
|
412
|
+
msg: message
|
413
|
+
}))
|
414
|
+
end
|
415
|
+
|
416
|
+
def on_shutdown
|
417
|
+
@bindings.each_value &method(:do_unbind)
|
418
|
+
@bindings = nil
|
419
|
+
@debug.resolve(true) if @debug # detach debug listeners
|
420
|
+
|
421
|
+
@access_log.systems = @accessed.to_a
|
422
|
+
@access_log.save
|
423
|
+
end
|
424
|
+
end
|
425
|
+
end
|