"Writing" backend methods now protected (need logged in user (or dummy user if access control is disabled) to perform action), most "writing" frontend controls disabled if no logged in (or dummy) user returned from backend.

master
Gina Häußge 2013-03-19 23:06:48 +01:00
parent b27e1ce15e
commit 1febcd671a
4 changed files with 224 additions and 163 deletions

View File

@ -138,11 +138,13 @@ def connect():
return jsonify(state="Connecting") return jsonify(state="Connecting")
@app.route(BASEURL + "control/disconnect", methods=["POST"]) @app.route(BASEURL + "control/disconnect", methods=["POST"])
@login_required
def disconnect(): def disconnect():
printer.disconnect() printer.disconnect()
return jsonify(state="Offline") return jsonify(state="Offline")
@app.route(BASEURL + "control/command", methods=["POST"]) @app.route(BASEURL + "control/command", methods=["POST"])
@login_required
def printerCommand(): def printerCommand():
if "application/json" in request.headers["Content-Type"]: if "application/json" in request.headers["Content-Type"]:
data = request.json data = request.json
@ -166,21 +168,25 @@ def printerCommand():
return jsonify(SUCCESS) return jsonify(SUCCESS)
@app.route(BASEURL + "control/print", methods=["POST"]) @app.route(BASEURL + "control/print", methods=["POST"])
@login_required
def printGcode(): def printGcode():
printer.startPrint() printer.startPrint()
return jsonify(SUCCESS) return jsonify(SUCCESS)
@app.route(BASEURL + "control/pause", methods=["POST"]) @app.route(BASEURL + "control/pause", methods=["POST"])
@login_required
def pausePrint(): def pausePrint():
printer.togglePausePrint() printer.togglePausePrint()
return jsonify(SUCCESS) return jsonify(SUCCESS)
@app.route(BASEURL + "control/cancel", methods=["POST"]) @app.route(BASEURL + "control/cancel", methods=["POST"])
@login_required
def cancelPrint(): def cancelPrint():
printer.cancelPrint() printer.cancelPrint()
return jsonify(SUCCESS) return jsonify(SUCCESS)
@app.route(BASEURL + "control/temperature", methods=["POST"]) @app.route(BASEURL + "control/temperature", methods=["POST"])
@login_required
def setTargetTemperature(): def setTargetTemperature():
if not printer.isOperational(): if not printer.isOperational():
return jsonify(SUCCESS) return jsonify(SUCCESS)
@ -198,6 +204,7 @@ def setTargetTemperature():
return jsonify(SUCCESS) return jsonify(SUCCESS)
@app.route(BASEURL + "control/jog", methods=["POST"]) @app.route(BASEURL + "control/jog", methods=["POST"])
@login_required
def jog(): def jog():
if not printer.isOperational() or printer.isPrinting(): if not printer.isOperational() or printer.isPrinting():
# do not jog when a print job is running or we don't have a connection # do not jog when a print job is running or we don't have a connection
@ -234,6 +241,7 @@ def getSpeedValues():
return jsonify(feedrate=printer.feedrateState()) return jsonify(feedrate=printer.feedrateState())
@app.route(BASEURL + "control/speed", methods=["POST"]) @app.route(BASEURL + "control/speed", methods=["POST"])
@login_required
def speed(): def speed():
if not printer.isOperational(): if not printer.isOperational():
return jsonify(SUCCESS) return jsonify(SUCCESS)
@ -261,6 +269,7 @@ def readGcodeFile(filename):
return send_from_directory(settings().getBaseFolder("uploads"), filename, as_attachment=True) return send_from_directory(settings().getBaseFolder("uploads"), filename, as_attachment=True)
@app.route(BASEURL + "gcodefiles/upload", methods=["POST"]) @app.route(BASEURL + "gcodefiles/upload", methods=["POST"])
@login_required
def uploadGcodeFile(): def uploadGcodeFile():
filename = None filename = None
if "gcode_file" in request.files.keys(): if "gcode_file" in request.files.keys():
@ -269,6 +278,7 @@ def uploadGcodeFile():
return jsonify(files=gcodeManager.getAllFileData(), filename=filename) return jsonify(files=gcodeManager.getAllFileData(), filename=filename)
@app.route(BASEURL + "gcodefiles/load", methods=["POST"]) @app.route(BASEURL + "gcodefiles/load", methods=["POST"])
@login_required
def loadGcodeFile(): def loadGcodeFile():
if "filename" in request.values.keys(): if "filename" in request.values.keys():
printAfterLoading = False printAfterLoading = False
@ -280,6 +290,7 @@ def loadGcodeFile():
return jsonify(SUCCESS) return jsonify(SUCCESS)
@app.route(BASEURL + "gcodefiles/delete", methods=["POST"]) @app.route(BASEURL + "gcodefiles/delete", methods=["POST"])
@login_required
def deleteGcodeFile(): def deleteGcodeFile():
if "filename" in request.values.keys(): if "filename" in request.values.keys():
filename = request.values["filename"] filename = request.values["filename"]
@ -318,6 +329,7 @@ def downloadTimelapse(filename):
return send_from_directory(settings().getBaseFolder("timelapse"), filename, as_attachment=True) return send_from_directory(settings().getBaseFolder("timelapse"), filename, as_attachment=True)
@app.route(BASEURL + "timelapse/<filename>", methods=["DELETE"]) @app.route(BASEURL + "timelapse/<filename>", methods=["DELETE"])
@login_required
def deleteTimelapse(filename): def deleteTimelapse(filename):
if util.isAllowedFile(filename, set(["mpg"])): if util.isAllowedFile(filename, set(["mpg"])):
secure = os.path.join(settings().getBaseFolder("timelapse"), secure_filename(filename)) secure = os.path.join(settings().getBaseFolder("timelapse"), secure_filename(filename))
@ -326,6 +338,7 @@ def deleteTimelapse(filename):
return getTimelapseData() return getTimelapseData()
@app.route(BASEURL + "timelapse/config", methods=["POST"]) @app.route(BASEURL + "timelapse/config", methods=["POST"])
@login_required
def setTimelapseConfig(): def setTimelapseConfig():
if request.values.has_key("type"): if request.values.has_key("type"):
type = request.values["type"] type = request.values["type"]
@ -389,6 +402,7 @@ def getSettings():
}) })
@app.route(BASEURL + "settings", methods=["POST"]) @app.route(BASEURL + "settings", methods=["POST"])
@login_required
def setSettings(): def setSettings():
if "application/json" in request.headers["Content-Type"]: if "application/json" in request.headers["Content-Type"]:
data = request.json data = request.json
@ -434,6 +448,7 @@ def setSettings():
#~~ system control #~~ system control
@app.route(BASEURL + "system", methods=["POST"]) @app.route(BASEURL + "system", methods=["POST"])
@login_required
def performSystemAction(): def performSystemAction():
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
if request.values.has_key("action"): if request.values.has_key("action"):
@ -467,15 +482,14 @@ def login():
user = userManager.findUser(username) user = userManager.findUser(username)
if user is not None: if user is not None:
passwordHash = users.UserManager.createPasswordHash(password) if user.check_password(users.UserManager.createPasswordHash(password)):
if passwordHash == user.passwordHash:
login_user(user, remember=remember) login_user(user, remember=remember)
return jsonify({"name": user.username, "roles": user.roles}) return jsonify({"name": user.get_name(), "user": user.is_user(), "admin": user.is_admin()})
return app.make_response(("User unknown or password incorrect", 401, [])) return app.make_response(("User unknown or password incorrect", 401, []))
elif "passive" in request.values.keys(): elif "passive" in request.values.keys():
user = current_user user = current_user
if user is not None and not user.is_anonymous(): if user is not None and not user.is_anonymous():
return jsonify({"name": user.username, "roles": user.roles}) return jsonify({"name": user.get_name(), "user": user.is_user(), "admin": user.is_admin()})
else: else:
return jsonify(SUCCESS) return jsonify(SUCCESS)
@ -488,8 +502,7 @@ def logout():
def load_user(id): def load_user(id):
if userManager is not None: if userManager is not None:
return userManager.findUser(id) return userManager.findUser(id)
else: return None
return users.DummyUser()
#~~ startup code #~~ startup code
class Server(): class Server():

View File

@ -1,8 +1,80 @@
//~~ View models //~~ View models
function ConnectionViewModel() { function LoginStateViewModel() {
var self = this; var self = this;
self.loggedIn = ko.observable(false);
self.username = ko.observable(undefined);
self.isAdmin = ko.observable(false);
self.isUser = ko.observable(false);
self.userMenuText = ko.computed(function() {
if (self.loggedIn()) {
return "\"" + self.username() + "\"";
} else {
return "Login";
}
})
self.requestData = function() {
$.ajax({
url: AJAX_BASEURL + "login",
type: "POST",
data: {"passive": true},
success: self.fromResponse
})
}
self.fromResponse = function(response) {
if (response && response.name) {
self.loggedIn(true);
self.username(response.name);
self.isUser(response.user);
self.isAdmin(response.admin);
} else {
self.loggedIn(false);
self.username(undefined);
self.isUser(false);
self.isAdmin(false);
}
}
self.login = function() {
var username = $("#login_user").val();
var password = $("#login_pass").val();
var remember = $("#login_remember").is(":checked");
$.ajax({
url: AJAX_BASEURL + "login",
type: "POST",
data: {"user": username, "pass": password, "remember": remember},
success: function(response) {
$.pnotify({title: "Login successful", text: "You are now logged in", type: "success"});
self.fromResponse(response);
},
error: function(jqXHR, textStatus, errorThrown) {
$.pnotify({title: "Login failed", text: "User unknown or wrong password", type: "error"});
}
})
}
self.logout = function() {
$.ajax({
url: AJAX_BASEURL + "logout",
type: "POST",
success: function(response) {
$.pnotify({title: "Logout successful", text: "You are now logged out", type: "success"});
self.fromResponse(response);
}
})
}
}
function ConnectionViewModel(loginStateViewModel) {
var self = this;
self.loginState = loginStateViewModel;
self.portOptions = ko.observableArray(undefined); self.portOptions = ko.observableArray(undefined);
self.baudrateOptions = ko.observableArray(undefined); self.baudrateOptions = ko.observableArray(undefined);
self.selectedPort = ko.observable(undefined); self.selectedPort = ko.observable(undefined);
@ -107,9 +179,11 @@ function ConnectionViewModel() {
} }
} }
function PrinterStateViewModel() { function PrinterStateViewModel(loginStateViewModel) {
var self = this; var self = this;
self.loginState = loginStateViewModel;
self.stateString = ko.observable(undefined); self.stateString = ko.observable(undefined);
self.isErrorOrClosed = ko.observable(undefined); self.isErrorOrClosed = ko.observable(undefined);
self.isOperational = ko.observable(undefined); self.isOperational = ko.observable(undefined);
@ -238,9 +312,11 @@ function PrinterStateViewModel() {
} }
} }
function TemperatureViewModel(settingsViewModel) { function TemperatureViewModel(loginStateViewModel, settingsViewModel) {
var self = this; var self = this;
self.loginState = loginStateViewModel;
self.temp = ko.observable(undefined); self.temp = ko.observable(undefined);
self.bedTemp = ko.observable(undefined); self.bedTemp = ko.observable(undefined);
self.targetTemp = ko.observable(undefined); self.targetTemp = ko.observable(undefined);
@ -412,9 +488,11 @@ function TemperatureViewModel(settingsViewModel) {
} }
} }
function ControlViewModel() { function ControlViewModel(loginStateViewModel) {
var self = this; var self = this;
self.loginState = loginStateViewModel;
self.isErrorOrClosed = ko.observable(undefined); self.isErrorOrClosed = ko.observable(undefined);
self.isOperational = ko.observable(undefined); self.isOperational = ko.observable(undefined);
self.isPrinting = ko.observable(undefined); self.isPrinting = ko.observable(undefined);
@ -567,9 +645,11 @@ function ControlViewModel() {
} }
function SpeedViewModel() { function SpeedViewModel(loginStateViewModel) {
var self = this; var self = this;
self.loginState = loginStateViewModel;
self.outerWall = ko.observable(undefined); self.outerWall = ko.observable(undefined);
self.innerWall = ko.observable(undefined); self.innerWall = ko.observable(undefined);
self.fill = ko.observable(undefined); self.fill = ko.observable(undefined);
@ -625,9 +705,11 @@ function SpeedViewModel() {
} }
} }
function TerminalViewModel() { function TerminalViewModel(loginStateViewModel) {
var self = this; var self = this;
self.loginState = loginStateViewModel;
self.log = []; self.log = [];
self.isErrorOrClosed = ko.observable(undefined); self.isErrorOrClosed = ko.observable(undefined);
@ -654,6 +736,7 @@ function TerminalViewModel() {
if (!self.log) if (!self.log)
self.log = [] self.log = []
self.log = self.log.concat(data) self.log = self.log.concat(data)
self.log = self.log.slice(-300)
self.updateOutput(); self.updateOutput();
} }
@ -690,9 +773,11 @@ function TerminalViewModel() {
} }
} }
function GcodeFilesViewModel() { function GcodeFilesViewModel(loginStateViewModel) {
var self = this; var self = this;
self.loginState = loginStateViewModel;
self.isErrorOrClosed = ko.observable(undefined); self.isErrorOrClosed = ko.observable(undefined);
self.isOperational = ko.observable(undefined); self.isOperational = ko.observable(undefined);
self.isPrinting = ko.observable(undefined); self.isPrinting = ko.observable(undefined);
@ -824,9 +909,11 @@ function GcodeFilesViewModel() {
} }
function TimelapseViewModel() { function TimelapseViewModel(loginStateViewModel) {
var self = this; var self = this;
self.loginState = loginStateViewModel;
self.timelapseType = ko.observable(undefined); self.timelapseType = ko.observable(undefined);
self.timelapseTimedInterval = ko.observable(undefined); self.timelapseTimedInterval = ko.observable(undefined);
@ -943,9 +1030,11 @@ function TimelapseViewModel() {
} }
} }
function GcodeViewModel() { function GcodeViewModel(loginStateViewModel) {
var self = this; var self = this;
self.loginState = loginStateViewModel;
self.loadedFilename = undefined; self.loadedFilename = undefined;
self.status = 'idle'; self.status = 'idle';
self.enabled = false; self.enabled = false;
@ -1006,9 +1095,11 @@ function GcodeViewModel() {
} }
function SettingsViewModel() { function SettingsViewModel(loginStateViewModel) {
var self = this; var self = this;
self.loginState = loginStateViewModel;
self.appearance_name = ko.observable(undefined); self.appearance_name = ko.observable(undefined);
self.appearance_color = ko.observable(undefined); self.appearance_color = ko.observable(undefined);
@ -1135,9 +1226,10 @@ function SettingsViewModel() {
} }
function NavigationViewModel(appearanceViewModel, settingsViewModel) { function NavigationViewModel(loginStateViewModel, appearanceViewModel, settingsViewModel) {
var self = this; var self = this;
self.loginState = loginStateViewModel;
self.appearance = appearanceViewModel; self.appearance = appearanceViewModel;
self.systemActions = settingsViewModel.system_actions; self.systemActions = settingsViewModel.system_actions;
@ -1166,9 +1258,10 @@ function NavigationViewModel(appearanceViewModel, settingsViewModel) {
} }
} }
function DataUpdater(connectionViewModel, printerStateViewModel, temperatureViewModel, controlViewModel, speedViewModel, terminalViewModel, gcodeFilesViewModel, timelapseViewModel, gcodeViewModel) { function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewModel, temperatureViewModel, controlViewModel, speedViewModel, terminalViewModel, gcodeFilesViewModel, timelapseViewModel, gcodeViewModel) {
var self = this; var self = this;
self.loginStateViewModel = loginStateViewModel;
self.connectionViewModel = connectionViewModel; self.connectionViewModel = connectionViewModel;
self.printerStateViewModel = printerStateViewModel; self.printerStateViewModel = printerStateViewModel;
self.temperatureViewModel = temperatureViewModel; self.temperatureViewModel = temperatureViewModel;
@ -1185,6 +1278,7 @@ function DataUpdater(connectionViewModel, printerStateViewModel, temperatureView
$("#offline_overlay").hide(); $("#offline_overlay").hide();
self.timelapseViewModel.requestData(); self.timelapseViewModel.requestData();
$("#webcam_image").attr("src", CONFIG_WEBCAM_STREAM + "?" + new Date().getTime()); $("#webcam_image").attr("src", CONFIG_WEBCAM_STREAM + "?" + new Date().getTime());
self.loginStateViewModel.requestData();
} }
}) })
self._socket.on("disconnect", function() { self._socket.on("disconnect", function() {
@ -1480,20 +1574,22 @@ function AppearanceViewModel(settingsViewModel) {
$(function() { $(function() {
//~~ View models //~~ View models
var connectionViewModel = new ConnectionViewModel(); var loginStateViewModel = new LoginStateViewModel();
var printerStateViewModel = new PrinterStateViewModel(); var connectionViewModel = new ConnectionViewModel(loginStateViewModel);
var settingsViewModel = new SettingsViewModel(); var printerStateViewModel = new PrinterStateViewModel(loginStateViewModel);
var settingsViewModel = new SettingsViewModel(loginStateViewModel);
var appearanceViewModel = new AppearanceViewModel(settingsViewModel); var appearanceViewModel = new AppearanceViewModel(settingsViewModel);
var temperatureViewModel = new TemperatureViewModel(settingsViewModel); var temperatureViewModel = new TemperatureViewModel(loginStateViewModel, settingsViewModel);
var controlViewModel = new ControlViewModel(); var controlViewModel = new ControlsViewModel(loginStateViewModel);
var speedViewModel = new SpeedViewModel(); var speedViewModel = new SpeedViewModel(loginStateViewModel);
var terminalViewModel = new TerminalViewModel(); var terminalViewModel = new TerminalViewModel(loginStateViewModel);
var gcodeFilesViewModel = new GcodeFilesViewModel(); var gcodeFilesViewModel = new GcodeFilesViewModel(loginStateViewModel);
var timelapseViewModel = new TimelapseViewModel(); var timelapseViewModel = new TimelapseViewModel(loginStateViewModel);
var gcodeViewModel = new GcodeViewModel(); var gcodeViewModel = new GcodeViewModel(loginStateViewModel);
var navigationViewModel = new NavigationViewModel(appearanceViewModel, settingsViewModel); var navigationViewModel = new NavigationViewModel(loginStateViewModel, appearanceViewModel, settingsViewModel);
var dataUpdater = new DataUpdater( var dataUpdater = new DataUpdater(
loginStateViewModel,
connectionViewModel, connectionViewModel,
printerStateViewModel, printerStateViewModel,
temperatureViewModel, temperatureViewModel,
@ -1505,7 +1601,8 @@ $(function() {
gcodeViewModel gcodeViewModel
); );
//work around a stupid iOS6 bug where ajax requests get cached and only work once, as described at http://stackoverflow.com/questions/12506897/is-safari-on-ios-6-caching-ajax-results // work around a stupid iOS6 bug where ajax requests get cached and only work once, as described at
// http://stackoverflow.com/questions/12506897/is-safari-on-ios-6-caching-ajax-results
$.ajaxSetup({ $.ajaxSetup({
type: 'POST', type: 'POST',
headers: { "cache-control": "no-cache" } headers: { "cache-control": "no-cache" }
@ -1547,6 +1644,7 @@ $(function() {
}) })
$('#tabs a[data-toggle="tab"]').on('shown', function (e) { $('#tabs a[data-toggle="tab"]').on('shown', function (e) {
temperatureViewModel.updatePlot(); temperatureViewModel.updatePlot();
terminalViewModel.updateOutput();
}); });
//~~ Speed controls //~~ Speed controls
@ -1618,71 +1716,6 @@ $(function() {
//~~ Offline overlay //~~ Offline overlay
$("#offline_overlay_reconnect").click(function() {dataUpdater.reconnect()}); $("#offline_overlay_reconnect").click(function() {dataUpdater.reconnect()});
//~~ Alert
/*
function displayAlert(text, timeout, type) {
var placeholder = $("#alert_placeholder");
var alertType = "";
if (type == "success" || type == "error" || type == "info") {
alertType = " alert-" + type;
}
placeholder.append($("<div id='activeAlert' class='alert " + alertType + " fade in' data-alert='alert'><p>" + text + "</p></div>"));
placeholder.fadeIn();
$("#activeAlert").delay(timeout).fadeOut("slow", function() {$(this).remove(); $("#alert_placeholder").hide();});
}
*/
//~~ Login/logout
$("#login_button").click(function() {
var username = $("#login_user").val();
var password = $("#login_pass").val();
var remember = $("#login_remember").is(":checked");
$.ajax({
url: AJAX_BASEURL + "login",
type: "POST",
data: {"user": username, "pass": password, "remember": remember},
success: function(response) {
$.pnotify({title: "Login successful", text: "You are now logged in", type: "success"});
$("#login_dropdown_text").text("\"" + response.name + "\"");
$("#login_dropdown_loggedout").removeClass("dropdown-menu").addClass("hide");
$("#login_dropdown_loggedin").removeClass("hide").addClass("dropdown-menu");
},
error: function(jqXHR, textStatus, errorThrown) {
$.pnotify({title: "Login failed", text: "User unknown or wrong password", type: "error"});
}
})
});
$("#logout_button").click(function(){
$.ajax({
url: AJAX_BASEURL + "logout",
type: "POST",
success: function(response) {
$.pnotify({title: "Logout successful", text: "You are now logged out", type: "success"});
$("#login_dropdown_text").text("Login");
$("#login_dropdown_loggedin").removeClass("dropdown-menu").addClass("hide");
$("#login_dropdown_loggedout").removeClass("hide").addClass("dropdown-menu");
}
})
})
$.ajax({
url: AJAX_BASEURL + "login",
type: "POST",
data: {"passive": true},
success: function(response) {
if (response["name"]) {
$("#login_dropdown_text").text("\"" + response.name + "\"");
$("#login_dropdown_loggedout").removeClass("dropdown-menu").addClass("hide");
$("#login_dropdown_loggedin").removeClass("hide").addClass("dropdown-menu");
}
}
})
//~~ knockout.js bindings //~~ knockout.js bindings
ko.bindingHandlers.popover = { ko.bindingHandlers.popover = {
@ -1717,14 +1750,15 @@ $(function() {
var timelapseElement = document.getElementById("timelapse"); var timelapseElement = document.getElementById("timelapse");
if (timelapseElement) { if (timelapseElement) {
ko.applyBindings(timelapseViewModel, document.getElementById("timelapse")); ko.applyBindings(timelapseViewModel, timelapseElement);
} }
var gCodeVisualizerElement = document.getElementById("gcode"); var gCodeVisualizerElement = document.getElementById("gcode");
if(gCodeVisualizerElement){ if (gCodeVisualizerElement) {
gcodeViewModel.initialize(); gcodeViewModel.initialize();
} }
//~~ startup commands //~~ startup commands
loginStateViewModel.requestData();
connectionViewModel.requestData(); connectionViewModel.requestData();
controlViewModel.requestData(); controlViewModel.requestData();
gcodeFilesViewModel.requestData(); gcodeFilesViewModel.requestData();
@ -1745,7 +1779,7 @@ $(function() {
$.pnotify.defaults.history = false; $.pnotify.defaults.history = false;
// Fix input element click problem // Fix input element click problem on login dialog
$('.dropdown input, .dropdown label').click(function(e) { $('.dropdown input, .dropdown label').click(function(e) {
e.stopPropagation(); e.stopPropagation();
}); });

View File

@ -33,9 +33,13 @@
<a class="brand" href="#"><img src="{{ url_for('static', filename='img/tentacle-20x20.png') }}"> <span data-bind="text: appearance.brand">OctoPrint</span></a> <a class="brand" href="#"><img src="{{ url_for('static', filename='img/tentacle-20x20.png') }}"> <span data-bind="text: appearance.brand">OctoPrint</span></a>
<div class="nav-collapse"> <div class="nav-collapse">
<ul class="nav pull-right"> <ul class="nav pull-right">
<li><a id="navbar_show_settings" class="pull-right" href="#settings_dialog"><i class="icon-wrench"></i> Settings</a></li> <li class="hide" data-bind="css: {hide: !loginState.isAdmin()}">
<a id="navbar_show_settings" class="pull-right" href="#settings_dialog">
<i class="icon-wrench"></i> Settings
</a>
</li>
{% if enableSystemMenu %} {% if enableSystemMenu %}
<li class="dropdown"> <li class="dropdown hide" data-bind="css: {hide: !loginState.isAdmin()}">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="icon-off"></i> System <i class="icon-off"></i> System
<b class="caret"></b> <b class="caret"></b>
@ -48,10 +52,10 @@
{% if enableAccessControl %} {% if enableAccessControl %}
<li class="dropdown"> <li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="icon-user"></i> <span id="login_dropdown_text">Login</span> <i class="icon-user"></i> <span data-bind="text: loginState.userMenuText">Login</span>
<b class="caret"></b> <b class="caret"></b>
</a> </a>
<div id="login_dropdown_loggedout" class="dropdown-menu" style="padding: 15px"> <div id="login_dropdown_loggedout" style="padding: 15px" class="dropdown-menu" data-bind="css: {hide: loginState.loggedIn(), 'dropdown-menu': !loginState.loggedIn()}">
<label for="login_user">Username</label> <label for="login_user">Username</label>
<input type="text" id="login_user" placeholder="Username"> <input type="text" id="login_user" placeholder="Username">
<label for="login_pass">Password</label> <label for="login_pass">Password</label>
@ -59,12 +63,10 @@
<label class="checkbox"> <label class="checkbox">
<input type="checkbox" id="login_remember"> Remember me <input type="checkbox" id="login_remember"> Remember me
</label> </label>
<button class="btn btn-block btn-primary" id="login_button">Login</button> <button class="btn btn-block btn-primary" id="login_button" data-bind="click: loginState.login">Login</button>
</div> </div>
<ul id="login_dropdown_loggedin" class="hide"> <ul id="login_dropdown_loggedin" class="hide" data-bind="css: {hide: !loginState.loggedIn(), 'dropdown-menu': loginState.loggedIn()}">
<li><a href="#">Profile</a></li> <li><a href="#" id="logout_button" data-bind="click: loginState.logout">Logout</a></li>
<li class="divider"></li>
<li><a href="#" id="logout_button">Logout</a></li>
</ul> </ul>
</li> </li>
{% endif %} {% endif %}
@ -82,14 +84,14 @@
</div> </div>
<div class="accordion-body collapse in" id="connection"> <div class="accordion-body collapse in" id="connection">
<div class="accordion-inner"> <div class="accordion-inner">
<label for="connection_ports" data-bind="css: {disabled: !isErrorOrClosed}, enable: isErrorOrClosed">Serial Port</label> <label for="connection_ports" data-bind="css: {disabled: !isErrorOrClosed}, enable: isErrorOrClosed && loginState.isUser">Serial Port</label>
<select id="connection_ports" data-bind="options: portOptions, optionsCaption: 'AUTO', value: selectedPort, css: {disabled: !isErrorOrClosed}, enable: isErrorOrClosed"></select> <select id="connection_ports" data-bind="options: portOptions, optionsCaption: 'AUTO', value: selectedPort, css: {disabled: !isErrorOrClosed}, enable: isErrorOrClosed && loginState.isUser"></select>
<label for="connection_baudrates" data-bind="css: {disabled: !isErrorOrClosed}, enable: isErrorOrClosed">Baudrate</label> <label for="connection_baudrates" data-bind="css: {disabled: !isErrorOrClosed}, enable: isErrorOrClosed && loginState.isUser">Baudrate</label>
<select id="connection_baudrates" data-bind="options: baudrateOptions, optionsCaption: 'AUTO', value: selectedBaudrate, css: {disabled: !isErrorOrClosed}, enable: isErrorOrClosed"></select> <select id="connection_baudrates" data-bind="options: baudrateOptions, optionsCaption: 'AUTO', value: selectedBaudrate, css: {disabled: !isErrorOrClosed}, enable: isErrorOrClosed && loginState.isUser"></select>
<label class="checkbox"> <label class="checkbox">
<input type="checkbox" id="connection_save" data-bind="checked: saveSettings, css: {disabled: !isErrorOrClosed}, enable: isErrorOrClosed"> Save connection settings <input type="checkbox" id="connection_save" data-bind="checked: saveSettings, css: {disabled: !isErrorOrClosed}, enable: isErrorOrClosed && loginState.isUser"> Save connection settings
</label> </label>
<button class="btn btn-block" id="printer_connect" data-bind="click: connect, text: buttonText()">Connect</button> <button class="btn btn-block" id="printer_connect" data-bind="click: connect, text: buttonText(), enable: loginState.isUser">Connect</button>
</div> </div>
</div> </div>
</div> </div>
@ -113,9 +115,9 @@
</div> </div>
<div class="row-fluid print-control"> <div class="row-fluid print-control">
<button class="btn btn-primary span4" data-bind="click: print, enable: isOperational() && isReady() && !isPrinting(), css: {'btn-danger': isPaused()}" id="job_print"><i class="icon-white" data-bind="css: {'icon-print': !isPaused(), 'icon-undo': isPaused()}"></i> <span data-bind="text: (isPaused() ? 'Restart' : 'Print')">Print</span></button> <button class="btn btn-primary span4" data-bind="click: print, enable: isOperational() && isReady() && !isPrinting() && loginState.isUser(), css: {'btn-danger': isPaused()}" id="job_print"><i class="icon-white" data-bind="css: {'icon-print': !isPaused(), 'icon-undo': isPaused()}"></i> <span data-bind="text: (isPaused() ? 'Restart' : 'Print')">Print</span></button>
<button class="btn span4" id="job_pause" data-bind="click: pause, enable: isPrinting() || isPaused(), css: {active: isPaused()}"><i class="icon-pause"></i> <span>Pause</span></button> <button class="btn span4" id="job_pause" data-bind="click: pause, enable: isOperational() && (isPrinting() || isPaused()) && loginState.isUser(), css: {active: isPaused()}"><i class="icon-pause"></i> <span>Pause</span></button>
<button class="btn span4" id="job_cancel" data-bind="click: cancel, enable: isPrinting() || isPaused()"><i class="icon-stop"></i> Cancel</button> <button class="btn span4" id="job_cancel" data-bind="click: cancel, enable: isOperational() && (isPrinting() || isPaused()) && loginState.isUser()"><i class="icon-stop"></i> Cancel</button>
</div> </div>
</div> </div>
</div> </div>
@ -209,12 +211,12 @@
<label for="temp_newTemp">New Target</label> <label for="temp_newTemp">New Target</label>
<div class="input-append"> <div class="input-append">
<input type="text" id="temp_newTemp" data-bind="attr: {placeholder: targetTemp}" class="tempInput"> <input type="text" id="temp_newTemp" data-bind="attr: {placeholder: targetTemp}, enable: isOperational() && loginState.isUser()" class="tempInput">
<span class="add-on">&deg;C</span> <span class="add-on">&deg;C</span>
</div> </div>
<div class="btn-group"> <div class="btn-group">
<button type="submit" class="btn" id="temp_newTemp_set">Set</button> <button type="submit" class="btn" id="temp_newTemp_set" data-bind="enable: isOperational() && loginState.isUser()">Set</button>
<button class="btn dropdown-toggle" data-toggle="dropdown"> <button class="btn dropdown-toggle" data-toggle="dropdown" data-bind="enable: isOperational() && loginState.isUser()">
<span class="caret"></span> <span class="caret"></span>
</button> </button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
@ -239,12 +241,12 @@
<label for="temp_newBedTemp">New Target</label> <label for="temp_newBedTemp">New Target</label>
<div class="input-append"> <div class="input-append">
<input type="text" id="temp_newBedTemp" data-bind="attr: {placeholder: bedTargetTemp}" class="tempInput"> <input type="text" id="temp_newBedTemp" data-bind="attr: {placeholder: bedTargetTemp}, enable: isOperational() && loginState.isUser()" class="tempInput">
<span class="add-on">&deg;C</span> <span class="add-on">&deg;C</span>
</div> </div>
<div class="btn-group"> <div class="btn-group">
<button type="submit" class="btn" id="temp_newBedTemp_set">Set</button> <button type="submit" class="btn" id="temp_newBedTemp_set" data-bind="enable: isOperational() && loginState.isUser()">Set</button>
<button class="btn dropdown-toggle" data-toggle="dropdown"> <button class="btn dropdown-toggle" data-toggle="dropdown" data-bind="enable: isOperational() && loginState.isUser()">
<span class="caret"></span> <span class="caret"></span>
</button> </button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
@ -274,37 +276,37 @@
<div class="jog-panel"> <div class="jog-panel">
<h1>X/Y</h1> <h1>X/Y</h1>
<div> <div>
<button class="btn box" data-bind="enable: isOperational() && !isPrinting(), click: function() { $root.sendJogCommand('y',1) }"><i class="icon-arrow-up"></i></button> <button class="btn box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendJogCommand('y',1) }"><i class="icon-arrow-up"></i></button>
</div> </div>
<div> <div>
<button class="btn box pull-left" data-bind="enable: isOperational() && !isPrinting(), 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.sendJogCommand('x',-1) }"><i class="icon-arrow-left"></i></button>
<button class="btn box pull-left" data-bind="enable: isOperational() && !isPrinting(), 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('XY') }"><i class="icon-home"></i></button>
<button class="btn box pull-left" data-bind="enable: isOperational() && !isPrinting(), click: function() { $root.sendJogCommand('x',1) }"><i class="icon-arrow-right"></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>
<div> <div>
<button class="btn box" data-bind="enable: isOperational() && !isPrinting(), click: function() { $root.sendJogCommand('y',-1) }"><i class="icon-arrow-down"></i></button> <button class="btn box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendJogCommand('y',-1) }"><i class="icon-arrow-down"></i></button>
</div> </div>
</div> </div>
<!-- Z jogging control panel --> <!-- Z jogging control panel -->
<div class="jog-panel"> <div class="jog-panel">
<h1>Z</h1> <h1>Z</h1>
<div> <div>
<button class="btn box" data-bind="enable: isOperational() && !isPrinting(), click: function() { $root.sendJogCommand('z',1) }"><i class="icon-arrow-up"></i></button> <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>
<div> <div>
<button class="btn box" data-bind="enable: isOperational() && !isPrinting(), 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>
<div> <div>
<button class="btn box" data-bind="enable: isOperational() && !isPrinting(), click: function() { $root.sendJogCommand('z',-1) }"><i class="icon-arrow-down"></i></button> <button class="btn box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendJogCommand('z',-1) }"><i class="icon-arrow-down"></i></button>
</div> </div>
</div> </div>
<!-- Jog distance --> <!-- Jog distance -->
<div class="distance"> <div class="distance">
<div class="btn-group" data-toggle="buttons-radio" id="jog_distance"> <div class="btn-group" data-toggle="buttons-radio" id="jog_distance">
<button type="button" class="btn" data-distance="0.1">0.1</button> <button type="button" class="btn" data-distance="0.1" data-bind="enable: loginState.isUser()">0.1</button>
<button type="button" class="btn" data-distance="1">1</button> <button type="button" class="btn" data-distance="1" data-bind="enable: loginState.isUser()">1</button>
<button type="button" class="btn active" data-distance="10">10</button> <button type="button" class="btn active" data-distance="10" data-bind="enable: loginState.isUser()">10</button>
<button type="button" class="btn" data-distance="100">100</button> <button type="button" class="btn" data-distance="100" data-bind="enable: loginState.isUser()">100</button>
</div> </div>
</div> </div>
</div> </div>
@ -313,20 +315,20 @@
<h1>E</h1> <h1>E</h1>
<div> <div>
<div class="input-append control-box"> <div class="input-append control-box">
<input type="text" class="input-mini text-right" data-bind="value: extrusionAmount, enable: isOperational() && !isPrinting(), attr: {placeholder: 5}"> <input type="text" class="input-mini text-right" data-bind="value: extrusionAmount, enable: isOperational() && !isPrinting() && loginState.isUser(), attr: {placeholder: 5}">
<span class="add-on">mm</span> <span class="add-on">mm</span>
</div> </div>
<button class="btn control-box" data-bind="enable: isOperational() && !isPrinting(), click: function() { $root.sendExtrudeCommand() }">Extrude</button> <button class="btn control-box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendExtrudeCommand() }">Extrude</button>
<button class="btn control-box" data-bind="enable: isOperational() && !isPrinting(), click: function() { $root.sendRetractCommand() }">Retract</button> <button class="btn control-box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendRetractCommand() }">Retract</button>
</div> </div>
</div> </div>
<!-- General control panel --> <!-- General control panel -->
<div class="jog-panel"> <div class="jog-panel">
<h1>General</h1> <h1>General</h1>
<div> <div>
<button class="btn control-box" data-bind="enable: isOperational() && !isPrinting(), click: function() { $root.sendCustomCommand({type:'command',command:'M18'}) }"><i class="icon-off"></i>&nbsp;Motors off</button> <button class="btn control-box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendCustomCommand({type:'command',command:'M18'}) }"><i class="icon-off"></i>&nbsp;Motors off</button>
<button class="btn control-box" data-bind="enable: isOperational(), click: function() { $root.sendCustomCommand({type:'command',command:'M106'}) }">Fans on</button> <button class="btn control-box" data-bind="enable: isOperational() && loginState.isUser(), click: function() { $root.sendCustomCommand({type:'command',command:'M106'}) }">Fans on</button>
<button class="btn control-box" data-bind="enable: isOperational(), click: function() { $root.sendCustomCommand({type:'command',command:'M106 S0'}) }">Fans off</button> <button class="btn control-box" data-bind="enable: isOperational() && loginState.isUser(), click: function() { $root.sendCustomCommand({type:'command',command:'M106 S0'}) }">Fans off</button>
</div> </div>
</div> </div>
@ -341,7 +343,7 @@
</script> </script>
<script type="text/html" id="customControls_commandTemplate"> <script type="text/html" id="customControls_commandTemplate">
<form class="form-inline"> <form class="form-inline">
<button class="btn" data-bind="text: name, enable: $root.isOperational(), click: function() { $root.sendCustomCommand($data) }"></button> <button class="btn" data-bind="text: name, enable: $root.isOperational() && $root.loginState.isUser(), click: function() { $root.sendCustomCommand($data) }"></button>
</form> </form>
</script> </script>
<script type="text/html" id="customControls_parametricCommandTemplate"> <script type="text/html" id="customControls_parametricCommandTemplate">
@ -350,7 +352,7 @@
<label data-bind="text: name"></label> <label data-bind="text: name"></label>
<input type="text" class="input-small" data-bind="attr: {placeholder: name}, value: value"> <input type="text" class="input-small" data-bind="attr: {placeholder: name}, value: value">
<!-- /ko --> <!-- /ko -->
<button class="btn" data-bind="text: name, enable: $root.isOperational(), click: function() { $root.sendCustomCommand($data) }"></button> <button class="btn" data-bind="text: name, enable: $root.isOperational() && $root.loginState.isUser(), click: function() { $root.sendCustomCommand($data) }"></button>
</form> </form>
</script> </script>
<script type="text/html" id="customControls_emptyTemplate"><div></div></script> <script type="text/html" id="customControls_emptyTemplate"><div></div></script>
@ -360,30 +362,30 @@
<div class="form-horizontal" style="margin-bottom: 20px"> <div class="form-horizontal" style="margin-bottom: 20px">
<label for="speed_outerWall">Outer Wall</label> <label for="speed_outerWall">Outer Wall</label>
<div class="input-append"> <div class="input-append">
<input type="text" id="speed_outerWall" class="input-mini" data-bind="enable: isOperational(), attr: {placeholder: outerWall}"> <input type="text" id="speed_outerWall" class="input-mini" data-bind="enable: isOperational() && loginState.isUser(), attr: {placeholder: outerWall}">
<span class="add-on">%</span> <span class="add-on">%</span>
<button type="submit" class="btn" id="speed_outerWall_set" data-bind="enable: isOperational()">Set</button> <button type="submit" class="btn" id="speed_outerWall_set" data-bind="enable: isOperational() && loginState.isUser()">Set</button>
</div> </div>
<label for="speed_innerWall">Inner Wall</label> <label for="speed_innerWall">Inner Wall</label>
<div class="input-append"> <div class="input-append">
<input type="text" id="speed_innerWall" class="input-mini" data-bind="enable: isOperational(), attr: {placeholder: innerWall}"> <input type="text" id="speed_innerWall" class="input-mini" data-bind="enable: isOperational() && loginState.isUser(), attr: {placeholder: innerWall}">
<span class="add-on">%</span> <span class="add-on">%</span>
<button type="submit" class="btn" id="speed_innerWall_set" data-bind="enable: isOperational()">Set</button> <button type="submit" class="btn" id="speed_innerWall_set" data-bind="enable: isOperational() && loginState.isUser()">Set</button>
</div> </div>
<label for="speed_fill">Fill</label> <label for="speed_fill">Fill</label>
<div class="input-append"> <div class="input-append">
<input type="text" id="speed_fill" class="input-mini" data-bind="enable: isOperational(), attr: {placeholder: fill}"> <input type="text" id="speed_fill" class="input-mini" data-bind="enable: isOperational() && loginState.isUser(), attr: {placeholder: fill}">
<span class="add-on">%</span> <span class="add-on">%</span>
<button type="submit" class="btn" id="speed_fill_set" data-bind="enable: isOperational()">Set</button> <button type="submit" class="btn" id="speed_fill_set" data-bind="enable: isOperational() && loginState.isUser()">Set</button>
</div> </div>
<label for="speed_support">Support</label> <label for="speed_support">Support</label>
<div class="input-append"> <div class="input-append">
<input type="text" id="speed_support" class="input-mini" data-bind="enable: isOperational(), attr: {placeholder: support}"> <input type="text" id="speed_support" class="input-mini" data-bind="enable: isOperational() && loginState.isUser(), attr: {placeholder: support}">
<span class="add-on">%</span> <span class="add-on">%</span>
<button type="submit" class="btn" id="speed_support_set" data-bind="enable: isOperational()">Set</button> <button type="submit" class="btn" id="speed_support_set" data-bind="enable: isOperational() && loginState.isUser()">Set</button>
</div> </div>
</div> </div>
</div> </div>
@ -482,7 +484,7 @@
<div class="input-append"> <div class="input-append">
<input type="text" id="terminal-command"> <input type="text" id="terminal-command">
<button class="btn" type="button" id="terminal-send">Send</button> <button class="btn" type="button" id="terminal-send" data-bind="enable: isOperational() && loginState.isUser()">Send</button>
</div> </div>
</div> </div>
{% if enableTimelapse %} {% if enableTimelapse %}
@ -490,7 +492,7 @@
<h1>Timelapse Configuration</h1> <h1>Timelapse Configuration</h1>
<label for="webcam_timelapse_mode">Timelapse Mode</label> <label for="webcam_timelapse_mode">Timelapse Mode</label>
<select id="webcam_timelapse_mode" data-bind="value: timelapseType, enable: isOperational() && !isPrinting()"> <select id="webcam_timelapse_mode" data-bind="value: timelapseType, enable: isOperational() && !isPrinting() && loginState.isUser()">
<option value="off">Off</option> <option value="off">Off</option>
<option value="zchange">On Z Change</option> <option value="zchange">On Z Change</option>
<option value="timed">Timed</option> <option value="timed">Timed</option>
@ -505,7 +507,7 @@
</div> </div>
<div> <div>
<button class="btn" data-bind="click: save, enable: isOperational() && !isPrinting()">Save Settings</button> <button class="btn" data-bind="click: save, enable: isOperational() && !isPrinting() && loginState.isUser()">Save Settings</button>
</div> </div>
<h1>Finished Timelapses</h1> <h1>Finished Timelapses</h1>

View File

@ -150,22 +150,34 @@ class UnknownRole(Exception):
class User(UserMixin): class User(UserMixin):
def __init__(self, username, passwordHash, active, roles): def __init__(self, username, passwordHash, active, roles):
self.username = username self._username = username
self.passwordHash = passwordHash self._passwordHash = passwordHash
self.active = active self._active = active
self.roles = roles self._roles = roles
def check_password(self, passwordHash):
return self._passwordHash == passwordHash
def get_id(self): def get_id(self):
return self.username return self._username
def get_name(self):
return self._username
def is_active(self): def is_active(self):
return self.active return self._active
def is_user(self):
return "user" in self._roles
def is_admin(self):
return "admin" in self._roles
##~~ DummyUser object to use when accessControl is disabled ##~~ DummyUser object to use when accessControl is disabled
class DummyUser(UserMixin): class DummyUser(User):
def __init__(self): def __init__(self):
self.roles = UserManager.valid_roles User.__init__(self, "dummy", "", True, UserManager.valid_roles)
def get_id(self): def check_password(self, passwordHash):
return "dummy" return True