Rebuilt the network view in Svelte
This commit is contained in:
@@ -1,30 +1,3 @@
|
||||
################################################################################
|
||||
# #
|
||||
# This file is part of the Buildbotics firmware. #
|
||||
# #
|
||||
# Copyright (c) 2015 - 2018, Buildbotics LLC #
|
||||
# All rights reserved. #
|
||||
# #
|
||||
# This file ("the software") is free software: you can redistribute it #
|
||||
# and/or modify it under the terms of the GNU General Public License, #
|
||||
# version 2 as published by the Free Software Foundation. You should #
|
||||
# have received a copy of the GNU General Public License, version 2 #
|
||||
# along with the software. If not, see <http://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
# The software is distributed in the hope that it will be useful, but #
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU #
|
||||
# Lesser General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU Lesser General Public #
|
||||
# License along with the software. If not, see #
|
||||
# <http://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
# For information regarding this software email: #
|
||||
# "Joseph Coffland" <joseph@buildbotics.com> #
|
||||
# #
|
||||
################################################################################
|
||||
|
||||
import os
|
||||
import json
|
||||
import tornado
|
||||
@@ -36,50 +9,26 @@ from tornado.web import HTTPError
|
||||
from tornado import gen
|
||||
|
||||
import bbctrl
|
||||
|
||||
import iw_parse
|
||||
|
||||
|
||||
def call_get_output(cmd):
|
||||
p = subprocess.Popen(cmd, stdout = subprocess.PIPE)
|
||||
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
||||
s = p.communicate()[0].decode('utf-8')
|
||||
if p.returncode: raise HTTPError(400, 'Command failed')
|
||||
if p.returncode:
|
||||
raise HTTPError(400, 'Command failed')
|
||||
return s
|
||||
|
||||
|
||||
def get_username():
|
||||
return call_get_output(['getent', 'passwd', '1001']).split(':')[0]
|
||||
|
||||
|
||||
def set_username(username):
|
||||
if subprocess.call(['usermod', '-l', username, get_username()]):
|
||||
raise HTTPError(400, 'Failed to set username to "%s"' % username)
|
||||
|
||||
|
||||
def check_password(password):
|
||||
# Get current password
|
||||
s = call_get_output(['getent', 'shadow', get_username()])
|
||||
current = s.split(':')[1].split('$')
|
||||
|
||||
# Check password type
|
||||
if len(current) < 2 or current[1] != '1':
|
||||
raise HTTPError(401, "Password invalid")
|
||||
|
||||
# Check current password
|
||||
cmd = ['openssl', 'passwd', '-salt', current[2], '-1', password]
|
||||
s = call_get_output(cmd).strip()
|
||||
|
||||
if s.split('$') != current: raise HTTPError(401, 'Wrong password')
|
||||
|
||||
|
||||
|
||||
class RebootHandler(bbctrl.APIHandler):
|
||||
def put_ok(self):
|
||||
self.get_ctrl().lcd.goodbye('Rebooting...')
|
||||
subprocess.Popen('reboot')
|
||||
|
||||
|
||||
|
||||
class ShutdownHandler(bbctrl.APIHandler):
|
||||
def put_ok(self):
|
||||
subprocess.Popen(['shutdown','-h','now'])
|
||||
subprocess.Popen(['shutdown', '-h', 'now'])
|
||||
|
||||
|
||||
class LogHandler(bbctrl.RequestHandler):
|
||||
@@ -87,7 +36,6 @@ class LogHandler(bbctrl.RequestHandler):
|
||||
with open(self.get_ctrl().log.get_path(), 'r') as f:
|
||||
self.write(f.read())
|
||||
|
||||
|
||||
def set_default_headers(self):
|
||||
fmt = socket.gethostname() + '-%Y%m%d.log'
|
||||
filename = datetime.date.today().strftime(fmt)
|
||||
@@ -102,14 +50,16 @@ class MessageAckHandler(bbctrl.APIHandler):
|
||||
|
||||
class BugReportHandler(bbctrl.RequestHandler):
|
||||
def get(self):
|
||||
import tarfile, io
|
||||
import tarfile
|
||||
import io
|
||||
|
||||
buf = io.BytesIO()
|
||||
tar = tarfile.open(mode = 'w:bz2', fileobj = buf)
|
||||
tar = tarfile.open(mode='w:bz2', fileobj=buf)
|
||||
|
||||
def check_add(path, arcname = None):
|
||||
def check_add(path, arcname=None):
|
||||
if os.path.isfile(path):
|
||||
if arcname is None: arcname = path
|
||||
if arcname is None:
|
||||
arcname = path
|
||||
tar.add(path, self.basename + '/' + arcname)
|
||||
|
||||
def check_add_basename(path):
|
||||
@@ -128,7 +78,6 @@ class BugReportHandler(bbctrl.RequestHandler):
|
||||
|
||||
self.write(buf.getvalue())
|
||||
|
||||
|
||||
def set_default_headers(self):
|
||||
fmt = socket.gethostname() + '-%Y%m%d-%H%M%S'
|
||||
self.basename = datetime.datetime.now().strftime(fmt)
|
||||
@@ -138,8 +87,6 @@ class BugReportHandler(bbctrl.RequestHandler):
|
||||
|
||||
|
||||
class HostnameHandler(bbctrl.APIHandler):
|
||||
def get(self): self.write_json(socket.gethostname())
|
||||
|
||||
def put(self):
|
||||
if self.get_ctrl().args.demo:
|
||||
raise HTTPError(400, 'Cannot set hostname in demo mode')
|
||||
@@ -153,77 +100,59 @@ class HostnameHandler(bbctrl.APIHandler):
|
||||
raise HTTPError(400, 'Failed to set hostname')
|
||||
|
||||
|
||||
class WifiHandler(bbctrl.APIHandler):
|
||||
class NetworkHandler(bbctrl.APIHandler):
|
||||
def get(self):
|
||||
data = {'ssid': '', 'channel': 0}
|
||||
try:
|
||||
data = json.loads(call_get_output(['config-wifi', '-j']))
|
||||
except: pass
|
||||
self.write_json(data)
|
||||
ipAddresses = call_get_output(['hostname', '-I']).split()
|
||||
except:
|
||||
ipAddresses = ""
|
||||
|
||||
hostname = socket.gethostname()
|
||||
|
||||
try:
|
||||
wifi = json.loads(call_get_output(['config-wifi', '-j']))
|
||||
except:
|
||||
wifi = {'enabled': False}
|
||||
|
||||
try:
|
||||
lines = iw_parse.call_iwlist().decode("utf-8").split("\n")
|
||||
wifi['networks'] = iw_parse.get_parsed_cells(lines)
|
||||
except:
|
||||
wifi['networks'] = []
|
||||
|
||||
self.write_json({
|
||||
'ipAddresses': ipAddresses,
|
||||
'hostname': hostname,
|
||||
'wifi': wifi
|
||||
})
|
||||
|
||||
def put(self):
|
||||
if self.get_ctrl().args.demo:
|
||||
raise HTTPError(400, 'Cannot configure WiFi in demo mode')
|
||||
|
||||
if 'mode' in self.json:
|
||||
cmd = ['config-wifi', '-r']
|
||||
mode = self.json['mode']
|
||||
if not 'wifi' in self.json:
|
||||
raise HTTPError(400, 'Payload is missing wifi config information')
|
||||
|
||||
if mode == 'disabled': cmd += ['-d']
|
||||
elif 'ssid' in self.json:
|
||||
cmd += ['-s', self.json['ssid']]
|
||||
wifi = self.json['wifi']
|
||||
|
||||
if mode == 'ap':
|
||||
cmd += ['-a']
|
||||
if 'channel' in self.json:
|
||||
cmd += ['-c', self.json['channel']]
|
||||
cmd = ['config-wifi', '-r']
|
||||
|
||||
if 'pass' in self.json:
|
||||
cmd += ['-p', self.json['pass']]
|
||||
if not wifi['enabled']:
|
||||
cmd += ['-d']
|
||||
else:
|
||||
if 'ssid' in wifi:
|
||||
cmd += ['-s', wifi['ssid']]
|
||||
|
||||
if subprocess.call(cmd) == 0:
|
||||
self.write_json('ok')
|
||||
return
|
||||
if 'password' in wifi:
|
||||
cmd += ['-p', wifi['password']]
|
||||
|
||||
if subprocess.call(cmd) == 0:
|
||||
self.write_json('ok')
|
||||
return
|
||||
|
||||
raise HTTPError(400, 'Failed to configure wifi')
|
||||
|
||||
|
||||
class UsernameHandler(bbctrl.APIHandler):
|
||||
def get(self): self.write_json(get_username())
|
||||
|
||||
|
||||
def put_ok(self):
|
||||
if self.get_ctrl().args.demo:
|
||||
raise HTTPError(400, 'Cannot set username in demo mode')
|
||||
|
||||
if 'username' in self.json: set_username(self.json['username'])
|
||||
else: raise HTTPError(400, 'Missing "username"')
|
||||
|
||||
|
||||
class PasswordHandler(bbctrl.APIHandler):
|
||||
def put(self):
|
||||
if self.get_ctrl().args.demo:
|
||||
raise HTTPError(400, 'Cannot set password in demo mode')
|
||||
|
||||
if 'current' in self.json and 'password' in self.json:
|
||||
check_password(self.json['current'])
|
||||
|
||||
# Set password
|
||||
s = '%s:%s' % (get_username(), self.json['password'])
|
||||
s = s.encode('utf-8')
|
||||
|
||||
p = subprocess.Popen(['chpasswd', '-c', 'MD5'],
|
||||
stdin = subprocess.PIPE)
|
||||
p.communicate(input = s)
|
||||
|
||||
if p.returncode == 0:
|
||||
self.write_json('ok')
|
||||
return
|
||||
|
||||
raise HTTPError(401, 'Failed to set password')
|
||||
|
||||
|
||||
class ConfigLoadHandler(bbctrl.APIHandler):
|
||||
def get(self):
|
||||
self.write_json(self.get_ctrl().config.load())
|
||||
@@ -238,7 +167,7 @@ class ConfigDownloadHandler(bbctrl.APIHandler):
|
||||
'attachment; filename="%s"' % filename)
|
||||
|
||||
def get(self):
|
||||
self.write_json(self.get_ctrl().config.load(), pretty = True)
|
||||
self.write_json(self.get_ctrl().config.load(), pretty=True)
|
||||
|
||||
|
||||
class ConfigSaveHandler(bbctrl.APIHandler):
|
||||
@@ -252,14 +181,14 @@ class ConfigResetHandler(bbctrl.APIHandler):
|
||||
class FirmwareUpdateHandler(bbctrl.APIHandler):
|
||||
def prepare(self): pass
|
||||
|
||||
|
||||
def put_ok(self):
|
||||
if not 'firmware' in self.request.files:
|
||||
raise HTTPError(401, 'Missing "firmware"')
|
||||
|
||||
firmware = self.request.files['firmware'][0]
|
||||
|
||||
if not os.path.exists('firmware'): os.mkdir('firmware')
|
||||
if not os.path.exists('firmware'):
|
||||
os.mkdir('firmware')
|
||||
|
||||
with open('firmware/update.tar.bz2', 'wb') as f:
|
||||
f.write(firmware['body'])
|
||||
@@ -284,20 +213,23 @@ class PathHandler(bbctrl.APIHandler):
|
||||
future = preplanner.get_plan(filename)
|
||||
|
||||
try:
|
||||
delta = datetime.timedelta(seconds = 1)
|
||||
delta = datetime.timedelta(seconds=1)
|
||||
data = yield gen.with_timeout(delta, future)
|
||||
|
||||
except gen.TimeoutError:
|
||||
progress = preplanner.get_plan_progress(filename)
|
||||
self.write_json(dict(progress = progress))
|
||||
self.write_json(dict(progress=progress))
|
||||
return
|
||||
|
||||
try:
|
||||
if data is None: return
|
||||
if data is None:
|
||||
return
|
||||
meta, positions, speeds = data
|
||||
|
||||
if dataType == '/positions': data = positions
|
||||
elif dataType == '/speeds': data = speeds
|
||||
if dataType == '/positions':
|
||||
data = positions
|
||||
elif dataType == '/speeds':
|
||||
data = speeds
|
||||
else:
|
||||
self.get_ctrl().state.set_bounds(meta['bounds'])
|
||||
self.write_json(meta)
|
||||
@@ -316,12 +248,14 @@ class PathHandler(bbctrl.APIHandler):
|
||||
self.write(chunk)
|
||||
yield self.flush()
|
||||
|
||||
except tornado.iostream.StreamClosedError as e: pass
|
||||
except tornado.iostream.StreamClosedError as e:
|
||||
pass
|
||||
|
||||
|
||||
class HomeHandler(bbctrl.APIHandler):
|
||||
def put_ok(self, axis, action, *args):
|
||||
if axis is not None: axis = ord(axis[1:2].lower())
|
||||
if axis is not None:
|
||||
axis = ord(axis[1:2].lower())
|
||||
|
||||
if action == '/set':
|
||||
if not 'position' in self.json:
|
||||
@@ -329,8 +263,10 @@ class HomeHandler(bbctrl.APIHandler):
|
||||
|
||||
self.get_ctrl().mach.home(axis, self.json['position'])
|
||||
|
||||
elif action == '/clear': self.get_ctrl().mach.unhome(axis)
|
||||
else: self.get_ctrl().mach.home(axis)
|
||||
elif action == '/clear':
|
||||
self.get_ctrl().mach.unhome(axis)
|
||||
else:
|
||||
self.get_ctrl().mach.home(axis)
|
||||
|
||||
|
||||
class StartHandler(bbctrl.APIHandler):
|
||||
@@ -386,7 +322,7 @@ class ModbusReadHandler(bbctrl.APIHandler):
|
||||
class ModbusWriteHandler(bbctrl.APIHandler):
|
||||
def put_ok(self):
|
||||
self.get_ctrl().mach.modbus_write(int(self.json['address']),
|
||||
int(self.json['value']))
|
||||
int(self.json['value']))
|
||||
|
||||
|
||||
class JogHandler(bbctrl.APIHandler):
|
||||
@@ -402,7 +338,8 @@ class JogHandler(bbctrl.APIHandler):
|
||||
last = self.app.last_jog.get(id, 0)
|
||||
self.app.last_jog[id] = ts
|
||||
|
||||
if ts < last: return # Out of order
|
||||
if ts < last:
|
||||
return # Out of order
|
||||
|
||||
self.get_ctrl().mach.jog(self.json)
|
||||
|
||||
@@ -413,17 +350,14 @@ class ClientConnection(object):
|
||||
self.app = app
|
||||
self.count = 0
|
||||
|
||||
|
||||
def heartbeat(self):
|
||||
self.timer = self.app.ioloop.call_later(3, self.heartbeat)
|
||||
self.send({'heartbeat': self.count})
|
||||
self.count += 1
|
||||
|
||||
|
||||
def send(self, msg): raise HTTPError(400, 'Not implemented')
|
||||
|
||||
|
||||
def on_open(self, id = None):
|
||||
def on_open(self, id=None):
|
||||
self.ctrl = self.app.get_ctrl(id)
|
||||
|
||||
self.ctrl.state.add_listener(self.send)
|
||||
@@ -432,7 +366,6 @@ class ClientConnection(object):
|
||||
self.heartbeat()
|
||||
self.app.opened(self.ctrl)
|
||||
|
||||
|
||||
def on_close(self):
|
||||
self.app.ioloop.remove_timeout(self.timer)
|
||||
self.ctrl.state.remove_listener(self.send)
|
||||
@@ -440,7 +373,6 @@ class ClientConnection(object):
|
||||
self.is_open = False
|
||||
self.app.closed(self.ctrl)
|
||||
|
||||
|
||||
def on_message(self, data):
|
||||
self.ctrl.mach.mdi(data)
|
||||
|
||||
@@ -465,23 +397,24 @@ class SockJSConnection(ClientConnection, sockjs.tornado.SockJSConnection):
|
||||
ClientConnection.__init__(self, session.server.app)
|
||||
sockjs.tornado.SockJSConnection.__init__(self, session)
|
||||
|
||||
|
||||
def send(self, msg):
|
||||
try:
|
||||
sockjs.tornado.SockJSConnection.send(self, msg)
|
||||
except:
|
||||
self.close()
|
||||
|
||||
|
||||
def on_open(self, info):
|
||||
cookie = info.get_cookie('client-id')
|
||||
if cookie is None: self.send(dict(sid = '')) # Trigger client reset
|
||||
if cookie is None:
|
||||
self.send(dict(sid='')) # Trigger client reset
|
||||
else:
|
||||
id = cookie.value
|
||||
|
||||
ip = info.ip
|
||||
if 'X-Real-IP' in info.headers: ip = info.headers['X-Real-IP']
|
||||
self.app.get_ctrl(id).log.get('Web').info('Connection from %s' % ip)
|
||||
if 'X-Real-IP' in info.headers:
|
||||
ip = info.headers['X-Real-IP']
|
||||
self.app.get_ctrl(id).log.get(
|
||||
'Web').info('Connection from %s' % ip)
|
||||
super().on_open(id)
|
||||
|
||||
|
||||
@@ -499,10 +432,13 @@ class Web(tornado.web.Application):
|
||||
|
||||
# Init camera
|
||||
if not args.disable_camera:
|
||||
if self.args.demo: log = bbctrl.log.Log(args, ioloop, 'camera.log')
|
||||
else: log = self.get_ctrl().log
|
||||
if self.args.demo:
|
||||
log = bbctrl.log.Log(args, ioloop, 'camera.log')
|
||||
else:
|
||||
log = self.get_ctrl().log
|
||||
self.camera = bbctrl.Camera(ioloop, args, log)
|
||||
else: self.camera = None
|
||||
else:
|
||||
self.camera = None
|
||||
|
||||
# Init controller
|
||||
if not self.args.demo:
|
||||
@@ -517,9 +453,7 @@ class Web(tornado.web.Application):
|
||||
(r'/api/reboot', RebootHandler),
|
||||
(r'/api/shutdown', ShutdownHandler),
|
||||
(r'/api/hostname', HostnameHandler),
|
||||
(r'/api/wifi', WifiHandler),
|
||||
(r'/api/remote/username', UsernameHandler),
|
||||
(r'/api/remote/password', PasswordHandler),
|
||||
(r'/api/network', NetworkHandler),
|
||||
(r'/api/config/load', ConfigLoadHandler),
|
||||
(r'/api/config/download', ConfigDownloadHandler),
|
||||
(r'/api/config/save', ConfigSaveHandler),
|
||||
@@ -547,7 +481,7 @@ class Web(tornado.web.Application):
|
||||
(r'/(.*)', StaticFileHandler,
|
||||
{'path': bbctrl.get_resource('http/'),
|
||||
'default_filename': 'index.html'}),
|
||||
]
|
||||
]
|
||||
|
||||
router = sockjs.tornado.SockJSRouter(SockJSConnection, '/sockjs')
|
||||
router.app = self
|
||||
@@ -555,7 +489,7 @@ class Web(tornado.web.Application):
|
||||
tornado.web.Application.__init__(self, router.urls + handlers)
|
||||
|
||||
try:
|
||||
self.listen(args.port, address = args.addr)
|
||||
self.listen(args.port, address=args.addr)
|
||||
|
||||
except Exception as e:
|
||||
raise Exception('Failed to bind %s:%d: %s' % (
|
||||
@@ -563,33 +497,32 @@ class Web(tornado.web.Application):
|
||||
|
||||
print('Listening on http://%s:%d/' % (args.addr, args.port))
|
||||
|
||||
|
||||
def opened(self, ctrl): ctrl.clear_timeout()
|
||||
|
||||
|
||||
def closed(self, ctrl):
|
||||
# Time out clients in demo mode
|
||||
if self.args.demo: ctrl.set_timeout(self._reap_ctrl, ctrl)
|
||||
|
||||
if self.args.demo:
|
||||
ctrl.set_timeout(self._reap_ctrl, ctrl)
|
||||
|
||||
def _reap_ctrl(self, ctrl):
|
||||
ctrl.close()
|
||||
del self.ctrls[ctrl.id]
|
||||
|
||||
|
||||
def get_ctrl(self, id = None):
|
||||
if not id or not self.args.demo: id = ''
|
||||
def get_ctrl(self, id=None):
|
||||
if not id or not self.args.demo:
|
||||
id = ''
|
||||
|
||||
if not id in self.ctrls:
|
||||
ctrl = bbctrl.Ctrl(self.args, self.ioloop, id)
|
||||
self.ctrls[id] = ctrl
|
||||
|
||||
else: ctrl = self.ctrls[id]
|
||||
else:
|
||||
ctrl = self.ctrls[id]
|
||||
|
||||
return ctrl
|
||||
|
||||
|
||||
# Override default logger
|
||||
|
||||
def log_request(self, handler):
|
||||
log = self.get_ctrl(handler.get_cookie('client-id')).log.get('Web')
|
||||
log.info("%d %s", handler.get_status(), handler._request_summary())
|
||||
|
||||
Reference in New Issue
Block a user