"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")
@app.route(BASEURL + "control/disconnect", methods=["POST"])
@login_required
def disconnect():
printer.disconnect()
return jsonify(state="Offline")
@app.route(BASEURL + "control/command", methods=["POST"])
@login_required
def printerCommand():
if "application/json" in request.headers["Content-Type"]:
data = request.json
@ -166,21 +168,25 @@ def printerCommand():
return jsonify(SUCCESS)
@app.route(BASEURL + "control/print", methods=["POST"])
@login_required
def printGcode():
printer.startPrint()
return jsonify(SUCCESS)
@app.route(BASEURL + "control/pause", methods=["POST"])
@login_required
def pausePrint():
printer.togglePausePrint()
return jsonify(SUCCESS)
@app.route(BASEURL + "control/cancel", methods=["POST"])
@login_required
def cancelPrint():
printer.cancelPrint()
return jsonify(SUCCESS)
@app.route(BASEURL + "control/temperature", methods=["POST"])
@login_required
def setTargetTemperature():
if not printer.isOperational():
return jsonify(SUCCESS)
@ -198,6 +204,7 @@ def setTargetTemperature():
return jsonify(SUCCESS)
@app.route(BASEURL + "control/jog", methods=["POST"])
@login_required
def jog():
if not printer.isOperational() or printer.isPrinting():
# 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())
@app.route(BASEURL + "control/speed", methods=["POST"])
@login_required
def speed():
if not printer.isOperational():
return jsonify(SUCCESS)
@ -261,6 +269,7 @@ def readGcodeFile(filename):
return send_from_directory(settings().getBaseFolder("uploads"), filename, as_attachment=True)
@app.route(BASEURL + "gcodefiles/upload", methods=["POST"])
@login_required
def uploadGcodeFile():
filename = None
if "gcode_file" in request.files.keys():
@ -269,6 +278,7 @@ def uploadGcodeFile():
return jsonify(files=gcodeManager.getAllFileData(), filename=filename)
@app.route(BASEURL + "gcodefiles/load", methods=["POST"])
@login_required
def loadGcodeFile():
if "filename" in request.values.keys():
printAfterLoading = False
@ -280,6 +290,7 @@ def loadGcodeFile():
return jsonify(SUCCESS)
@app.route(BASEURL + "gcodefiles/delete", methods=["POST"])
@login_required
def deleteGcodeFile():
if "filename" in request.values.keys():
filename = request.values["filename"]
@ -318,6 +329,7 @@ def downloadTimelapse(filename):
return send_from_directory(settings().getBaseFolder("timelapse"), filename, as_attachment=True)
@app.route(BASEURL + "timelapse/<filename>", methods=["DELETE"])
@login_required
def deleteTimelapse(filename):
if util.isAllowedFile(filename, set(["mpg"])):
secure = os.path.join(settings().getBaseFolder("timelapse"), secure_filename(filename))
@ -326,6 +338,7 @@ def deleteTimelapse(filename):
return getTimelapseData()
@app.route(BASEURL + "timelapse/config", methods=["POST"])
@login_required
def setTimelapseConfig():
if request.values.has_key("type"):
type = request.values["type"]
@ -389,6 +402,7 @@ def getSettings():
})
@app.route(BASEURL + "settings", methods=["POST"])
@login_required
def setSettings():
if "application/json" in request.headers["Content-Type"]:
data = request.json
@ -434,6 +448,7 @@ def setSettings():
#~~ system control
@app.route(BASEURL + "system", methods=["POST"])
@login_required
def performSystemAction():
logger = logging.getLogger(__name__)
if request.values.has_key("action"):
@ -467,15 +482,14 @@ def login():
user = userManager.findUser(username)
if user is not None:
passwordHash = users.UserManager.createPasswordHash(password)
if passwordHash == user.passwordHash:
if user.check_password(users.UserManager.createPasswordHash(password)):
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, []))
elif "passive" in request.values.keys():
user = current_user
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:
return jsonify(SUCCESS)
@ -488,8 +502,7 @@ def logout():
def load_user(id):
if userManager is not None:
return userManager.findUser(id)
else:
return users.DummyUser()
return None
#~~ startup code
class Server():

View File

@ -1,8 +1,80 @@
//~~ View models
function ConnectionViewModel() {
function LoginStateViewModel() {
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.baudrateOptions = ko.observableArray(undefined);
self.selectedPort = ko.observable(undefined);
@ -107,9 +179,11 @@ function ConnectionViewModel() {
}
}
function PrinterStateViewModel() {
function PrinterStateViewModel(loginStateViewModel) {
var self = this;
self.loginState = loginStateViewModel;
self.stateString = ko.observable(undefined);
self.isErrorOrClosed = ko.observable(undefined);
self.isOperational = ko.observable(undefined);
@ -238,9 +312,11 @@ function PrinterStateViewModel() {
}
}
function TemperatureViewModel(settingsViewModel) {
function TemperatureViewModel(loginStateViewModel, settingsViewModel) {
var self = this;
self.loginState = loginStateViewModel;
self.temp = ko.observable(undefined);
self.bedTemp = ko.observable(undefined);
self.targetTemp = ko.observable(undefined);
@ -412,9 +488,11 @@ function TemperatureViewModel(settingsViewModel) {
}
}
function ControlViewModel() {
function ControlViewModel(loginStateViewModel) {
var self = this;
self.loginState = loginStateViewModel;
self.isErrorOrClosed = ko.observable(undefined);
self.isOperational = ko.observable(undefined);
self.isPrinting = ko.observable(undefined);
@ -567,9 +645,11 @@ function ControlViewModel() {
}
function SpeedViewModel() {
function SpeedViewModel(loginStateViewModel) {
var self = this;
self.loginState = loginStateViewModel;
self.outerWall = ko.observable(undefined);
self.innerWall = ko.observable(undefined);
self.fill = ko.observable(undefined);
@ -625,9 +705,11 @@ function SpeedViewModel() {
}
}
function TerminalViewModel() {
function TerminalViewModel(loginStateViewModel) {
var self = this;
self.loginState = loginStateViewModel;
self.log = [];
self.isErrorOrClosed = ko.observable(undefined);
@ -654,6 +736,7 @@ function TerminalViewModel() {
if (!self.log)
self.log = []
self.log = self.log.concat(data)
self.log = self.log.slice(-300)
self.updateOutput();
}
@ -690,9 +773,11 @@ function TerminalViewModel() {
}
}
function GcodeFilesViewModel() {
function GcodeFilesViewModel(loginStateViewModel) {
var self = this;
self.loginState = loginStateViewModel;
self.isErrorOrClosed = ko.observable(undefined);
self.isOperational = ko.observable(undefined);
self.isPrinting = ko.observable(undefined);
@ -824,9 +909,11 @@ function GcodeFilesViewModel() {
}
function TimelapseViewModel() {
function TimelapseViewModel(loginStateViewModel) {
var self = this;
self.loginState = loginStateViewModel;
self.timelapseType = ko.observable(undefined);
self.timelapseTimedInterval = ko.observable(undefined);
@ -943,9 +1030,11 @@ function TimelapseViewModel() {
}
}
function GcodeViewModel() {
function GcodeViewModel(loginStateViewModel) {
var self = this;
self.loginState = loginStateViewModel;
self.loadedFilename = undefined;
self.status = 'idle';
self.enabled = false;
@ -1006,9 +1095,11 @@ function GcodeViewModel() {
}
function SettingsViewModel() {
function SettingsViewModel(loginStateViewModel) {
var self = this;
self.loginState = loginStateViewModel;
self.appearance_name = 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;
self.loginState = loginStateViewModel;
self.appearance = appearanceViewModel;
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;
self.loginStateViewModel = loginStateViewModel;
self.connectionViewModel = connectionViewModel;
self.printerStateViewModel = printerStateViewModel;
self.temperatureViewModel = temperatureViewModel;
@ -1185,6 +1278,7 @@ function DataUpdater(connectionViewModel, printerStateViewModel, temperatureView
$("#offline_overlay").hide();
self.timelapseViewModel.requestData();
$("#webcam_image").attr("src", CONFIG_WEBCAM_STREAM + "?" + new Date().getTime());
self.loginStateViewModel.requestData();
}
})
self._socket.on("disconnect", function() {
@ -1480,20 +1574,22 @@ function AppearanceViewModel(settingsViewModel) {
$(function() {
//~~ View models
var connectionViewModel = new ConnectionViewModel();
var printerStateViewModel = new PrinterStateViewModel();
var settingsViewModel = new SettingsViewModel();
var loginStateViewModel = new LoginStateViewModel();
var connectionViewModel = new ConnectionViewModel(loginStateViewModel);
var printerStateViewModel = new PrinterStateViewModel(loginStateViewModel);
var settingsViewModel = new SettingsViewModel(loginStateViewModel);
var appearanceViewModel = new AppearanceViewModel(settingsViewModel);
var temperatureViewModel = new TemperatureViewModel(settingsViewModel);
var controlViewModel = new ControlViewModel();
var speedViewModel = new SpeedViewModel();
var terminalViewModel = new TerminalViewModel();
var gcodeFilesViewModel = new GcodeFilesViewModel();
var timelapseViewModel = new TimelapseViewModel();
var gcodeViewModel = new GcodeViewModel();
var navigationViewModel = new NavigationViewModel(appearanceViewModel, settingsViewModel);
var temperatureViewModel = new TemperatureViewModel(loginStateViewModel, settingsViewModel);
var controlViewModel = new ControlsViewModel(loginStateViewModel);
var speedViewModel = new SpeedViewModel(loginStateViewModel);
var terminalViewModel = new TerminalViewModel(loginStateViewModel);
var gcodeFilesViewModel = new GcodeFilesViewModel(loginStateViewModel);
var timelapseViewModel = new TimelapseViewModel(loginStateViewModel);
var gcodeViewModel = new GcodeViewModel(loginStateViewModel);
var navigationViewModel = new NavigationViewModel(loginStateViewModel, appearanceViewModel, settingsViewModel);
var dataUpdater = new DataUpdater(
loginStateViewModel,
connectionViewModel,
printerStateViewModel,
temperatureViewModel,
@ -1505,7 +1601,8 @@ $(function() {
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({
type: 'POST',
headers: { "cache-control": "no-cache" }
@ -1547,6 +1644,7 @@ $(function() {
})
$('#tabs a[data-toggle="tab"]').on('shown', function (e) {
temperatureViewModel.updatePlot();
terminalViewModel.updateOutput();
});
//~~ Speed controls
@ -1618,71 +1716,6 @@ $(function() {
//~~ Offline overlay
$("#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
ko.bindingHandlers.popover = {
@ -1717,14 +1750,15 @@ $(function() {
var timelapseElement = document.getElementById("timelapse");
if (timelapseElement) {
ko.applyBindings(timelapseViewModel, document.getElementById("timelapse"));
ko.applyBindings(timelapseViewModel, timelapseElement);
}
var gCodeVisualizerElement = document.getElementById("gcode");
if(gCodeVisualizerElement){
if (gCodeVisualizerElement) {
gcodeViewModel.initialize();
}
//~~ startup commands
loginStateViewModel.requestData();
connectionViewModel.requestData();
controlViewModel.requestData();
gcodeFilesViewModel.requestData();
@ -1745,7 +1779,7 @@ $(function() {
$.pnotify.defaults.history = false;
// Fix input element click problem
// Fix input element click problem on login dialog
$('.dropdown input, .dropdown label').click(function(e) {
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>
<div class="nav-collapse">
<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 %}
<li class="dropdown">
<li class="dropdown hide" data-bind="css: {hide: !loginState.isAdmin()}">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="icon-off"></i> System
<b class="caret"></b>
@ -48,10 +52,10 @@
{% if enableAccessControl %}
<li class="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>
</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>
<input type="text" id="login_user" placeholder="Username">
<label for="login_pass">Password</label>
@ -59,12 +63,10 @@
<label class="checkbox">
<input type="checkbox" id="login_remember"> Remember me
</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>
<ul id="login_dropdown_loggedin" class="hide">
<li><a href="#">Profile</a></li>
<li class="divider"></li>
<li><a href="#" id="logout_button">Logout</a></li>
<ul id="login_dropdown_loggedin" class="hide" data-bind="css: {hide: !loginState.loggedIn(), 'dropdown-menu': loginState.loggedIn()}">
<li><a href="#" id="logout_button" data-bind="click: loginState.logout">Logout</a></li>
</ul>
</li>
{% endif %}
@ -82,14 +84,14 @@
</div>
<div class="accordion-body collapse in" id="connection">
<div class="accordion-inner">
<label for="connection_ports" data-bind="css: {disabled: !isErrorOrClosed}, enable: isErrorOrClosed">Serial Port</label>
<select id="connection_ports" data-bind="options: portOptions, optionsCaption: 'AUTO', value: selectedPort, css: {disabled: !isErrorOrClosed}, enable: isErrorOrClosed"></select>
<label for="connection_baudrates" data-bind="css: {disabled: !isErrorOrClosed}, enable: isErrorOrClosed">Baudrate</label>
<select id="connection_baudrates" data-bind="options: baudrateOptions, optionsCaption: 'AUTO', value: selectedBaudrate, css: {disabled: !isErrorOrClosed}, enable: isErrorOrClosed"></select>
<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 && loginState.isUser"></select>
<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 && loginState.isUser"></select>
<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>
<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>
@ -113,9 +115,9 @@
</div>
<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 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_cancel" data-bind="click: cancel, enable: isPrinting() || isPaused()"><i class="icon-stop"></i> Cancel</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: 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: isOperational() && (isPrinting() || isPaused()) && loginState.isUser()"><i class="icon-stop"></i> Cancel</button>
</div>
</div>
</div>
@ -209,12 +211,12 @@
<label for="temp_newTemp">New Target</label>
<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>
</div>
<div class="btn-group">
<button type="submit" class="btn" id="temp_newTemp_set">Set</button>
<button class="btn dropdown-toggle" data-toggle="dropdown">
<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" data-bind="enable: isOperational() && loginState.isUser()">
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
@ -239,12 +241,12 @@
<label for="temp_newBedTemp">New Target</label>
<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>
</div>
<div class="btn-group">
<button type="submit" class="btn" id="temp_newBedTemp_set">Set</button>
<button class="btn dropdown-toggle" data-toggle="dropdown">
<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" data-bind="enable: isOperational() && loginState.isUser()">
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
@ -274,37 +276,37 @@
<div class="jog-panel">
<h1>X/Y</h1>
<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>
<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(), 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-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.sendJogCommand('x',1) }"><i class="icon-arrow-right"></i></button>
</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>
<!-- Z jogging control panel -->
<div class="jog-panel">
<h1>Z</h1>
<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>
<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>
<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>
<!-- Jog distance -->
<div class="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="1">1</button>
<button type="button" class="btn active" data-distance="10">10</button>
<button type="button" class="btn" data-distance="100">100</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" data-bind="enable: loginState.isUser()">1</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" data-bind="enable: loginState.isUser()">100</button>
</div>
</div>
</div>
@ -313,20 +315,20 @@
<h1>E</h1>
<div>
<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>
</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(), click: function() { $root.sendRetractCommand() }">Retract</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() && loginState.isUser(), click: function() { $root.sendRetractCommand() }">Retract</button>
</div>
</div>
<!-- General control panel -->
<div class="jog-panel">
<h1>General</h1>
<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(), 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() && !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() && loginState.isUser(), 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 S0'}) }">Fans off</button>
</div>
</div>
@ -341,7 +343,7 @@
</script>
<script type="text/html" id="customControls_commandTemplate">
<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>
</script>
<script type="text/html" id="customControls_parametricCommandTemplate">
@ -350,7 +352,7 @@
<label data-bind="text: name"></label>
<input type="text" class="input-small" data-bind="attr: {placeholder: name}, value: value">
<!-- /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>
</script>
<script type="text/html" id="customControls_emptyTemplate"><div></div></script>
@ -360,30 +362,30 @@
<div class="form-horizontal" style="margin-bottom: 20px">
<label for="speed_outerWall">Outer Wall</label>
<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>
<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>
<label for="speed_innerWall">Inner Wall</label>
<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>
<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>
<label for="speed_fill">Fill</label>
<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>
<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>
<label for="speed_support">Support</label>
<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>
<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>
@ -482,7 +484,7 @@
<div class="input-append">
<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>
{% if enableTimelapse %}
@ -490,7 +492,7 @@
<h1>Timelapse Configuration</h1>
<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="zchange">On Z Change</option>
<option value="timed">Timed</option>
@ -505,7 +507,7 @@
</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>
<h1>Finished Timelapses</h1>

View File

@ -150,22 +150,34 @@ class UnknownRole(Exception):
class User(UserMixin):
def __init__(self, username, passwordHash, active, roles):
self.username = username
self.passwordHash = passwordHash
self.active = active
self.roles = roles
self._username = username
self._passwordHash = passwordHash
self._active = active
self._roles = roles
def check_password(self, passwordHash):
return self._passwordHash == passwordHash
def get_id(self):
return self.username
return self._username
def get_name(self):
return self._username
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
class DummyUser(UserMixin):
class DummyUser(User):
def __init__(self):
self.roles = UserManager.valid_roles
User.__init__(self, "dummy", "", True, UserManager.valid_roles)
def get_id(self):
return "dummy"
def check_password(self, passwordHash):
return True