easel-dashboard 0.5 → 0.6
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|