Added user apikeys, made ajax api available via apikeys, restified ajax api (WIP)
This commit is contained in:
parent
ae3e474c92
commit
e690053fd4
21 changed files with 730 additions and 311 deletions
|
|
@ -165,9 +165,15 @@ class GcodeManager:
|
|||
|
||||
#~~ file handling
|
||||
|
||||
def addFile(self, file, destination):
|
||||
from octoprint.filemanager.destinations import FileDestinations
|
||||
def addFile(self, file, destination, uploadCallback=None):
|
||||
"""
|
||||
Adds the given file for the given destination to the systems. Takes care of slicing if enabled and
|
||||
necessary.
|
||||
|
||||
If the file's processing won't be finished directly with the return from this method but happen
|
||||
asynchronously in the background (e.g. due to slicing), returns a tuple containing the just added file's
|
||||
filename and False. Otherwise returns a tuple (filename, True).
|
||||
"""
|
||||
if not file or not destination:
|
||||
return None, True
|
||||
|
||||
|
|
@ -183,14 +189,12 @@ class GcodeManager:
|
|||
file.save(absolutePath)
|
||||
|
||||
if gcode:
|
||||
return self.processGcode(absolutePath), True
|
||||
return self.processGcode(absolutePath, destination, uploadCallback), True
|
||||
else:
|
||||
local = (destination == FileDestinations.LOCAL)
|
||||
if curaEnabled and isSTLFileName(filename) and local:
|
||||
self.processStl(absolutePath)
|
||||
if curaEnabled and isSTLFileName(filename):
|
||||
self.processStl(absolutePath, destination, uploadCallback)
|
||||
return filename, False
|
||||
|
||||
|
||||
def getFutureFileName(self, file):
|
||||
if not file:
|
||||
return None
|
||||
|
|
@ -201,8 +205,7 @@ class GcodeManager:
|
|||
|
||||
return self._getBasicFilename(absolutePath)
|
||||
|
||||
|
||||
def processStl(self, absolutePath):
|
||||
def processStl(self, absolutePath, destination, uploadCallback=None):
|
||||
from octoprint.slicers.cura import CuraFactory
|
||||
|
||||
cura = CuraFactory.create_slicer()
|
||||
|
|
@ -210,6 +213,7 @@ class GcodeManager:
|
|||
config = self._settings.get(["cura", "config"])
|
||||
|
||||
slicingStart = time.time()
|
||||
|
||||
def stlProcessed(stlPath, gcodePath, error=None):
|
||||
if error:
|
||||
eventManager().fire("SlicingFailed", {"stl": self._getBasicFilename(stlPath), "gcode": self._getBasicFilename(gcodePath), "reason": error})
|
||||
|
|
@ -218,13 +222,12 @@ class GcodeManager:
|
|||
else:
|
||||
slicingStop = time.time()
|
||||
eventManager().fire("SlicingDone", {"stl": self._getBasicFilename(stlPath), "gcode": self._getBasicFilename(gcodePath), "time": "%.2f" % (slicingStop - slicingStart)})
|
||||
self.processGcode(gcodePath)
|
||||
self.processGcode(gcodePath, destination, uploadCallback)
|
||||
|
||||
eventManager().fire("SlicingStarted", {"stl": self._getBasicFilename(absolutePath), "gcode": self._getBasicFilename(gcodePath)})
|
||||
cura.process_file(config, gcodePath, absolutePath, stlProcessed, [absolutePath, gcodePath])
|
||||
|
||||
|
||||
def processGcode(self, absolutePath):
|
||||
def processGcode(self, absolutePath, destination, uploadCallback=None):
|
||||
if absolutePath is None:
|
||||
return None
|
||||
|
||||
|
|
@ -238,6 +241,8 @@ class GcodeManager:
|
|||
|
||||
self._metadataAnalyzer.addFileToQueue(os.path.basename(absolutePath))
|
||||
|
||||
if uploadCallback is not None:
|
||||
uploadCallback(filename, absolutePath, destination)
|
||||
return filename
|
||||
|
||||
def getFutureFilename(self, file):
|
||||
|
|
|
|||
|
|
@ -9,14 +9,14 @@ import copy
|
|||
import os
|
||||
import logging
|
||||
|
||||
#import logging, logging.config
|
||||
|
||||
import octoprint.util.comm as comm
|
||||
import octoprint.util as util
|
||||
|
||||
from octoprint.settings import settings
|
||||
from octoprint.events import eventManager
|
||||
|
||||
from octoprint.filemanager.destinations import FileDestinations
|
||||
|
||||
def getConnectionOptions():
|
||||
"""
|
||||
Retrieves the available ports, baudrates, prefered port and baudrate for connecting to the printer.
|
||||
|
|
@ -71,6 +71,8 @@ class Printer():
|
|||
self._sdPrinting = False
|
||||
self._sdStreaming = False
|
||||
self._sdFilelistAvailable = threading.Event()
|
||||
self._sdRemoteName = None
|
||||
self._streamingFinishedCallback = None
|
||||
|
||||
self._selectedFile = None
|
||||
|
||||
|
|
@ -455,9 +457,13 @@ class Printer():
|
|||
self._setProgressData(0.0, 0, 0, None)
|
||||
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
|
||||
|
||||
def mcFileTransferDone(self):
|
||||
def mcFileTransferDone(self, filename):
|
||||
self._sdStreaming = False
|
||||
|
||||
if self._streamingFinishedCallback is not None:
|
||||
self._streamingFinishedCallback(self._sdRemoteName, FileDestinations.SDCARD)
|
||||
|
||||
self._sdRemoteName = None
|
||||
self._setCurrentZ(None)
|
||||
self._setJobData(None, None, None)
|
||||
self._setProgressData(None, None, None, None)
|
||||
|
|
@ -473,35 +479,18 @@ class Printer():
|
|||
return []
|
||||
return self._comm.getSdFiles()
|
||||
|
||||
def addSdFile(self, filename, absolutePath):
|
||||
from octoprint.gcodefiles import isGcodeFileName
|
||||
from octoprint.gcodefiles import isSTLFileName
|
||||
|
||||
def addSdFile(self, filename, absolutePath, streamingFinishedCallback):
|
||||
if not self._comm or self._comm.isBusy() or not self._comm.isSdReady():
|
||||
logging.error("No connection to printer or printer is busy")
|
||||
return
|
||||
|
||||
if isGcodeFileName(filename):
|
||||
self.streamSdFile(filename, absolutePath)
|
||||
|
||||
if isSTLFileName(filename):
|
||||
gcodePath = util.genGcodeFileName(absolutePath)
|
||||
gcodeFileName = util.genGcodeFileName(filename)
|
||||
callBackArgs = [gcodeFileName, gcodePath]
|
||||
callBack = self.streamSdFile
|
||||
|
||||
self._gcodeManager.processStl(
|
||||
absolutePath, callBack, callBackArgs)
|
||||
|
||||
def streamSdFile(self, filename, path):
|
||||
if not self._comm or self._comm.isBusy() or not self._comm.isSdReady():
|
||||
return
|
||||
self._streamingFinishedCallback = streamingFinishedCallback
|
||||
|
||||
self.refreshSdFiles(blocking=True)
|
||||
existingSdFiles = self._comm.getSdFiles()
|
||||
|
||||
sdFilename = util.getDosFilename(filename, existingSdFiles)
|
||||
self._comm.startFileTransfer(path, sdFilename)
|
||||
self._sdRemoteName = util.getDosFilename(filename, existingSdFiles)
|
||||
self._comm.startFileTransfer(absolutePath, self._sdRemoteName)
|
||||
|
||||
def deleteSdFile(self, filename):
|
||||
if not self._comm or not self._comm.isSdReady():
|
||||
|
|
@ -569,6 +558,13 @@ class Printer():
|
|||
}
|
||||
}
|
||||
|
||||
def getCurrentConnection(self):
|
||||
if self._comm is None:
|
||||
return "Closed", None, None
|
||||
|
||||
port, baudrate = self._comm.getConnection()
|
||||
return self._comm.getStateString(), port, baudrate
|
||||
|
||||
def isClosedOrError(self):
|
||||
return self._comm is None or self._comm.isClosedOrError()
|
||||
|
||||
|
|
|
|||
|
|
@ -2,148 +2,255 @@
|
|||
__author__ = "Gina Häußge <osd@foosel.net>"
|
||||
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
|
||||
|
||||
from flask import request, jsonify
|
||||
from flask import request, jsonify, make_response
|
||||
|
||||
from octoprint.settings import settings
|
||||
from octoprint.settings import settings, valid_boolean_trues
|
||||
from octoprint.printer import getConnectionOptions
|
||||
from octoprint.server import printer, restricted_access, SUCCESS
|
||||
from octoprint.server.ajax import ajax
|
||||
|
||||
import octoprint.util as util
|
||||
|
||||
#~~ Printer control
|
||||
|
||||
|
||||
@ajax.route("/control/connection/options", methods=["GET"])
|
||||
@ajax.route("/control/connection", methods=["GET"])
|
||||
def connectionOptions():
|
||||
return jsonify(getConnectionOptions())
|
||||
state, port, baudrate = printer.getCurrentConnection()
|
||||
current = {
|
||||
"state": state,
|
||||
"port": port,
|
||||
"baudrate": baudrate
|
||||
}
|
||||
return jsonify({"current": current, "options": getConnectionOptions()})
|
||||
|
||||
|
||||
@ajax.route("/control/connection", methods=["POST"])
|
||||
@restricted_access
|
||||
def connect():
|
||||
if "command" in request.values.keys() and request.values["command"] == "connect":
|
||||
def connectionCommand():
|
||||
valid_commands = {
|
||||
"connect": ["autoconnect"],
|
||||
"disconnect": []
|
||||
}
|
||||
|
||||
command, data, response = util.getJsonCommandFromRequest(request, valid_commands)
|
||||
if response is not None:
|
||||
return response
|
||||
|
||||
if command == "connect":
|
||||
options = getConnectionOptions()
|
||||
|
||||
port = None
|
||||
baudrate = None
|
||||
if "port" in request.values.keys():
|
||||
port = request.values["port"]
|
||||
if "baudrate" in request.values.keys():
|
||||
baudrate = request.values["baudrate"]
|
||||
if "save" in request.values.keys():
|
||||
if "port" in data.keys():
|
||||
port = data["port"]
|
||||
if port not in options["ports"]:
|
||||
return make_response("Invalid port: %s" % port, 400)
|
||||
if "baudrate" in data.keys():
|
||||
baudrate = data["baudrate"]
|
||||
if baudrate not in options["baudrates"]:
|
||||
return make_response("Invalid baudrate: %d" % baudrate, 400)
|
||||
if "save" in data.keys() and data["save"]:
|
||||
settings().set(["serial", "port"], port)
|
||||
settings().setInt(["serial", "baudrate"], baudrate)
|
||||
settings().save()
|
||||
if "autoconnect" in request.values.keys():
|
||||
settings().setBoolean(["serial", "autoconnect"], True)
|
||||
settings().save()
|
||||
settings().setBoolean(["serial", "autoconnect"], data["autoconnect"])
|
||||
settings().save()
|
||||
printer.connect(port=port, baudrate=baudrate)
|
||||
elif "command" in request.values.keys() and request.values["command"] == "disconnect":
|
||||
elif command == "disconnect":
|
||||
printer.disconnect()
|
||||
|
||||
return jsonify(SUCCESS)
|
||||
|
||||
|
||||
@ajax.route("/control/command", methods=["POST"])
|
||||
@ajax.route("/control/printer/command", methods=["POST"])
|
||||
@restricted_access
|
||||
def printerCommand():
|
||||
if "application/json" in request.headers["Content-Type"]:
|
||||
data = request.json
|
||||
if not printer.isOperational():
|
||||
return make_response("Printer is not operational", 403)
|
||||
|
||||
parameters = {}
|
||||
if "parameters" in data.keys(): parameters = data["parameters"]
|
||||
if not "application/json" in request.headers["Content-Type"]:
|
||||
return make_response("Expected content type JSON", 400)
|
||||
|
||||
commands = []
|
||||
if "command" in data.keys(): commands = [data["command"]]
|
||||
elif "commands" in data.keys(): commands = data["commands"]
|
||||
data = request.json
|
||||
|
||||
commandsToSend = []
|
||||
for command in commands:
|
||||
commandToSend = command
|
||||
if len(parameters) > 0:
|
||||
commandToSend = command % parameters
|
||||
commandsToSend.append(commandToSend)
|
||||
parameters = {}
|
||||
if "parameters" in data.keys(): parameters = data["parameters"]
|
||||
|
||||
printer.commands(commandsToSend)
|
||||
commands = []
|
||||
if "command" in data.keys(): commands = [data["command"]]
|
||||
elif "commands" in data.keys(): commands = data["commands"]
|
||||
|
||||
commandsToSend = []
|
||||
for command in commands:
|
||||
commandToSend = command
|
||||
if len(parameters) > 0:
|
||||
commandToSend = command % parameters
|
||||
commandsToSend.append(commandToSend)
|
||||
|
||||
printer.commands(commandsToSend)
|
||||
|
||||
return jsonify(SUCCESS)
|
||||
|
||||
|
||||
@ajax.route("/control/job", methods=["POST"])
|
||||
@restricted_access
|
||||
def printJobControl():
|
||||
if "command" in request.values.keys():
|
||||
if request.values["command"] == "start":
|
||||
printer.startPrint()
|
||||
elif request.values["command"] == "pause":
|
||||
printer.togglePausePrint()
|
||||
elif request.values["command"] == "cancel":
|
||||
printer.cancelPrint()
|
||||
def controlJob():
|
||||
if not printer.isOperational():
|
||||
return make_response("Printer is not operational", 403)
|
||||
|
||||
valid_commands = {
|
||||
"start": [],
|
||||
"pause": [],
|
||||
"cancel": []
|
||||
}
|
||||
|
||||
command, data, response = util.getJsonCommandFromRequest(request, valid_commands)
|
||||
if response is not None:
|
||||
return response
|
||||
|
||||
if command == "start":
|
||||
printer.startPrint()
|
||||
elif command == "pause":
|
||||
printer.togglePausePrint()
|
||||
elif command == "cancel":
|
||||
printer.cancelPrint()
|
||||
return jsonify(SUCCESS)
|
||||
|
||||
|
||||
@ajax.route("/control/temperature", methods=["POST"])
|
||||
@ajax.route("/control/printer/hotend", methods=["POST"])
|
||||
@restricted_access
|
||||
def setTargetTemperature():
|
||||
if "temp" in request.values.keys():
|
||||
# set target temperature
|
||||
temp = request.values["temp"]
|
||||
printer.command("M104 S" + temp)
|
||||
def controlPrinterHotend():
|
||||
if not printer.isOperational():
|
||||
return make_response("Printer is not operational", 403)
|
||||
|
||||
if "bedTemp" in request.values.keys():
|
||||
# set target bed temperature
|
||||
bedTemp = request.values["bedTemp"]
|
||||
printer.command("M140 S" + bedTemp)
|
||||
valid_commands = {
|
||||
"temp": ["temps"],
|
||||
"offset": ["offsets"]
|
||||
}
|
||||
command, data, response = util.getJsonCommandFromRequest(request, valid_commands)
|
||||
if response is not None:
|
||||
return response
|
||||
|
||||
if "tempOffset" in request.values.keys():
|
||||
# set target temperature offset
|
||||
try:
|
||||
tempOffset = float(request.values["tempOffset"])
|
||||
if tempOffset >= -50 and tempOffset <= 50:
|
||||
printer.setTemperatureOffset(tempOffset, None)
|
||||
except:
|
||||
pass
|
||||
valid_targets = ["hotend", "bed"]
|
||||
|
||||
if "bedTempOffset" in request.values.keys():
|
||||
# set target bed temperature offset
|
||||
try:
|
||||
bedTempOffset = float(request.values["bedTempOffset"])
|
||||
if bedTempOffset >= -50 and bedTempOffset <= 50:
|
||||
printer.setTemperatureOffset(None, bedTempOffset)
|
||||
except:
|
||||
pass
|
||||
##~~ temperature
|
||||
if command == "temp":
|
||||
temps = data["temps"]
|
||||
|
||||
# make sure the targets are valid and the values are numbers
|
||||
validated_values = {}
|
||||
for type, value in temps.iteritems():
|
||||
if not type in valid_targets:
|
||||
return make_response("Invalid target for setting temperature: %s" % type, 400)
|
||||
if not isinstance(value, (int, long, float)):
|
||||
return make_response("Not a number for %s: %r" % (type, value), 400)
|
||||
validated_values[type] = value
|
||||
|
||||
# perform the actual temperature commands
|
||||
# TODO make this a generic method call (printer.setTemperature(type, value)) to get rid of gcode here
|
||||
if "hotend" in validated_values:
|
||||
printer.command("M104 S%f" % validated_values["hotend"])
|
||||
if "bed" in validated_values:
|
||||
printer.command("M140 S%f" % validated_values["bed"])
|
||||
|
||||
##~~ temperature offset
|
||||
elif command == "offset":
|
||||
offsets = data["offsets"]
|
||||
|
||||
# make sure the targets are valid, the values are numbers and in the range [-50, 50]
|
||||
validated_values = {}
|
||||
for type, value in offsets.iteritems():
|
||||
if not type in valid_targets:
|
||||
return make_response("Invalid target for setting temperature: %s" % type, 400)
|
||||
if not isinstance(value, (int, long, float)):
|
||||
return make_response("Not a number for %s: %r" % (type, value), 400)
|
||||
if not -50 <= value <= 50:
|
||||
return make_response("Offset %s not in range [-50, 50]: %f" % (type, value), 400)
|
||||
validated_values[type] = value
|
||||
|
||||
# set the offsets
|
||||
if "hotend" in validated_values and "bed" in validated_values:
|
||||
printer.setTemperatureOffset(validated_values["hotend"], validated_values["bed"])
|
||||
elif "hotend" in validated_values:
|
||||
printer.setTemperatureOffset(validated_values["hotend"], None)
|
||||
elif "bed" in validated_values:
|
||||
printer.setTemperatureOffset(None, validated_values["bed"])
|
||||
|
||||
return jsonify(SUCCESS)
|
||||
|
||||
|
||||
@ajax.route("/control/jog", methods=["POST"])
|
||||
@ajax.route("/control/printer/printhead", methods=["POST"])
|
||||
@restricted_access
|
||||
def jog():
|
||||
def controlPrinterPrinthead():
|
||||
if not printer.isOperational() or printer.isPrinting():
|
||||
# do not jog when a print job is running or we don't have a connection
|
||||
return jsonify(SUCCESS)
|
||||
return make_response("Printer is not operational or currently printing", 403)
|
||||
|
||||
(movementSpeedX, movementSpeedY, movementSpeedZ, movementSpeedE) = settings().get(["printerParameters", "movementSpeed", ["x", "y", "z", "e"]])
|
||||
if "x" in request.values.keys():
|
||||
# jog x
|
||||
x = request.values["x"]
|
||||
printer.commands(["G91", "G1 X%s F%d" % (x, movementSpeedX), "G90"])
|
||||
if "y" in request.values.keys():
|
||||
# jog y
|
||||
y = request.values["y"]
|
||||
printer.commands(["G91", "G1 Y%s F%d" % (y, movementSpeedY), "G90"])
|
||||
if "z" in request.values.keys():
|
||||
# jog z
|
||||
z = request.values["z"]
|
||||
printer.commands(["G91", "G1 Z%s F%d" % (z, movementSpeedZ), "G90"])
|
||||
if "homeXY" in request.values.keys():
|
||||
# home x/y
|
||||
printer.command("G28 X0 Y0")
|
||||
if "homeZ" in request.values.keys():
|
||||
# home z
|
||||
printer.command("G28 Z0")
|
||||
if "extrude" in request.values.keys():
|
||||
# extrude/retract
|
||||
length = request.values["extrude"]
|
||||
printer.commands(["G91", "G1 E%s F%d" % (length, movementSpeedE), "G90"])
|
||||
valid_commands = {
|
||||
"jog": [],
|
||||
"home": ["axes"]
|
||||
}
|
||||
command, data, response = util.getJsonCommandFromRequest(request, valid_commands)
|
||||
if response is not None:
|
||||
return response
|
||||
|
||||
movementSpeed = settings().get(["printerParameters", "movementSpeed", ["x", "y", "z"]], asdict=True)
|
||||
|
||||
valid_axes = ["x", "y", "z"]
|
||||
##~~ jog command
|
||||
if command == "jog":
|
||||
# validate all jog instructions, make sure that the values are numbers
|
||||
validated_values = {}
|
||||
for axis in valid_axes:
|
||||
if axis in data:
|
||||
value = data[axis]
|
||||
if not isinstance(value, (int, long, float)):
|
||||
return make_response("Not a number for axis %s: %r" % (axis, value), 400)
|
||||
validated_values[axis] = value
|
||||
|
||||
# execute the jog commands
|
||||
for axis, value in validated_values.iteritems():
|
||||
# TODO make this a generic method call (printer.jog(axis, value)) to get rid of gcode here
|
||||
printer.commands(["G91", "G1 %s%.4f F%d" % (axis.upper(), value, movementSpeed[axis]), "G90"])
|
||||
|
||||
##~~ home command
|
||||
elif command == "home":
|
||||
validated_values = []
|
||||
axes = data["axes"]
|
||||
for axis in axes:
|
||||
if not axis in valid_axes:
|
||||
return make_response("Invalid axis: %s" % axis, 400)
|
||||
validated_values.append(axis)
|
||||
|
||||
# execute the home command
|
||||
# TODO make this a generic method call (printer.home(axis, ...)) to get rid of gcode here
|
||||
printer.commands(["G91", "G1 %s" % " ".join(map(lambda x: "%s0" % x.upper(), validated_values)), "G90"])
|
||||
|
||||
return jsonify(SUCCESS)
|
||||
|
||||
|
||||
@ajax.route("/control/printer/feeder", methods=["POST"])
|
||||
@restricted_access
|
||||
def controlPrinterFeeder():
|
||||
if not printer.isOperational() or printer.isPrinting():
|
||||
# do not jog when a print job is running or we don't have a connection
|
||||
return make_response("Printer is not operational or currently printing", 403)
|
||||
|
||||
valid_commands = {
|
||||
"extrude": ["amount"]
|
||||
}
|
||||
command, data, response = util.getJsonCommandFromRequest(request, valid_commands)
|
||||
if response is not None:
|
||||
return response
|
||||
|
||||
extrusionSpeed = settings().get(["printerParameters", "movementSpeed", "e"])
|
||||
|
||||
if command == "extrude":
|
||||
amount = data["amount"]
|
||||
if not isinstance(amount, (int, long, float)):
|
||||
return make_response("Not a number for extrusion amount: %r" % amount, 400)
|
||||
|
||||
# TODO make this a generic method call (printer.extruder([hotend,] amount)) to get rid of gcode here
|
||||
printer.commands(["G91", "G1 E%s F%d" % (data["amount"], extrusionSpeed), "G90"])
|
||||
|
||||
return jsonify(SUCCESS)
|
||||
|
||||
|
|
@ -158,16 +265,23 @@ def getCustomControls():
|
|||
@restricted_access
|
||||
def sdCommand():
|
||||
if not settings().getBoolean(["feature", "sdSupport"]) or not printer.isOperational() or printer.isPrinting():
|
||||
return jsonify(SUCCESS)
|
||||
return make_response("SD support is disabled", 403)
|
||||
|
||||
if "command" in request.values.keys():
|
||||
command = request.values["command"]
|
||||
if command == "init":
|
||||
printer.initSdCard()
|
||||
elif command == "refresh":
|
||||
printer.refreshSdFiles()
|
||||
elif command == "release":
|
||||
printer.releaseSdCard()
|
||||
valid_commands = {
|
||||
"init": [],
|
||||
"refresh": [],
|
||||
"release": []
|
||||
}
|
||||
command, data, response = util.getJsonCommandFromRequest(request, valid_commands)
|
||||
if response is not None:
|
||||
return response
|
||||
|
||||
if command == "init":
|
||||
printer.initSdCard()
|
||||
elif command == "refresh":
|
||||
printer.refreshSdFiles()
|
||||
elif command == "release":
|
||||
printer.releaseSdCard()
|
||||
|
||||
return jsonify(SUCCESS)
|
||||
|
||||
|
|
|
|||
|
|
@ -18,33 +18,51 @@ from octoprint.server.ajax import ajax
|
|||
|
||||
@ajax.route("/gcodefiles", methods=["GET"])
|
||||
def readGcodeFiles():
|
||||
files = gcodeManager.getAllFileData()
|
||||
|
||||
sdFileList = printer.getSdFiles()
|
||||
if sdFileList is not None:
|
||||
for sdFile in sdFileList:
|
||||
files.append({
|
||||
"name": sdFile,
|
||||
"size": "n/a",
|
||||
"bytes": 0,
|
||||
"date": "n/a",
|
||||
"origin": "sd"
|
||||
})
|
||||
files = _getFileList(FileDestinations.LOCAL)
|
||||
files.extend(_getFileList(FileDestinations.SDCARD))
|
||||
return jsonify(files=files, free=util.getFormattedSize(util.getFreeBytes(settings().getBaseFolder("uploads"))))
|
||||
|
||||
|
||||
@ajax.route("/gcodefiles/<path:filename>", methods=["GET"])
|
||||
def readGcodeFile(filename):
|
||||
return redirectToTornado(request, url_for("index") + "downloads/gcode/" + filename)
|
||||
@ajax.route("/gcodefiles/<string:target>", methods=["GET"])
|
||||
def readGcodeFilesForTarget(target):
|
||||
if target not in [FileDestinations.LOCAL, FileDestinations.SDCARD]:
|
||||
return make_response("Invalid target: %s" % target, 400)
|
||||
|
||||
return jsonify(files=_getFileList(target), free=util.getFormattedSize(util.getFreeBytes(settings().getBaseFolder("uploads"))))
|
||||
|
||||
|
||||
@ajax.route("/gcodefiles/upload", methods=["POST"])
|
||||
def _getFileList(target):
|
||||
if target == FileDestinations.SDCARD:
|
||||
sdFileList = printer.getSdFiles()
|
||||
|
||||
files = []
|
||||
if sdFileList is not None:
|
||||
for sdFile in sdFileList:
|
||||
files.append({
|
||||
"name": sdFile,
|
||||
"size": "n/a",
|
||||
"bytes": 0,
|
||||
"date": "n/a",
|
||||
"origin": FileDestinations.SDCARD
|
||||
})
|
||||
else:
|
||||
files = gcodeManager.getAllFileData()
|
||||
return files
|
||||
|
||||
|
||||
@ajax.route("/gcodefiles/<string:target>", methods=["POST"])
|
||||
@restricted_access
|
||||
def uploadGcodeFile():
|
||||
def uploadGcodeFile(target):
|
||||
if not target in [FileDestinations.LOCAL, FileDestinations.SDCARD]:
|
||||
return make_response("Invalid target: %s" % target, 400)
|
||||
|
||||
if "gcode_file" in request.files.keys():
|
||||
file = request.files["gcode_file"]
|
||||
sd = "target" in request.values.keys() and request.values["target"] == "sd";
|
||||
sd = target == FileDestinations.SDCARD
|
||||
selectAfterUpload = "select" in request.values.keys() and request.values["select"] in valid_boolean_trues
|
||||
printAfterSelect = "print" in request.values.keys() and request.values["print"] in valid_boolean_trues
|
||||
|
||||
# determine current job
|
||||
currentFilename = None
|
||||
currentSd = None
|
||||
currentJob = printer.getCurrentJob()
|
||||
|
|
@ -52,82 +70,126 @@ def uploadGcodeFile():
|
|||
currentFilename = currentJob["filename"]
|
||||
currentSd = currentJob["sd"]
|
||||
|
||||
# determine future filename of file to be uploaded, abort if it can't be uploaded
|
||||
futureFilename = gcodeManager.getFutureFilename(file)
|
||||
if futureFilename is None or (not settings().getBoolean(["cura", "enabled"]) and not gcodefiles.isGcodeFileName(futureFilename)):
|
||||
return make_response("Can not upload file %s, wrong format?" % file.filename, 400)
|
||||
|
||||
# prohibit overwriting currently selected file while it's being printed
|
||||
if futureFilename == currentFilename and sd == currentSd and printer.isPrinting() or printer.isPaused():
|
||||
# trying to overwrite currently selected file, but it is being printed
|
||||
return make_response("Trying to overwrite file that is currently being printed: %s" % currentFilename, 403)
|
||||
|
||||
filename = None
|
||||
|
||||
def fileProcessingFinished(filename, absFilename, destination):
|
||||
"""
|
||||
Callback for when the file processing (upload, optional slicing, addition to analysis queue) has
|
||||
finished.
|
||||
|
||||
Depending on the file's destination triggers either streaming to SD card or directly calls selectOrPrint.
|
||||
"""
|
||||
sd = destination == FileDestinations.SDCARD
|
||||
if sd:
|
||||
printer.addSdFile(filename, absFilename, selectAndOrPrint)
|
||||
else:
|
||||
selectAndOrPrint(absFilename, destination)
|
||||
|
||||
def selectAndOrPrint(nameToSelect, destination):
|
||||
"""
|
||||
Callback for when the file is ready to be selected and optionally printed. For SD file uploads this only
|
||||
the case after they have finished streaming to the printer, which is why this callback is also used
|
||||
for the corresponding call to addSdFile.
|
||||
|
||||
Selects the just uploaded file if either selectAfterUpload or printAfterSelect are True, or if the
|
||||
exact file is already selected, such reloading it.
|
||||
"""
|
||||
sd = destination == FileDestinations.SDCARD
|
||||
if selectAfterUpload or printAfterSelect or (currentFilename == filename and currentSd == sd):
|
||||
printer.selectFile(nameToSelect, sd, printAfterSelect)
|
||||
|
||||
destination = FileDestinations.SDCARD if sd else FileDestinations.LOCAL
|
||||
|
||||
filename, done = gcodeManager.addFile(file, destination)
|
||||
|
||||
filename, done = gcodeManager.addFile(file, destination, fileProcessingFinished)
|
||||
if filename is None:
|
||||
return make_response("Could not upload the file %s" % file.filename, 500)
|
||||
|
||||
absFilename = gcodeManager.getAbsolutePath(filename)
|
||||
if sd:
|
||||
printer.addSdFile(filename, absFilename)
|
||||
|
||||
if currentFilename == filename and currentSd == sd:
|
||||
# reload file as it was updated
|
||||
if sd:
|
||||
printer.selectFile(filename, sd, False)
|
||||
else:
|
||||
printer.selectFile(absFilename, sd, False)
|
||||
|
||||
eventManager.fire("Upload", filename)
|
||||
return jsonify(files=gcodeManager.getAllFileData(), filename=filename, done=done)
|
||||
return jsonify(files=gcodeManager.getAllFileData(), filename=filename, done=done)
|
||||
else:
|
||||
return make_response("No gcode_file included", 400)
|
||||
|
||||
|
||||
@ajax.route("/gcodefiles/load", methods=["POST"])
|
||||
@ajax.route("/gcodefiles/local/<path:filename>", methods=["GET"])
|
||||
def readGcodeFile(filename):
|
||||
return redirectToTornado(request, url_for("index") + "downloads/gcode/" + filename)
|
||||
|
||||
|
||||
@ajax.route("/gcodefiles/<string:target>/<path:filename>", methods=["POST"])
|
||||
@restricted_access
|
||||
def loadGcodeFile():
|
||||
if "filename" in request.values.keys():
|
||||
def gcodeFileCommand(filename, target):
|
||||
if not target in [FileDestinations.LOCAL, FileDestinations.SDCARD]:
|
||||
return make_response("Invalid target: %s" % target, 400)
|
||||
|
||||
# valid file commands, dict mapping command name to mandatory parameters
|
||||
valid_commands = {
|
||||
"load": []
|
||||
}
|
||||
|
||||
command, data, response = util.getJsonCommandFromRequest(request, valid_commands)
|
||||
if response is not None:
|
||||
return response
|
||||
|
||||
if command == "load":
|
||||
# selects/loads a file
|
||||
printAfterLoading = False
|
||||
if "print" in request.values.keys() and request.values["print"] in valid_boolean_trues:
|
||||
if "print" in data.keys() and data["print"] in valid_boolean_trues:
|
||||
printAfterLoading = True
|
||||
|
||||
sd = False
|
||||
if "target" in request.values.keys() and request.values["target"] == "sd":
|
||||
filename = request.values["filename"]
|
||||
if target == FileDestinations.SDCARD:
|
||||
filenameToSelect = filename
|
||||
sd = True
|
||||
else:
|
||||
filename = gcodeManager.getAbsolutePath(request.values["filename"])
|
||||
printer.selectFile(filename, sd, printAfterLoading)
|
||||
return jsonify(SUCCESS)
|
||||
filenameToSelect = gcodeManager.getAbsolutePath(filename)
|
||||
printer.selectFile(filenameToSelect, sd, printAfterLoading)
|
||||
return jsonify(SUCCESS)
|
||||
|
||||
return make_response("Command %s is currently not implemented" % command, 400)
|
||||
|
||||
|
||||
@ajax.route("/gcodefiles/delete", methods=["POST"])
|
||||
@ajax.route("/gcodefiles/<string:target>/<path:filename>", methods=["DELETE"])
|
||||
@restricted_access
|
||||
def deleteGcodeFile():
|
||||
if "filename" in request.values.keys():
|
||||
filename = request.values["filename"]
|
||||
sd = "target" in request.values.keys() and request.values["target"] == "sd"
|
||||
def deleteGcodeFile(filename, target):
|
||||
if not target in [FileDestinations.LOCAL, FileDestinations.SDCARD]:
|
||||
return make_response("Invalid target: %s" % target, 400)
|
||||
|
||||
currentJob = printer.getCurrentJob()
|
||||
currentFilename = None
|
||||
currentSd = None
|
||||
if currentJob is not None and "filename" in currentJob.keys() and "sd" in currentJob.keys():
|
||||
currentFilename = currentJob["filename"]
|
||||
currentSd = currentJob["sd"]
|
||||
sd = target == FileDestinations.SDCARD
|
||||
|
||||
if currentFilename is not None and filename == currentFilename and not (printer.isPrinting() or printer.isPaused()):
|
||||
printer.unselectFile()
|
||||
currentJob = printer.getCurrentJob()
|
||||
currentFilename = None
|
||||
currentSd = None
|
||||
if currentJob is not None and "filename" in currentJob.keys() and "sd" in currentJob.keys():
|
||||
currentFilename = currentJob["filename"]
|
||||
currentSd = currentJob["sd"]
|
||||
|
||||
if not (currentFilename == filename and currentSd == sd and (printer.isPrinting() or printer.isPaused())):
|
||||
if sd:
|
||||
printer.deleteSdFile(filename)
|
||||
else:
|
||||
gcodeManager.removeFile(filename)
|
||||
if currentFilename is not None and filename == currentFilename and not (printer.isPrinting() or printer.isPaused()):
|
||||
printer.unselectFile()
|
||||
|
||||
if not (currentFilename == filename and currentSd == sd and (printer.isPrinting() or printer.isPaused())):
|
||||
if sd:
|
||||
printer.deleteSdFile(filename)
|
||||
else:
|
||||
gcodeManager.removeFile(filename)
|
||||
return readGcodeFiles()
|
||||
|
||||
|
||||
@ajax.route("/gcodefiles/refresh", methods=["POST"])
|
||||
@ajax.route("/gcodefiles/<string:target>/refresh", methods=["POST"])
|
||||
@restricted_access
|
||||
def refreshFiles():
|
||||
printer.updateSdFiles()
|
||||
def refreshFiles(target):
|
||||
if not target in [FileDestinations.LOCAL, FileDestinations.SDCARD]:
|
||||
return make_response("Invalid target: %s" % target, 400)
|
||||
|
||||
if target == FileDestinations.SDCARD:
|
||||
printer.updateSdFiles()
|
||||
|
||||
return jsonify(SUCCESS)
|
||||
|
||||
|
|
|
|||
|
|
@ -123,3 +123,34 @@ def changePasswordForUser(username):
|
|||
else:
|
||||
return make_response(("Forbidden", 403, []))
|
||||
|
||||
|
||||
@ajax.route("/users/<username>/apikey", methods=["DELETE"])
|
||||
@restricted_access
|
||||
def deleteApikeyForUser(username):
|
||||
if userManager is None:
|
||||
return jsonify(SUCCESS)
|
||||
|
||||
if current_user is not None and not current_user.is_anonymous() and (current_user.get_name() == username or current_user.is_admin()):
|
||||
try:
|
||||
userManager.deleteApikey(username)
|
||||
except users.UnknownUser:
|
||||
return make_response(("Unknown user: %s" % username, 404, []))
|
||||
return jsonify(SUCCESS)
|
||||
else:
|
||||
return make_response(("Forbidden", 403, []))
|
||||
|
||||
|
||||
@ajax.route("/users/<username>/apikey", methods=["POST"])
|
||||
@restricted_access
|
||||
def generateApikeyForUser(username):
|
||||
if userManager is None:
|
||||
return jsonify(SUCCESS)
|
||||
|
||||
if current_user is not None and not current_user.is_anonymous() and (current_user.get_name() == username or current_user.is_admin()):
|
||||
try:
|
||||
apikey = userManager.generateApiKey(username)
|
||||
except users.UnknownUser:
|
||||
return make_response(("Unknown user: %s" % username, 404, []))
|
||||
return jsonify({"apikey": apikey})
|
||||
else:
|
||||
return make_response(("Forbidden", 403, []))
|
||||
|
|
|
|||
|
|
@ -7,36 +7,30 @@ import logging
|
|||
from flask import Blueprint, request, jsonify, abort
|
||||
|
||||
from octoprint.server import printer, gcodeManager, SUCCESS
|
||||
from octoprint.settings import settings, valid_boolean_trues
|
||||
from octoprint.server.util import api_access
|
||||
from octoprint.settings import valid_boolean_trues
|
||||
from octoprint.filemanager.destinations import FileDestinations
|
||||
import octoprint.gcodefiles as gcodefiles
|
||||
|
||||
api = Blueprint("api", __name__)
|
||||
|
||||
#-- very simple api routines
|
||||
|
||||
|
||||
@api.route("/load", methods=["POST"])
|
||||
@api_access
|
||||
def apiLoad():
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if not settings().get(["api", "enabled"]):
|
||||
abort(401)
|
||||
|
||||
if not "apikey" in request.values.keys():
|
||||
abort(401)
|
||||
|
||||
if request.values["apikey"] != settings().get(["api", "key"]):
|
||||
abort(403)
|
||||
|
||||
if not "file" in request.files.keys():
|
||||
abort(400)
|
||||
|
||||
# Perform an upload
|
||||
file = request.files["file"]
|
||||
if not gcodefiles.isGcodeFileName(file.filename):
|
||||
f = request.files["file"]
|
||||
if not gcodefiles.isGcodeFileName(f.filename):
|
||||
abort(400)
|
||||
|
||||
destination = FileDestinations.LOCAL
|
||||
filename, done = gcodeManager.addFile(file, destination)
|
||||
filename, done = gcodeManager.addFile(f, destination)
|
||||
if filename is None:
|
||||
logger.warn("Upload via API failed")
|
||||
abort(500)
|
||||
|
|
@ -51,19 +45,11 @@ def apiLoad():
|
|||
return jsonify(SUCCESS)
|
||||
|
||||
@api.route("/state", methods=["GET"])
|
||||
@api_access
|
||||
def apiPrinterState():
|
||||
if not settings().get(["api", "enabled"]):
|
||||
abort(401)
|
||||
|
||||
if not "apikey" in request.values.keys():
|
||||
abort(401)
|
||||
|
||||
if request.values["apikey"] != settings().get(["api", "key"]):
|
||||
abort(403)
|
||||
|
||||
currentData = printer.getCurrentData()
|
||||
currentData.update({
|
||||
"temperatures": printer.getCurrentTemperatures()
|
||||
"temperatures": printer.getCurrentTemperatures()
|
||||
})
|
||||
return jsonify(currentData)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
from flask.ext.principal import identity_changed, Identity
|
||||
from tornado.web import StaticFileHandler, HTTPError
|
||||
from flask import url_for, make_response
|
||||
from flask.ext.login import login_required
|
||||
from flask import url_for, make_response, request, current_app
|
||||
from flask.ext.login import login_required, login_user
|
||||
from werkzeug.utils import redirect
|
||||
from sockjs.tornado import SockJSConnection
|
||||
|
||||
|
|
@ -15,32 +15,70 @@ import threading
|
|||
import logging
|
||||
from functools import wraps
|
||||
|
||||
from octoprint.server import userManager
|
||||
from octoprint.settings import settings
|
||||
import octoprint.timelapse
|
||||
import octoprint.server
|
||||
from octoprint.users import ApiUser
|
||||
|
||||
|
||||
def restricted_access(func):
|
||||
def restricted_access(func, apiEnabled=True):
|
||||
"""
|
||||
If you decorate a view with this, it will ensure that first setup has been
|
||||
done for OctoPrint's Access Control plus that any conditions of the
|
||||
login_required decorator are met.
|
||||
login_required decorator are met. It also allows to login using the masterkey or any
|
||||
of the user's apikeys if API access is enabled globally and for the decorated view.
|
||||
|
||||
If OctoPrint's Access Control has not been setup yet (indicated by the "firstRun"
|
||||
flag from the settings being set to True and the userManager not indicating
|
||||
that it's user database has been customized from default), the decorator
|
||||
will cause a HTTP 403 status code to be returned by the decorated resource.
|
||||
|
||||
If an API key is provided and it matches a known key, the user will be logged in and
|
||||
the view will be called directly. If the provided key doesn't match any known key,
|
||||
a HTTP 403 status code will be returned by the decorated resource.
|
||||
|
||||
Otherwise the result of calling login_required will be returned.
|
||||
"""
|
||||
@wraps(func)
|
||||
def decorated_view(*args, **kwargs):
|
||||
if settings().getBoolean(["server", "firstRun"]) and (userManager is None or not userManager.hasBeenCustomized()):
|
||||
# if OctoPrint hasn't been set up yet, abort
|
||||
if settings().getBoolean(["server", "firstRun"]) and (octoprint.server.userManager is None or not octoprint.server.userManager.hasBeenCustomized()):
|
||||
return make_response("OctoPrint isn't setup yet", 403)
|
||||
|
||||
# if API is globally enabled, enabled for this request and an api key is provided, try to use that
|
||||
if settings().get(["api", "enabled"]) and apiEnabled and "apikey" in request.values.keys():
|
||||
apikey = request.values["apikey"]
|
||||
user = None
|
||||
|
||||
if apikey == settings().get(["api", "key"]):
|
||||
# master key was used
|
||||
user = ApiUser()
|
||||
else:
|
||||
# user key might have been used
|
||||
user = octoprint.server.userManager.findUser(apikey=apikey)
|
||||
|
||||
if user is None:
|
||||
make_response("Invalid API key", 403)
|
||||
if login_user(user, remember=False):
|
||||
identity_changed.send(current_app._get_current_object(), identity=Identity(user.get_id()))
|
||||
return func(*args, **kwargs)
|
||||
|
||||
# call regular login_required decorator
|
||||
return login_required(func)(*args, **kwargs)
|
||||
return decorated_view
|
||||
|
||||
|
||||
def api_access(func):
|
||||
@wraps(func)
|
||||
def decorated_view(*args, **kwargs):
|
||||
if not settings().get(["api", "enabled"]):
|
||||
make_response("API disabled", 401)
|
||||
if not "apikey" in request.values.keys():
|
||||
make_response("No API key provided", 401)
|
||||
if request.values["apikey"] != settings().get(["api", "key"]):
|
||||
make_response("Invalid API key", 403)
|
||||
return func(args, kwargs)
|
||||
return decorated_view
|
||||
|
||||
#~~ Printer state
|
||||
|
||||
|
||||
|
|
@ -286,7 +324,7 @@ class ReverseProxied(object):
|
|||
|
||||
def redirectToTornado(request, target):
|
||||
requestUrl = request.url
|
||||
appBaseUrl = requestUrl[:requestUrl.find(url_for("ajax.base"))]
|
||||
appBaseUrl = requestUrl[:requestUrl.find(url_for("index") + "/ajax")]
|
||||
|
||||
redirectUrl = appBaseUrl + target
|
||||
if "?" in requestUrl:
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ class Settings(object):
|
|||
|
||||
#~~ getter
|
||||
|
||||
def get(self, path):
|
||||
def get(self, path, asdict=False):
|
||||
if len(path) == 0:
|
||||
return None
|
||||
|
||||
|
|
@ -211,17 +211,28 @@ class Settings(object):
|
|||
else:
|
||||
keys = k
|
||||
|
||||
results = []
|
||||
if asdict:
|
||||
results = {}
|
||||
else:
|
||||
results = []
|
||||
for key in keys:
|
||||
if key in config.keys():
|
||||
results.append(config[key])
|
||||
value = config[key]
|
||||
elif key in defaults:
|
||||
results.append(defaults[key])
|
||||
value = defaults[key]
|
||||
else:
|
||||
results.append(None)
|
||||
value = None
|
||||
|
||||
if asdict:
|
||||
results[key] = value
|
||||
else:
|
||||
results.append(value)
|
||||
|
||||
if not isinstance(k, (list, tuple)):
|
||||
return results.pop()
|
||||
if asdict:
|
||||
return results.values().pop()
|
||||
else:
|
||||
return results.pop()
|
||||
else:
|
||||
return results
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@ function ItemListHelper(listType, supportedSorting, supportedFilters, defaultSor
|
|||
|
||||
//~~ item handling
|
||||
|
||||
self.refresh = function() {
|
||||
self._updateItems();
|
||||
}
|
||||
|
||||
self.updateItems = function(items) {
|
||||
self.allItems = items;
|
||||
self._updateItems();
|
||||
|
|
|
|||
|
|
@ -86,9 +86,9 @@ $(function() {
|
|||
|
||||
function enable_local_dropzone() {
|
||||
$("#gcode_upload").fileupload({
|
||||
url: AJAX_BASEURL + "gcodefiles/local",
|
||||
dataType: "json",
|
||||
dropZone: localTarget,
|
||||
formData: {target: "local"},
|
||||
done: gcode_upload_done,
|
||||
fail: gcode_upload_fail,
|
||||
progressall: gcode_upload_progress
|
||||
|
|
@ -97,9 +97,9 @@ $(function() {
|
|||
|
||||
function disable_local_dropzone() {
|
||||
$("#gcode_upload").fileupload({
|
||||
url: AJAX_BASEURL + "gcodefiles/local",
|
||||
dataType: "json",
|
||||
dropZone: null,
|
||||
formData: {target: "local"},
|
||||
done: gcode_upload_done,
|
||||
fail: gcode_upload_fail,
|
||||
progressall: gcode_upload_progress
|
||||
|
|
@ -108,9 +108,9 @@ $(function() {
|
|||
|
||||
function enable_sd_dropzone() {
|
||||
$("#gcode_upload_sd").fileupload({
|
||||
url: AJAX_BASEURL + "gcodefiles/sdcard",
|
||||
dataType: "json",
|
||||
dropZone: $("#drop_sd"),
|
||||
formData: {target: "sd"},
|
||||
done: gcode_upload_done,
|
||||
fail: gcode_upload_fail,
|
||||
progressall: gcode_upload_progress
|
||||
|
|
@ -119,6 +119,7 @@ $(function() {
|
|||
|
||||
function disable_sd_dropzone() {
|
||||
$("#gcode_upload_sd").fileupload({
|
||||
url: AJAX_BASEURL + "gcodefiles/sdcard",
|
||||
dataType: "json",
|
||||
dropZone: null,
|
||||
formData: {target: "sd"},
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ function ConnectionViewModel(loginStateViewModel, settingsViewModel) {
|
|||
self.selectedPort = ko.observable(undefined);
|
||||
self.selectedBaudrate = ko.observable(undefined);
|
||||
self.saveSettings = ko.observable(undefined);
|
||||
self.autoconnect = ko.observable(undefined);
|
||||
|
||||
self.isErrorOrClosed = ko.observable(undefined);
|
||||
self.isOperational = ko.observable(undefined);
|
||||
|
|
@ -29,7 +30,7 @@ function ConnectionViewModel(loginStateViewModel, settingsViewModel) {
|
|||
|
||||
self.requestData = function() {
|
||||
$.ajax({
|
||||
url: AJAX_BASEURL + "control/connection/options",
|
||||
url: AJAX_BASEURL + "control/connection",
|
||||
method: "GET",
|
||||
dataType: "json",
|
||||
success: function(response) {
|
||||
|
|
@ -39,13 +40,18 @@ function ConnectionViewModel(loginStateViewModel, settingsViewModel) {
|
|||
}
|
||||
|
||||
self.fromResponse = function(response) {
|
||||
self.portOptions(response.ports);
|
||||
self.baudrateOptions(response.baudrates);
|
||||
var ports = response.options.ports;
|
||||
var baudrates = response.options.baudrates;
|
||||
var portPreference = response.options.portPreference;
|
||||
var baudratePreference = response.options.baudratePreference;
|
||||
|
||||
if (!self.selectedPort() && response.ports && response.ports.indexOf(response.portPreference) >= 0)
|
||||
self.selectedPort(response.portPreference);
|
||||
if (!self.selectedBaudrate() && response.baudrates && response.baudrates.indexOf(response.baudratePreference) >= 0)
|
||||
self.selectedBaudrate(response.baudratePreference);
|
||||
self.portOptions(ports);
|
||||
self.baudrateOptions(baudrates);
|
||||
|
||||
if (!self.selectedPort() && ports && ports.indexOf(portPreference) >= 0)
|
||||
self.selectedPort(portPreference);
|
||||
if (!self.selectedBaudrate() && baudrates && baudrates.indexOf(baudratePreference) >= 0)
|
||||
self.selectedBaudrate(baudratePreference);
|
||||
|
||||
self.saveSettings(false);
|
||||
}
|
||||
|
|
@ -86,7 +92,8 @@ function ConnectionViewModel(loginStateViewModel, settingsViewModel) {
|
|||
var data = {
|
||||
"command": "connect",
|
||||
"port": self.selectedPort(),
|
||||
"baudrate": self.selectedBaudrate()
|
||||
"baudrate": self.selectedBaudrate(),
|
||||
"autoconnect": self.settings.serial_autoconnect()
|
||||
};
|
||||
|
||||
if (self.saveSettings())
|
||||
|
|
@ -96,19 +103,20 @@ function ConnectionViewModel(loginStateViewModel, settingsViewModel) {
|
|||
url: AJAX_BASEURL + "control/connection",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
data: data
|
||||
})
|
||||
|
||||
self.settings.serial_port(self.selectedPort())
|
||||
self.settings.serial_baudrate(self.selectedBaudrate())
|
||||
self.settings.saveData();
|
||||
contentType: "application/json; charset=UTF-8",
|
||||
data: JSON.stringify(data),
|
||||
success: function(response) {
|
||||
self.settings.requestData()
|
||||
}
|
||||
});
|
||||
} else {
|
||||
self.requestData();
|
||||
$.ajax({
|
||||
url: AJAX_BASEURL + "control/connection",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
data: {"command": "disconnect"}
|
||||
contentType: "application/json; charset=UTF-8",
|
||||
data: JSON.stringify({"command": "disconnect"})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,26 +80,37 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) {
|
|||
self.sendJogCommand = function(axis, multiplier, distance) {
|
||||
if (typeof distance === "undefined")
|
||||
distance = $('#jog_distance button.active').data('distance');
|
||||
|
||||
if (self.settings.getPrinterInvertAxis(axis)) {
|
||||
multiplier *= -1;
|
||||
}
|
||||
|
||||
var data = {
|
||||
"command": "jog"
|
||||
}
|
||||
data[axis] = distance * multiplier;
|
||||
|
||||
$.ajax({
|
||||
url: AJAX_BASEURL + "control/jog",
|
||||
url: AJAX_BASEURL + "control/printer/printhead",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
data: axis + "=" + ( distance * multiplier )
|
||||
})
|
||||
contentType: "application/json; charset=UTF-8",
|
||||
data: JSON.stringify(data)
|
||||
});
|
||||
}
|
||||
|
||||
self.sendHomeCommand = function(axis) {
|
||||
var data = {
|
||||
"command": "home",
|
||||
"axes": axis
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: AJAX_BASEURL + "control/jog",
|
||||
url: AJAX_BASEURL + "control/printer/printhead",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
data: "home" + axis
|
||||
})
|
||||
contentType: "application/json; charset=UTF-8",
|
||||
data: JSON.stringify(data)
|
||||
});
|
||||
}
|
||||
|
||||
self.sendExtrudeCommand = function() {
|
||||
|
|
@ -114,12 +125,14 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) {
|
|||
var length = self.extrusionAmount();
|
||||
if (!length)
|
||||
length = 5;
|
||||
|
||||
$.ajax({
|
||||
url: AJAX_BASEURL + "control/jog",
|
||||
url: AJAX_BASEURL + "control/printer/feeder",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
data: "extrude=" + (dir * length)
|
||||
})
|
||||
contentType: "application/json; charset=UTF-8",
|
||||
data: JSON.stringify({command: "extrude", amount: (dir * length)})
|
||||
});
|
||||
}
|
||||
|
||||
self.sendCustomCommand = function(command) {
|
||||
|
|
@ -143,11 +156,11 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) {
|
|||
}
|
||||
}
|
||||
|
||||
if (!data)
|
||||
if (data === undefined)
|
||||
return;
|
||||
|
||||
$.ajax({
|
||||
url: AJAX_BASEURL + "control/command",
|
||||
url: AJAX_BASEURL + "control/printer/command",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: "application/json; charset=UTF-8",
|
||||
|
|
|
|||
|
|
@ -43,10 +43,10 @@ function GcodeFilesViewModel(printerStateViewModel, loginStateViewModel) {
|
|||
return !(file["prints"] && file["prints"]["success"] && file["prints"]["success"] > 0);
|
||||
},
|
||||
"sd": function(file) {
|
||||
return file["origin"] && file["origin"] == "sd";
|
||||
return file["origin"] && file["origin"] == "sdcard";
|
||||
},
|
||||
"local": function(file) {
|
||||
return !(file["origin"] && file["origin"] == "sd");
|
||||
return !(file["origin"] && file["origin"] == "sdcard");
|
||||
}
|
||||
},
|
||||
"name",
|
||||
|
|
@ -127,11 +127,19 @@ function GcodeFilesViewModel(printerStateViewModel, loginStateViewModel) {
|
|||
var file = self.listHelper.getItem(function(item) {return item.name == filename});
|
||||
if (!file) return;
|
||||
|
||||
var origin;
|
||||
if (file.origin === undefined) {
|
||||
origin = "local";
|
||||
} else {
|
||||
origin = file.origin;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: AJAX_BASEURL + "gcodefiles/load",
|
||||
url: AJAX_BASEURL + "gcodefiles/" + origin + "/" + filename,
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
data: {filename: filename, print: printAfterLoad, target: file.origin}
|
||||
contentType: "application/json; charset=UTF-8",
|
||||
data: JSON.stringify({command: "load", print: printAfterLoad})
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -139,11 +147,16 @@ function GcodeFilesViewModel(printerStateViewModel, loginStateViewModel) {
|
|||
var file = self.listHelper.getItem(function(item) {return item.name == filename});
|
||||
if (!file) return;
|
||||
|
||||
var origin;
|
||||
if (file.origin === undefined) {
|
||||
origin = "local";
|
||||
} else {
|
||||
origin = file.origin;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: AJAX_BASEURL + "gcodefiles/delete",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
data: {filename: filename, target: file.origin},
|
||||
url: AJAX_BASEURL + "gcodefiles/" + origin + "/" + filename,
|
||||
type: "DELETE",
|
||||
success: self.fromResponse
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,7 +147,8 @@ function PrinterStateViewModel(loginStateViewModel) {
|
|||
url: AJAX_BASEURL + "control/job",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
data: {command: command}
|
||||
contentType: "application/json; charset=UTF-8",
|
||||
data: JSON.stringify({command: command})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -167,47 +167,64 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) {
|
|||
self.setTempFromProfile = function(profile) {
|
||||
if (!profile)
|
||||
return;
|
||||
self._updateTemperature(profile.extruder, "temp");
|
||||
self._sendHotendCommand("temp", "hotend", profile.extruder);
|
||||
}
|
||||
|
||||
self.setTemp = function() {
|
||||
self._updateTemperature(self.newTemp(), "temp", function(){self.targetTemp(self.newTemp()); self.newTemp("");});
|
||||
self._sendHotendCommand("temp", "hotend", self.newTemp(), function() {self.targetTemp(self.newTemp()); self.newTemp("");});
|
||||
};
|
||||
|
||||
self.setTempToZero = function() {
|
||||
self._updateTemperature(0, "temp", function(){self.targetTemp(0); self.newTemp("");});
|
||||
self._sendHotendCommand("temp", "hotend", 0, function() {self.targetTemp(0); self.newTemp("");});
|
||||
}
|
||||
|
||||
self.setTempOffset = function() {
|
||||
self._updateTemperature(self.newTempOffset(), "tempOffset", function() {self.tempOffset(self.newTempOffset()); self.newTempOffset("");});
|
||||
self._sendHotendCommand("offset", "hotend", self.newTempOffset(), function() {self.tempOffset(self.newTempOffset()); self.newTempOffset("");});
|
||||
}
|
||||
|
||||
self.setBedTempFromProfile = function(profile) {
|
||||
self._updateTemperature(profile.bed, "bedTemp");
|
||||
if (!profile)
|
||||
return;
|
||||
self._sendHotendCommand("temp", "bed", profile.bed);
|
||||
}
|
||||
|
||||
self.setBedTemp = function() {
|
||||
self._updateTemperature(self.newBedTemp(), "bedTemp", function() {self.bedTargetTemp(self.newBedTemp()); self.newBedTemp("");});
|
||||
self._sendHotendCommand("temp", "bed", self.newBedTemp(), function() {self.bedTargetTemp(self.newBedTemp()); self.newBedTemp("");});
|
||||
};
|
||||
|
||||
self.setBedTempToZero = function() {
|
||||
self._updateTemperature(0, "bedTemp", function() {self.bedTargetTemp(0); self.newBedTemp("");});
|
||||
self._sendHotendCommand("temp", "bed", 0, function() {self.bedTargetTemp(0); self.newBedTemp("");});
|
||||
}
|
||||
|
||||
self.setBedTempOffset = function() {
|
||||
self._updateTemperature(self.newBedTempOffset(), "bedTempOffset", function() {self.bedTempOffset(self.newBedTempOffset()); self.newBedTempOffset("");});
|
||||
self._sendHotendCommand("offset", "bed", self.newBedTempOffset(), function() {self.bedTempOffset(self.newBedTempOffset()); self.newBedTempOffset("");});
|
||||
}
|
||||
|
||||
self._updateTemperature = function(temp, type, callback) {
|
||||
var data = {};
|
||||
data[type] = temp;
|
||||
self._sendHotendCommand = function(command, type, temp, callback) {
|
||||
var group;
|
||||
if ("temp" == command) {
|
||||
group = "temps";
|
||||
} else if ("offset" == command) {
|
||||
group = "offsets";
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
var data = {
|
||||
"command": command
|
||||
};
|
||||
data[group] = {};
|
||||
data[group][type] = parseInt(temp);
|
||||
|
||||
$.ajax({
|
||||
url: AJAX_BASEURL + "control/temperature",
|
||||
url: AJAX_BASEURL + "control/printer/hotend",
|
||||
type: "POST",
|
||||
data: data,
|
||||
dataType: "json",
|
||||
contentType: "application/json; charset=UTF-8",
|
||||
data: JSON.stringify(data),
|
||||
success: function() { if (callback !== undefined) callback(); }
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
self.handleEnter = function(event, type) {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ function UsersViewModel(loginStateViewModel) {
|
|||
self.editorUsername = ko.observable(undefined);
|
||||
self.editorPassword = ko.observable(undefined);
|
||||
self.editorRepeatedPassword = ko.observable(undefined);
|
||||
self.editorApikey = ko.observable(undefined);
|
||||
self.editorAdmin = ko.observable(undefined);
|
||||
self.editorActive = ko.observable(undefined);
|
||||
|
||||
|
|
@ -36,10 +37,12 @@ function UsersViewModel(loginStateViewModel) {
|
|||
self.editorUsername(undefined);
|
||||
self.editorAdmin(undefined);
|
||||
self.editorActive(undefined);
|
||||
self.editorApikey(undefined);
|
||||
} else {
|
||||
self.editorUsername(newValue.name);
|
||||
self.editorAdmin(newValue.admin);
|
||||
self.editorActive(newValue.active);
|
||||
self.editorApikey(newValue.apikey);
|
||||
}
|
||||
self.editorPassword(undefined);
|
||||
self.editorRepeatedPassword(undefined);
|
||||
|
|
@ -122,6 +125,27 @@ function UsersViewModel(loginStateViewModel) {
|
|||
});
|
||||
}
|
||||
|
||||
self.confirmGenerateApikey = function() {
|
||||
if (!CONFIG_ACCESS_CONTROL) return;
|
||||
|
||||
self.generateApikey(self.currentUser().name, function(response) {
|
||||
self._updateApikey(response.apikey);
|
||||
})
|
||||
}
|
||||
|
||||
self.confirmDeleteApikey = function() {
|
||||
if (!CONFIG_ACCESS_CONTROL) return;
|
||||
|
||||
self.deleteApikey(self.currentUser().name, function() {
|
||||
self._updateApikey(undefined);
|
||||
})
|
||||
}
|
||||
|
||||
self._updateApikey = function(apikey) {
|
||||
self.editorApikey(apikey);
|
||||
self.requestData();
|
||||
}
|
||||
|
||||
//~~ AJAX calls
|
||||
|
||||
self.addUser = function(user, callback) {
|
||||
|
|
@ -187,4 +211,24 @@ function UsersViewModel(loginStateViewModel) {
|
|||
success: callback
|
||||
});
|
||||
}
|
||||
|
||||
self.generateApikey = function(username, callback) {
|
||||
if (!CONFIG_ACCESS_CONTROL) return;
|
||||
|
||||
$.ajax({
|
||||
url: AJAX_BASEURL + "users/" + username + "/apikey",
|
||||
type: "POST",
|
||||
success: callback
|
||||
});
|
||||
}
|
||||
|
||||
self.deleteApikey = function(username, callback) {
|
||||
if (!CONFIG_ACCESS_CONTROL) return;
|
||||
|
||||
$.ajax({
|
||||
url: AJAX_BASEURL + "users/" + username + "/apikey",
|
||||
type: "DELETE",
|
||||
success: callback
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -210,18 +210,18 @@
|
|||
<span class="btn btn-primary fileinput-button span6" data-bind="css: {disabled: !$root.loginState.isUser()}" style="margin-bottom: 10px">
|
||||
<i class="icon-upload-alt icon-white"></i>
|
||||
<span>Upload</span>
|
||||
<input id="gcode_upload" type="file" name="gcode_file" class="fileinput-button" data-url="/ajax/gcodefiles/upload" data-bind="enable: loginState.isUser()">
|
||||
<input id="gcode_upload" type="file" name="gcode_file" class="fileinput-button" data-bind="enable: loginState.isUser()">
|
||||
</span>
|
||||
<span class="btn btn-primary fileinput-button span6" data-bind="css: {disabled: !$root.loginState.isUser() || !$root.isSdReady()}" style="margin-bottom: 10px">
|
||||
<i class="icon-upload-alt icon-white"></i>
|
||||
<span>Upload to SD</span>
|
||||
<input id="gcode_upload_sd" type="file" name="gcode_file" class="fileinput-button" data-url="/ajax/gcodefiles/upload" data-bind="enable: loginState.isUser() && isSdReady()">
|
||||
<input id="gcode_upload_sd" type="file" name="gcode_file" class="fileinput-button" data-bind="enable: loginState.isUser() && isSdReady()">
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="btn btn-primary fileinput-button span12" data-bind="css: {disabled: !$root.loginState.isUser()}" style="margin-bottom: 10px">
|
||||
<i class="icon-upload-alt icon-white"></i>
|
||||
<span>Upload</span>
|
||||
<input id="gcode_upload" type="file" name="gcode_file" class="fileinput-button" data-url="/ajax/gcodefiles/upload" data-bind="enable: loginState.isUser()">
|
||||
<input id="gcode_upload" type="file" name="gcode_file" class="fileinput-button" data-bind="enable: loginState.isUser()">
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
@ -352,7 +352,7 @@
|
|||
</div>
|
||||
<div>
|
||||
<button class="btn box pull-left" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendJogCommand('x',-1) }"><i class="icon-arrow-left"></i></button>
|
||||
<button class="btn box pull-left" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendHomeCommand('XY') }"><i class="icon-home"></i></button>
|
||||
<button class="btn box pull-left" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendHomeCommand(['x', 'y']) }"><i class="icon-home"></i></button>
|
||||
<button class="btn box pull-left" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendJogCommand('x',1) }"><i class="icon-arrow-right"></i></button>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -366,7 +366,7 @@
|
|||
<button class="btn box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendJogCommand('z',1) }"><i class="icon-arrow-up"></i></button>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendHomeCommand('Z') }"><i class="icon-home"></i></button>
|
||||
<button class="btn box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendHomeCommand(['z']) }"><i class="icon-home"></i></button>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendJogCommand('z',-1) }"><i class="icon-arrow-down"></i></button>
|
||||
|
|
|
|||
|
|
@ -382,7 +382,7 @@
|
|||
</thead>
|
||||
<tbody data-bind="foreach: users.listHelper.paginatedItems">
|
||||
<tr>
|
||||
<td class="settings_users_name" data-bind="text: name"></td>
|
||||
<td class="settings_users_name"><span data-bind="text: name"></span><span class="muted" data-bind="visible: $root.api_enabled() && apikey"><br /><small>Apikey: <span data-bind="text: apikey"></span></small></span></td>
|
||||
<td class="settings_users_active"><i data-bind="css: { 'icon-check': active, 'icon-check-empty': !active }"></i></td>
|
||||
<td class="settings_users_admin"><i data-bind="css: { 'icon-check': admin, 'icon-check-empty': !admin }"></i></td>
|
||||
<td class="settings_users_actions" class="system_users_action">
|
||||
|
|
@ -504,6 +504,20 @@
|
|||
<span class="help-inline" data-bind="visible: $root.users.editorPasswordMismatch()">Passwords do not match</span>
|
||||
</div>
|
||||
</div>
|
||||
<fieldset data-bind="visible: api_enabled">
|
||||
<legend>Apikey</legend>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Current Apikey</label>
|
||||
<div class="controls">
|
||||
<div class="input-append">
|
||||
<input type="text" class="input-block-level uneditable-input" data-bind="value: $root.users.editorApikey, attr: {placeholder: 'N/A'}">
|
||||
<a class="btn" title="Generate new Apikey" data-bind="click: function() { $root.users.confirmGenerateApikey(); }"><i class="icon-refresh"></i></a>
|
||||
<a class="btn btn-danger" title="Delete Apikey" data-bind="click: function() { $root.users.confirmDeleteApikey(); }"><i class="icon-trash"></i></a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
|
@ -511,6 +525,7 @@
|
|||
<button class="btn btn-primary" data-bind="click: function() { $root.users.confirmChangePassword(); }, enable: !$root.users.editorPasswordMismatch()">Confirm</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from flask.ext.principal import Identity
|
|||
import hashlib
|
||||
import os
|
||||
import yaml
|
||||
import uuid
|
||||
|
||||
from octoprint.settings import settings
|
||||
|
||||
|
|
@ -70,7 +71,10 @@ class FilebasedUserManager(UserManager):
|
|||
data = yaml.safe_load(f)
|
||||
for name in data.keys():
|
||||
attributes = data[name]
|
||||
self._users[name] = User(name, attributes["password"], attributes["active"], attributes["roles"])
|
||||
apikey = None
|
||||
if "apikey" in attributes:
|
||||
apikey = attributes["apikey"]
|
||||
self._users[name] = User(name, attributes["password"], attributes["active"], attributes["roles"], apikey)
|
||||
else:
|
||||
self._customized = False
|
||||
|
||||
|
|
@ -84,7 +88,8 @@ class FilebasedUserManager(UserManager):
|
|||
data[name] = {
|
||||
"password": user._passwordHash,
|
||||
"active": user._active,
|
||||
"roles": user._roles
|
||||
"roles": user._roles,
|
||||
"apikey": user._apikey
|
||||
}
|
||||
|
||||
with open(self._userfile, "wb") as f:
|
||||
|
|
@ -92,11 +97,11 @@ class FilebasedUserManager(UserManager):
|
|||
self._dirty = False
|
||||
self._load()
|
||||
|
||||
def addUser(self, username, password, active=False, roles=["user"]):
|
||||
def addUser(self, username, password, active=False, roles=["user"], apikey=None):
|
||||
if username in self._users.keys():
|
||||
raise UserAlreadyExists(username)
|
||||
|
||||
self._users[username] = User(username, UserManager.createPasswordHash(password), active, roles)
|
||||
self._users[username] = User(username, UserManager.createPasswordHash(password), active, roles, apikey)
|
||||
self._dirty = True
|
||||
self._save()
|
||||
|
||||
|
|
@ -154,6 +159,25 @@ class FilebasedUserManager(UserManager):
|
|||
self._dirty = True
|
||||
self._save()
|
||||
|
||||
def generateApiKey(self, username):
|
||||
if not username in self._users.keys():
|
||||
raise UnknownUser(username)
|
||||
|
||||
user = self._users[username]
|
||||
user._apikey = ''.join('%02X' % ord(z) for z in uuid.uuid4().bytes)
|
||||
self._dirty = True
|
||||
self._save()
|
||||
return user._apikey
|
||||
|
||||
def deleteApikey(self, username):
|
||||
if not username in self._users.keys():
|
||||
raise UnknownUser(username)
|
||||
|
||||
user = self._users[username]
|
||||
user._apikey = None
|
||||
self._dirty = True
|
||||
self._save()
|
||||
|
||||
def removeUser(self, username):
|
||||
if not username in self._users.keys():
|
||||
raise UnknownUser(username)
|
||||
|
|
@ -162,14 +186,19 @@ class FilebasedUserManager(UserManager):
|
|||
self._dirty = True
|
||||
self._save()
|
||||
|
||||
def findUser(self, username=None):
|
||||
if username is None:
|
||||
return None
|
||||
def findUser(self, username=None, apikey=None):
|
||||
if username is not None:
|
||||
if username not in self._users.keys():
|
||||
return None
|
||||
|
||||
if username not in self._users.keys():
|
||||
return self._users[username]
|
||||
elif apikey is not None:
|
||||
for user in self._users.values():
|
||||
if apikey == user._apikey:
|
||||
return user
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
|
||||
return self._users[username]
|
||||
|
||||
def getAllUsers(self):
|
||||
return map(lambda x: x.asDict(), self._users.values())
|
||||
|
|
@ -194,18 +223,20 @@ class UnknownRole(Exception):
|
|||
##~~ User object
|
||||
|
||||
class User(UserMixin):
|
||||
def __init__(self, username, passwordHash, active, roles):
|
||||
def __init__(self, username, passwordHash, active, roles, apikey=None):
|
||||
self._username = username
|
||||
self._passwordHash = passwordHash
|
||||
self._active = active
|
||||
self._roles = roles
|
||||
self._apikey = apikey
|
||||
|
||||
def asDict(self):
|
||||
return {
|
||||
"name": self._username,
|
||||
"active": self.is_active(),
|
||||
"admin": self.is_admin(),
|
||||
"user": self.is_user()
|
||||
"user": self.is_user(),
|
||||
"apikey": self._apikey
|
||||
}
|
||||
|
||||
def check_password(self, passwordHash):
|
||||
|
|
@ -241,3 +272,11 @@ class DummyIdentity(Identity):
|
|||
|
||||
def dummy_identity_loader():
|
||||
return DummyIdentity()
|
||||
|
||||
|
||||
##~~ Apiuser object to use when api key is used to access the API
|
||||
|
||||
|
||||
class ApiUser(User):
|
||||
def __init__(self):
|
||||
User.__init__(self, "api", "", True, UserManager.valid_roles)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import sys
|
|||
import time
|
||||
import re
|
||||
import tempfile
|
||||
from flask import make_response
|
||||
|
||||
from octoprint.settings import settings
|
||||
|
||||
|
|
@ -188,3 +189,20 @@ def silentRemove(file):
|
|||
os.remove(file)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def getJsonCommandFromRequest(request, valid_commands):
|
||||
if not "application/json" in request.headers["Content-Type"]:
|
||||
return None, None, make_response("Expected content-type JSON", 400)
|
||||
|
||||
data = request.json
|
||||
if not "command" in data.keys() or not data["command"] in valid_commands.keys():
|
||||
return None, None, make_response("Expected valid command", 400)
|
||||
|
||||
command = data["command"]
|
||||
for parameter in valid_commands[command]:
|
||||
if not parameter in data:
|
||||
return None, None, make_response("Mandatory parameter %s missing for command %s" % (parameter, command), 400)
|
||||
|
||||
return command, data, None
|
||||
|
||||
|
|
|
|||
|
|
@ -291,6 +291,9 @@ class MachineCom(object):
|
|||
def getOffsets(self):
|
||||
return (self._tempOffset, self._bedTempOffset)
|
||||
|
||||
def getConnection(self):
|
||||
return self._port, self._baudrate
|
||||
|
||||
##~~ external interface
|
||||
|
||||
def close(self, isError = False):
|
||||
|
|
@ -826,8 +829,8 @@ class MachineCom(object):
|
|||
self._sendCommand("M29")
|
||||
filename = self._currentFile.getFilename()
|
||||
self._currentFile = None
|
||||
self._callback.mcFileTransferDone()
|
||||
self._changeState(self.STATE_OPERATIONAL)
|
||||
self._callback.mcFileTransferDone(filename)
|
||||
eventManager().fire("TransferDone", filename)
|
||||
self.refreshSdFiles()
|
||||
else:
|
||||
|
|
|
|||
Loading…
Reference in a new issue