easel-dashboard 0.5 → 0.6
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 +4 -4
- data/bin/easel +1 -1
- data/lib/easel/build_pages.rb +2 -1
- data/lib/easel/configuration.rb +18 -18
- data/lib/easel/controller.rb +1 -1
- data/lib/easel/data_gathering.rb +1 -1
- data/lib/easel/logging.rb +1 -1
- data/lib/easel/server.rb +43 -23
- data/lib/easel/websocket.rb +66 -32
- data/lib/easel.rb +3 -7
- data/lib/html/controller.js.erb +2 -2
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1bb0d5d2b352f523eaea5641d64b835153d8fe24e2fea68fc493636c207cc25b
|
4
|
+
data.tar.gz: 378c2dad0c4ad34d39b908d49b5a32ff779c7a7c26ffc4acc886226b58fb9c51
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: df5b92218548203a7268852978a8238a26709506ee3f7eaf8c7345b6baab6b47b4bdbf786797a04b09815fc70386cf86b46ae9dc6efdda44d1038591efb33521
|
7
|
+
data.tar.gz: d2baa673d372e3e86e38635a425c757698e1ca76eb69822725d78ccea2749cd3ffaef804e2331765239797057a127e10b48efe416d2d38cf7ee6e772c40cd1b6
|
data/bin/easel
CHANGED
data/lib/easel/build_pages.rb
CHANGED
data/lib/easel/configuration.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#!/
|
1
|
+
#!/bin/env ruby
|
2
2
|
#
|
3
3
|
# Author: Eric Power
|
4
4
|
#
|
@@ -68,23 +68,23 @@ $config = {
|
|
68
68
|
regex: ", (\\d+.\\d+)\\n"
|
69
69
|
}
|
70
70
|
]
|
71
|
-
}
|
72
|
-
{
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
}
|
71
|
+
}#,
|
72
|
+
# {
|
73
|
+
# name: "Memory",
|
74
|
+
# type: "time-series", # uses Time.now.strftime("%H:%M") as x asix.
|
75
|
+
# data: [
|
76
|
+
# {
|
77
|
+
# cmd: "free",
|
78
|
+
# name: "Total Memory",
|
79
|
+
# regex: "Mem:\\W+(\\d+)"
|
80
|
+
# },
|
81
|
+
# {
|
82
|
+
# cmd: "free",
|
83
|
+
# name: "Free Memory",
|
84
|
+
# regex: "Mem:\\W+\\d+\\W+(\\d+)"
|
85
|
+
# }
|
86
|
+
# ]
|
87
|
+
# }
|
88
88
|
]
|
89
89
|
}
|
90
90
|
]
|
data/lib/easel/controller.rb
CHANGED
data/lib/easel/data_gathering.rb
CHANGED
data/lib/easel/logging.rb
CHANGED
data/lib/easel/server.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#!/
|
1
|
+
#!/bin/env ruby
|
2
2
|
#
|
3
3
|
# Author: Eric Power
|
4
4
|
#
|
@@ -37,6 +37,7 @@ def launch_server
|
|
37
37
|
log_info "Interrupt received, server shutting down..."
|
38
38
|
rescue Exception => e
|
39
39
|
log_error "Unexpected error occured and closed client connection. Error: #{e}"
|
40
|
+
e.backtrace.each { |trace| log_error "#{trace}" }
|
40
41
|
end
|
41
42
|
end
|
42
43
|
|
@@ -46,32 +47,46 @@ def handle_request socket
|
|
46
47
|
log_info "Receieved request: #{socket}"
|
47
48
|
request = read_HTTP_message socket
|
48
49
|
|
49
|
-
|
50
|
+
if request.nil?
|
51
|
+
socket.print build_error 400
|
52
|
+
socket.close
|
53
|
+
return
|
54
|
+
end
|
55
|
+
|
50
56
|
case request[:method]
|
51
57
|
when "GET"
|
52
|
-
# TODO: respond with app, css file, favicon, or 404 error.
|
53
58
|
if request[:fields][:Upgrade] == "websocket\r\n"
|
54
59
|
run_websocket(socket, request)
|
55
60
|
else
|
56
61
|
handle_get(socket, request)
|
57
62
|
end
|
58
|
-
|
59
|
-
|
63
|
+
when "HEAD"
|
64
|
+
if request[:fields][:Upgrade] == "websocket\r\n"
|
65
|
+
else
|
66
|
+
read_end, write_end = IO.pipe
|
67
|
+
handle_get(write_end, request)
|
68
|
+
msg = ""
|
69
|
+
loop do
|
70
|
+
msg += read_end.readline
|
71
|
+
if msg.include? "\r\n\r\n"
|
72
|
+
socket.print msg
|
73
|
+
socket.close
|
74
|
+
break
|
75
|
+
elsif read_end.readline.nil?
|
76
|
+
socket.print build_error 500
|
77
|
+
socket.close
|
78
|
+
break
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
60
82
|
else
|
61
|
-
|
62
|
-
socket.print
|
63
|
-
"Content-Type: text/plain\r\n" +
|
64
|
-
"Content-Length: #{response.bytesize}\r\n" +
|
65
|
-
"Connection: close\r\n" +
|
66
|
-
"\r\n" +
|
67
|
-
response
|
83
|
+
puts "THIS SHOULD NOT OCCUREEEEEE"
|
84
|
+
socket.print build_error 400
|
68
85
|
socket.close
|
69
86
|
end
|
70
87
|
|
71
88
|
end
|
72
89
|
|
73
|
-
|
74
|
-
|
75
90
|
# handle_get
|
76
91
|
#
|
77
92
|
# Handle a get request.
|
@@ -80,28 +95,28 @@ def handle_get(socket, request)
|
|
80
95
|
case request[:url]
|
81
96
|
when "/", "/index.html"
|
82
97
|
socket.print build_app
|
83
|
-
socket.close
|
84
|
-
when "/test.html" # TODO: Remove this!
|
85
|
-
socket.print return_html "test.html"
|
86
|
-
socket.close
|
98
|
+
socket.close unless request[:Connection].is_a? String and request[:Connection].downcase == "keep-alive\r\n"
|
87
99
|
when "/app.css"
|
88
100
|
socket.print build_css
|
89
|
-
socket.close
|
101
|
+
socket.close unless request[:Connection].is_a? String and request[:Connection].downcase == "keep-alive\r\n"
|
90
102
|
when "/controller.js"
|
91
103
|
log_info "building controller"
|
92
104
|
socket.print build_js
|
93
|
-
socket.close
|
105
|
+
socket.close unless request[:Connection].is_a? String and request[:Connection].downcase == "keep-alive\r\n"
|
94
106
|
when "/dashboardElements.js"
|
95
107
|
socket.print return_js 'dashboardElements.js'
|
96
|
-
socket.close
|
108
|
+
socket.close unless request[:Connection].is_a? String and request[:Connection].downcase == "keep-alive\r\n"
|
97
109
|
when "/createComponents.js"
|
98
110
|
socket.print return_js 'createComponents.js'
|
99
|
-
socket.close
|
111
|
+
socket.close unless request[:Connection].is_a? String and request[:Connection].downcase == "keep-alive\r\n"
|
112
|
+
# TODO: respond with favicon
|
100
113
|
else
|
101
114
|
socket.print build_error 404
|
102
|
-
socket.close
|
115
|
+
socket.close unless request[:Connection].is_a? String and request[:Connection].downcase == "keep-alive\r\n"
|
103
116
|
end
|
104
117
|
|
118
|
+
log_info "Handled HTTP request: #{request}"
|
119
|
+
|
105
120
|
end
|
106
121
|
|
107
122
|
|
@@ -110,8 +125,13 @@ end
|
|
110
125
|
# Read an HTTP message from the socket, and parse it into a request Hash.
|
111
126
|
def read_HTTP_message socket
|
112
127
|
message = []
|
128
|
+
first_line = true
|
113
129
|
loop do
|
114
130
|
line = socket.gets
|
131
|
+
if first_line
|
132
|
+
return nil if line.nil? or not line.match(/^(GET|HEAD|POST|PUT|DELETE|OPTIONS|TRACE) .+ HTTP.+/)
|
133
|
+
first_line = false
|
134
|
+
end
|
115
135
|
message << line
|
116
136
|
if line == "\r\n"
|
117
137
|
break
|
data/lib/easel/websocket.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#!/
|
1
|
+
#!/bin/env ruby
|
2
2
|
#
|
3
3
|
# Author: Eric Power
|
4
4
|
#
|
@@ -69,6 +69,8 @@ require_relative 'data_gathering'
|
|
69
69
|
|
70
70
|
# Key Variables
|
71
71
|
MAX_WS_FRAME_SIZE = 50.0 # Must be a float number to allow a non-truncated division result.
|
72
|
+
# TODO: change to 127.0
|
73
|
+
|
72
74
|
|
73
75
|
# run_websocket
|
74
76
|
#
|
@@ -76,44 +78,63 @@ MAX_WS_FRAME_SIZE = 50.0 # Must be a float number to allow a non-truncated divi
|
|
76
78
|
def run_websocket(socket, initial_request)
|
77
79
|
|
78
80
|
accept_connection(socket, initial_request[:fields][:Sec_WebSocket_Key][0..-3])
|
81
|
+
log_info "Accepted WebSocket Connection"
|
79
82
|
child_threads = {}
|
80
83
|
send_msg_mutex = Mutex.new # One mutex per websocket to control sending messages.
|
81
84
|
|
85
|
+
|
82
86
|
Thread.new { # Periodically update the generic dashboard if set.
|
83
87
|
loop do
|
84
|
-
|
85
|
-
|
88
|
+
#begin
|
89
|
+
data = read_data
|
90
|
+
send_msg(socket, send_msg_mutex, nil, "DASH", data)
|
91
|
+
#rescue Errno::EPIPE
|
92
|
+
# log_info "Pipe closed erorr while sending periodic data to client"
|
93
|
+
# break
|
94
|
+
#end
|
86
95
|
sleep $config[:collect_data_period]
|
87
96
|
end
|
88
97
|
} unless $config[:collect_data_period] == 0
|
89
98
|
|
90
|
-
|
91
|
-
|
92
|
-
|
99
|
+
begin
|
100
|
+
loop {
|
101
|
+
begin
|
102
|
+
msg = receive_msg socket
|
103
|
+
rescue Errno::ECONNRESET => e
|
104
|
+
log_error "Client reset the connection"
|
105
|
+
socket.close
|
106
|
+
msg = nil
|
107
|
+
end
|
108
|
+
break if msg.nil? # The socket was closed by the client.
|
93
109
|
|
94
|
-
|
95
|
-
|
96
|
-
|
110
|
+
case msg.split(":")[1]
|
111
|
+
when "RUN"
|
112
|
+
cmd_id = msg.match(/^RUN:(.*)$/)[1].to_i
|
97
113
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
114
|
+
unless child_threads[cmd_id]
|
115
|
+
child_threads[cmd_id] = Thread.new do
|
116
|
+
run_command_and_stream(socket, cmd_id, send_msg_mutex)
|
117
|
+
child_threads[cmd_id] = nil
|
118
|
+
end
|
102
119
|
end
|
103
|
-
end
|
104
120
|
|
105
|
-
|
121
|
+
when "STOP"
|
106
122
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
123
|
+
cmd_id = msg.match(/^STOP:(.*)$/)[1].to_i
|
124
|
+
unless child_threads[cmd_id].nil?
|
125
|
+
child_threads[cmd_id].kill
|
126
|
+
child_threads[cmd_id] = nil
|
127
|
+
end
|
112
128
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
129
|
+
else
|
130
|
+
log_error "Received an unrecognized message over the websocket: #{msg}"
|
131
|
+
end
|
132
|
+
}
|
133
|
+
rescue Exception => e
|
134
|
+
log_error "Wuh Woh #2: #{e}"
|
135
|
+
e.backtrace.each { |trace| log_error "#{trace}" }
|
136
|
+
raise e
|
137
|
+
end
|
117
138
|
|
118
139
|
end
|
119
140
|
|
@@ -186,12 +207,14 @@ end
|
|
186
207
|
|
187
208
|
# receive_msg
|
188
209
|
#
|
189
|
-
#
|
210
|
+
# Extremely naive websocket server. Requires masking, and a message with a length
|
211
|
+
# of at most 125. Needs to be updated to conform with RFC 6544
|
190
212
|
def receive_msg socket
|
191
213
|
|
192
214
|
# Check first two bytes
|
193
215
|
byte1 = socket.getbyte
|
194
216
|
byte2 = socket.getbyte
|
217
|
+
return nil if byte1.nil? or byte2.nil?
|
195
218
|
if byte1 == 0x88 # Client is requesting that we close the connection.
|
196
219
|
# TODO: Unsure how to properly handle this case. Right now the socket will close and
|
197
220
|
# everything here will shut down - eventually? Kill all child threads first?
|
@@ -204,19 +227,28 @@ def receive_msg socket
|
|
204
227
|
msg_size = byte2 & 0b01111111
|
205
228
|
is_masked = byte2 & 0b10000000
|
206
229
|
unless fin and opcode == 1 and is_masked and msg_size < MAX_WS_FRAME_SIZE
|
207
|
-
log_error "Invalid websocket message received. #{
|
208
|
-
|
209
|
-
msg_size.times.map { socket.getbyte } # Read message from socket.
|
230
|
+
log_error "Invalid websocket message received. #{fin}, #{opcode == 1}, #{is_masked}, #{msg_size}"
|
231
|
+
msg_size.times { socket.getbyte } # Read message from socket.
|
210
232
|
return
|
211
233
|
end
|
212
234
|
|
213
235
|
# Get message
|
214
236
|
mask = 4.times.map { socket.getbyte }
|
215
|
-
|
216
|
-
|
237
|
+
raise "Not Integer: fin > #{fin }" if not fin.is_a? Integer
|
238
|
+
raise "Not Integer: msg_size > #{msg_size }" if not msg_size.is_a? Integer
|
239
|
+
raise "Not Integer: opcode > #{opcode }" if not opcode.is_a? Integer
|
240
|
+
raise "Not Integer: is_masked > #{is_masked}" if not is_masked.is_a? Integer
|
241
|
+
raise "Not aRRAY: mask > #{mask }" if not mask.is_a? Array
|
242
|
+
msg = msg_size.times.map { socket.getbyte }.each_with_index.map { |byte, i|
|
243
|
+
byte ^ mask[i % 4]
|
217
244
|
}.pack('C*').force_encoding('utf-8').inspect
|
245
|
+
|
246
|
+
log_info "WebSocket received: #{msg}"
|
247
|
+
|
218
248
|
msg[1..-2] # Remove quotation marks from message
|
219
249
|
|
250
|
+
|
251
|
+
|
220
252
|
end
|
221
253
|
|
222
254
|
|
@@ -225,7 +257,6 @@ end
|
|
225
257
|
#
|
226
258
|
def send_msg(socket, send_msg_mutex, cmd_id, msg_type, msg=nil)
|
227
259
|
|
228
|
-
|
229
260
|
case msg_type
|
230
261
|
when "OUT", "ERR" # See comments at the top of the file to explain this part of the protocol.
|
231
262
|
header = "#{cmd_id}:#{msg_type}:"
|
@@ -295,6 +326,8 @@ def send_msg(socket, send_msg_mutex, cmd_id, msg_type, msg=nil)
|
|
295
326
|
log_error "Trying to send a websocket message with unrecognized type: #{msg_type}"
|
296
327
|
end
|
297
328
|
|
329
|
+
log_info "Message sent via WebSocket: #{msg}"
|
330
|
+
|
298
331
|
end
|
299
332
|
|
300
333
|
# send_frame
|
@@ -304,10 +337,11 @@ end
|
|
304
337
|
# this function).
|
305
338
|
# TODO: Figure out the proper frame size (MAX_WS_FRAME_SIZE).
|
306
339
|
def send_frame(socket, msg)
|
340
|
+
|
307
341
|
output = [0b10000001, msg.size, msg]
|
308
342
|
begin
|
309
343
|
socket.write output.pack("CCA#{msg.size}")
|
310
|
-
rescue IOError
|
344
|
+
rescue IOError, Errno::EPIPE
|
311
345
|
log_error "WebSocket is closed. Msg: #{msg}"
|
312
346
|
end
|
313
347
|
end
|
data/lib/easel.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#!/
|
1
|
+
#!/usr/bin/env ruby
|
2
2
|
#
|
3
3
|
# Author: Eric Power
|
4
4
|
#
|
@@ -65,12 +65,8 @@ def parse_ARGV
|
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
|
-
opts.on("-h HOST", "--hostname HOST", "Sets the hostname. Default is '#{$config[:hostname]}'.") do |
|
69
|
-
|
70
|
-
$config[:port] = port
|
71
|
-
else
|
72
|
-
log_fatal "Command argument PORT '#{port}' not a valid port. Must be between 0 and 65535 (inclusive)"
|
73
|
-
end
|
68
|
+
opts.on("-h HOST", "--hostname HOST", "Sets the hostname. Default is '#{$config[:hostname]}'.") do |host|
|
69
|
+
$config[:host] = host
|
74
70
|
end
|
75
71
|
|
76
72
|
opts.on("-o [FILE]", "--output [FILE]", "Set a log file.") do |filename|
|
data/lib/html/controller.js.erb
CHANGED
@@ -118,9 +118,9 @@ function toggle_run(id){
|
|
118
118
|
return;
|
119
119
|
}
|
120
120
|
if (tDash.isRunning) { // TODO: Move into the Dashboard Class.
|
121
|
-
webSocket.send("STOP
|
121
|
+
webSocket.send(id + ":STOP");
|
122
122
|
} else {
|
123
|
-
webSocket.send("RUN
|
123
|
+
webSocket.send(id + ":RUN");
|
124
124
|
}
|
125
125
|
tDash.toggleRunning();
|
126
126
|
}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: easel-dashboard
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.6'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eric Power
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-10-
|
11
|
+
date: 2021-10-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -71,14 +71,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
71
71
|
requirements:
|
72
72
|
- - ">="
|
73
73
|
- !ruby/object:Gem::Version
|
74
|
-
version: '
|
74
|
+
version: '2.6'
|
75
75
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
76
|
requirements:
|
77
77
|
- - ">="
|
78
78
|
- !ruby/object:Gem::Version
|
79
79
|
version: '0'
|
80
80
|
requirements: []
|
81
|
-
rubygems_version: 3.2
|
81
|
+
rubygems_version: 3.1.2
|
82
82
|
signing_key:
|
83
83
|
specification_version: 4
|
84
84
|
summary: An easier way to manage your server.
|