From c5c382ade6ecd27f50e809c5785142964559990c Mon Sep 17 00:00:00 2001 From: Salandora Date: Mon, 27 Jan 2014 09:35:51 +0100 Subject: [PATCH 1/6] Log tab added to download and remove logs --- src/octoprint/server/__init__.py | 1 + src/octoprint/server/api/__init__.py | 1 + src/octoprint/server/api/log.py | 62 +++++++++++++++++++ src/octoprint/static/js/app/dataupdater.js | 6 +- src/octoprint/static/js/app/main.js | 6 +- src/octoprint/static/js/app/viewmodels/log.js | 62 +++++++++++++++++++ src/octoprint/templates/index.jinja2 | 37 +++++++++++ 7 files changed, 172 insertions(+), 3 deletions(-) create mode 100644 src/octoprint/server/api/log.py create mode 100644 src/octoprint/static/js/app/viewmodels/log.js diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py index 1fccc3e3..8eb1f9e3 100644 --- a/src/octoprint/server/__init__.py +++ b/src/octoprint/server/__init__.py @@ -177,6 +177,7 @@ class Server(): self._tornado_app = Application(self._router.urls + [ (r"/downloads/timelapse/([^/]*\.mpg)", LargeResponseHandler, {"path": settings().getBaseFolder("timelapse"), "as_attachment": True}), (r"/downloads/files/local/([^/]*\.(gco|gcode))", LargeResponseHandler, {"path": settings().getBaseFolder("uploads"), "as_attachment": True}), + (r"/downloads/logs/([^/]*)", LargeResponseHandler, {"path": settings().getBaseFolder("logs"), "as_attachment": True}), (r".*", FallbackHandler, {"fallback": WSGIContainer(app.wsgi_app)}) ]) self._server = HTTPServer(self._tornado_app) diff --git a/src/octoprint/server/api/__init__.py b/src/octoprint/server/api/__init__.py index 5614bee8..9ff5ae54 100644 --- a/src/octoprint/server/api/__init__.py +++ b/src/octoprint/server/api/__init__.py @@ -27,6 +27,7 @@ from . import files as api_files from . import settings as api_settings from . import timelapse as api_timelapse from . import users as api_users +from . import log as api_logs #~~ first run setup diff --git a/src/octoprint/server/api/log.py b/src/octoprint/server/api/log.py new file mode 100644 index 00000000..0fda12dc --- /dev/null +++ b/src/octoprint/server/api/log.py @@ -0,0 +1,62 @@ +# coding=utf-8 +__author__ = "Marc Hannappel Salandora" +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + +import os +import datetime + +from flask import request, jsonify, make_response, url_for +from werkzeug.utils import secure_filename + +import octoprint.util as util +from octoprint.settings import settings, valid_boolean_trues + +from octoprint.server import restricted_access, admin_permission +from octoprint.server.util import redirectToTornado +from octoprint.server.api import api + +@api.route("/logs", methods=["GET"]) +def getLogData(): + files = _getLogFiles() + return jsonify(files=files) + +@api.route("/logs/online/", methods=["GET"]) +def onlineLog(filename): + secure = os.path.join(settings().getBaseFolder("logs"), secure_filename(filename)) + if not os.path.exists(secure): + return make_response("Unknown filename: %s" % filename, 404) + + file = open(secure, 'r') + return jsonify(data=file.read()) + +@api.route("/logs/", methods=["GET"]) +def downloadLog(filename): + return redirectToTornado(request, url_for("index") + "downloads/logs/" + filename) + + +@api.route("/logs/", methods=["DELETE"]) +@restricted_access +def deleteLog(filename): + secure = os.path.join(settings().getBaseFolder("logs"), secure_filename(filename)) + if os.path.exists(secure): + os.remove(secure) + + return getLogData() + +def _getLogFiles(): + files = [] + basedir = settings().getBaseFolder("logs") + for osFile in os.listdir(basedir): + statResult = os.stat(os.path.join(basedir, osFile)) + files.append({ + "name": osFile, + "size": util.getFormattedSize(statResult.st_size), + "bytes": statResult.st_size, + "date": util.getFormattedDateTime(datetime.datetime.fromtimestamp(statResult.st_ctime)) + }) + + for file in files: + file["url"] = url_for("index") + "downloads/logs/" + file["name"] + + return files + diff --git a/src/octoprint/static/js/app/dataupdater.js b/src/octoprint/static/js/app/dataupdater.js index b7019ecf..3304fd64 100644 --- a/src/octoprint/static/js/app/dataupdater.js +++ b/src/octoprint/static/js/app/dataupdater.js @@ -1,4 +1,4 @@ -function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewModel, temperatureViewModel, controlViewModel, terminalViewModel, gcodeFilesViewModel, timelapseViewModel, gcodeViewModel) { +function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewModel, temperatureViewModel, controlViewModel, terminalViewModel, gcodeFilesViewModel, timelapseViewModel, gcodeViewModel, logViewModel) { var self = this; self.loginStateViewModel = loginStateViewModel; @@ -10,6 +10,7 @@ function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewM self.gcodeFilesViewModel = gcodeFilesViewModel; self.timelapseViewModel = timelapseViewModel; self.gcodeViewModel = gcodeViewModel; + self.logViewModel = logViewModel; self._socket = undefined; self._autoReconnecting = false; @@ -38,7 +39,8 @@ function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewM self._autoReconnectTrial = 0; if ($("#offline_overlay").is(":visible")) { - $("#offline_overlay").hide(); + $("#offline_overlay").hide(); + self.logViewModel.requestData(); self.timelapseViewModel.requestData(); self.loginStateViewModel.requestData(); self.gcodeFilesViewModel.requestData(); diff --git a/src/octoprint/static/js/app/main.js b/src/octoprint/static/js/app/main.js index f8270946..4c6b934c 100644 --- a/src/octoprint/static/js/app/main.js +++ b/src/octoprint/static/js/app/main.js @@ -14,6 +14,7 @@ $(function() { var gcodeFilesViewModel = new GcodeFilesViewModel(printerStateViewModel, loginStateViewModel); var gcodeViewModel = new GcodeViewModel(loginStateViewModel, settingsViewModel); var navigationViewModel = new NavigationViewModel(loginStateViewModel, appearanceViewModel, settingsViewModel, usersViewModel); + var logViewModel = new LogViewModel(loginStateViewModel); var dataUpdater = new DataUpdater( loginStateViewModel, @@ -24,7 +25,8 @@ $(function() { terminalViewModel, gcodeFilesViewModel, timelapseViewModel, - gcodeViewModel + gcodeViewModel, + logViewModel ); // work around a stupid iOS6 bug where ajax requests get cached and only work once, as described at @@ -302,6 +304,7 @@ $(function() { ko.applyBindings(navigationViewModel, document.getElementById("navbar")); ko.applyBindings(appearanceViewModel, document.getElementsByTagName("head")[0]); ko.applyBindings(printerStateViewModel, document.getElementById("drop_overlay")); + ko.applyBindings(logViewModel, document.getElementById("logs")); var timelapseElement = document.getElementById("timelapse"); if (timelapseElement) { @@ -316,6 +319,7 @@ $(function() { gcodeFilesViewModel.requestData(); timelapseViewModel.requestData(); settingsViewModel.requestData(); + logViewModel.requestData(); loginStateViewModel.subscribe(function(change, data) { if ("login" == change) { diff --git a/src/octoprint/static/js/app/viewmodels/log.js b/src/octoprint/static/js/app/viewmodels/log.js new file mode 100644 index 00000000..15578885 --- /dev/null +++ b/src/octoprint/static/js/app/viewmodels/log.js @@ -0,0 +1,62 @@ +function LogViewModel(loginStateViewModel) { + var self = this; + + self.loginState = loginStateViewModel; + + // initialize list helper + self.listHelper = new ItemListHelper( + "logFiles", + { + "name": function(a, b) { + // sorts ascending + if (a["name"].toLocaleLowerCase() < b["name"].toLocaleLowerCase()) return -1; + if (a["name"].toLocaleLowerCase() > b["name"].toLocaleLowerCase()) return 1; + return 0; + }, + "creation": function(a, b) { + // sorts descending + if (a["date"] > b["date"]) return -1; + if (a["date"] < b["date"]) return 1; + return 0; + }, + "size": function(a, b) { + // sorts descending + if (a["bytes"] > b["bytes"]) return -1; + if (a["bytes"] < b["bytes"]) return 1; + return 0; + } + }, + { + }, + "name", + [], + [], + CONFIG_LOGFILESPERPAGE + ); + + self.requestData = function() { + $.ajax({ + url: API_BASEURL + "logs", + type: "GET", + dataType: "json", + success: self.fromResponse + }); + }; + + self.fromResponse = function(response) { + var files = response.files; + if (files === undefined) + return; + + self.listHelper.updateItems(files); + } + + self.removeFile = function(filename) { + $.ajax({ + url: API_BASEURL + "logs/" + filename, + type: "DELETE", + dataType: "json", + success: self.requestData + }); + } +} diff --git a/src/octoprint/templates/index.jinja2 b/src/octoprint/templates/index.jinja2 index 17f25b44..cae0178a 100644 --- a/src/octoprint/templates/index.jinja2 +++ b/src/octoprint/templates/index.jinja2 @@ -27,6 +27,7 @@ var CONFIG_GCODEFILESPERPAGE = 5; var CONFIG_TIMELAPSEFILESPERPAGE = 10; + var CONFIG_LOGFILESPERPAGE = 10; var CONFIG_USERSPERPAGE = 10; var CONFIG_WEBCAM_STREAM = "{{ webcamStream }}"; var CONFIG_ACCESS_CONTROL = {% if enableAccessControl -%} true; {% else %} false; {%- endif %} @@ -252,6 +253,7 @@ {% if enableGCodeVisualizer %}
  • GCode Viewer
  • {% endif %}
  • Terminal
  • {% if enableTimelapse %}
  • Timelapse
  • {% endif %} +
  • Logs
  • @@ -585,6 +587,40 @@
    {% endif %} +
    +

    Logs

    + + + + + + + + + + + + + + + + + +
    NameSizeAction
     | 
    + +
    @@ -644,6 +680,7 @@ + From cf25d3d7be8884b12d17d00a56787c196cc6865d Mon Sep 17 00:00:00 2001 From: Salandora Date: Thu, 20 Feb 2014 18:08:21 +0100 Subject: [PATCH 2/6] Moved Log tab to Settings --- src/octoprint/static/js/app/main.js | 6 ++++ src/octoprint/templates/index.jinja2 | 35 ------------------ src/octoprint/templates/settings.jinja2 | 47 +++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 35 deletions(-) diff --git a/src/octoprint/static/js/app/main.js b/src/octoprint/static/js/app/main.js index 4c6b934c..68a47449 100644 --- a/src/octoprint/static/js/app/main.js +++ b/src/octoprint/static/js/app/main.js @@ -289,6 +289,12 @@ $(function() { } } + ko.bindingHandlers.allowBindings = { + init: function (elem, valueAccessor) { + return { controlsDescendantBindings: !valueAccessor() }; + } + }; + ko.applyBindings(connectionViewModel, document.getElementById("connection_accordion")); ko.applyBindings(printerStateViewModel, document.getElementById("state_accordion")); ko.applyBindings(gcodeFilesViewModel, document.getElementById("files_accordion")); diff --git a/src/octoprint/templates/index.jinja2 b/src/octoprint/templates/index.jinja2 index cae0178a..9932458f 100644 --- a/src/octoprint/templates/index.jinja2 +++ b/src/octoprint/templates/index.jinja2 @@ -253,7 +253,6 @@ {% if enableGCodeVisualizer %}
  • GCode Viewer
  • {% endif %}
  • Terminal
  • {% if enableTimelapse %}
  • Timelapse
  • {% endif %} -
  • Logs
  • @@ -587,40 +586,6 @@
    {% endif %} -
    -

    Logs

    - - - - - - - - - - - - - - - - - -
    NameSizeAction
     | 
    - -
    diff --git a/src/octoprint/templates/settings.jinja2 b/src/octoprint/templates/settings.jinja2 index a2f8e2ac..5437d55a 100644 --- a/src/octoprint/templates/settings.jinja2 +++ b/src/octoprint/templates/settings.jinja2 @@ -20,6 +20,7 @@
  • Folders
  • Appearance
  • +
  • Logs
  • @@ -535,6 +536,52 @@
    {% endif %} +
    +
    +

    Logs

    + + + + + + + + + + + + + + + + + +
    NameSizeAction
    +  |  +
    + +
    +
    From 197d46b511fec106e400f1e6535bce161950cd43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Sun, 23 Feb 2014 11:17:29 +0100 Subject: [PATCH 3/6] Fixed indentation --- src/octoprint/static/js/app/viewmodels/log.js | 8 +- src/octoprint/templates/settings.jinja2 | 92 +++++++++---------- 2 files changed, 50 insertions(+), 50 deletions(-) diff --git a/src/octoprint/static/js/app/viewmodels/log.js b/src/octoprint/static/js/app/viewmodels/log.js index 15578885..1f8af2d5 100644 --- a/src/octoprint/static/js/app/viewmodels/log.js +++ b/src/octoprint/static/js/app/viewmodels/log.js @@ -44,11 +44,11 @@ function LogViewModel(loginStateViewModel) { }; self.fromResponse = function(response) { - var files = response.files; - if (files === undefined) - return; + var files = response.files; + if (files === undefined) + return; - self.listHelper.updateItems(files); + self.listHelper.updateItems(files); } self.removeFile = function(filename) { diff --git a/src/octoprint/templates/settings.jinja2 b/src/octoprint/templates/settings.jinja2 index 5437d55a..340a12e7 100644 --- a/src/octoprint/templates/settings.jinja2 +++ b/src/octoprint/templates/settings.jinja2 @@ -20,7 +20,7 @@
  • Folders
  • Appearance
  • -
  • Logs
  • +
  • Logs
  • @@ -536,52 +536,52 @@
    {% endif %} -
    -
    -

    Logs

    +
    +
    +

    Logs

    - - - - - - - - - - - - - - - - -
    NameSizeAction
    -  |  -
    - -
    -
    + + + + + + + + + + + + + + + + +
    NameSizeAction
    +  |  +
    + +
    +
    From 4a6a458cdd5a2280f118c2cf094d768310af7678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Sun, 23 Feb 2014 12:25:46 +0100 Subject: [PATCH 4/6] Removed unused parts from backend code, moved formatting to frontend, added date column, switched to modification date (might be more interesting in case of the log files) --- src/octoprint/server/api/log.py | 26 +++++---------- src/octoprint/static/css/octoprint.css | 2 +- src/octoprint/static/js/app/viewmodels/log.js | 2 +- src/octoprint/static/less/octoprint.less | 32 +++++++++++++++++++ src/octoprint/templates/settings.jinja2 | 16 ++++++---- 5 files changed, 51 insertions(+), 27 deletions(-) diff --git a/src/octoprint/server/api/log.py b/src/octoprint/server/api/log.py index 0fda12dc..c2699628 100644 --- a/src/octoprint/server/api/log.py +++ b/src/octoprint/server/api/log.py @@ -3,31 +3,22 @@ __author__ = "Marc Hannappel Salandora" __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' import os -import datetime from flask import request, jsonify, make_response, url_for from werkzeug.utils import secure_filename -import octoprint.util as util -from octoprint.settings import settings, valid_boolean_trues +from octoprint.settings import settings -from octoprint.server import restricted_access, admin_permission +from octoprint.server import restricted_access from octoprint.server.util import redirectToTornado from octoprint.server.api import api + @api.route("/logs", methods=["GET"]) def getLogData(): files = _getLogFiles() return jsonify(files=files) -@api.route("/logs/online/", methods=["GET"]) -def onlineLog(filename): - secure = os.path.join(settings().getBaseFolder("logs"), secure_filename(filename)) - if not os.path.exists(secure): - return make_response("Unknown filename: %s" % filename, 404) - - file = open(secure, 'r') - return jsonify(data=file.read()) @api.route("/logs/", methods=["GET"]) def downloadLog(filename): @@ -39,10 +30,11 @@ def downloadLog(filename): def deleteLog(filename): secure = os.path.join(settings().getBaseFolder("logs"), secure_filename(filename)) if os.path.exists(secure): - os.remove(secure) + os.remove(secure) return getLogData() + def _getLogFiles(): files = [] basedir = settings().getBaseFolder("logs") @@ -50,13 +42,11 @@ def _getLogFiles(): statResult = os.stat(os.path.join(basedir, osFile)) files.append({ "name": osFile, - "size": util.getFormattedSize(statResult.st_size), + "size": statResult.st_size, "bytes": statResult.st_size, - "date": util.getFormattedDateTime(datetime.datetime.fromtimestamp(statResult.st_ctime)) + "date": int(statResult.st_mtime), + "url": url_for("index") + "downloads/logs/" + osFile }) - for file in files: - file["url"] = url_for("index") + "downloads/logs/" + file["name"] - return files diff --git a/src/octoprint/static/css/octoprint.css b/src/octoprint/static/css/octoprint.css index 6bdb45f9..524090a1 100644 --- a/src/octoprint/static/css/octoprint.css +++ b/src/octoprint/static/css/octoprint.css @@ -1 +1 @@ -.btn{display:inline-block;*display:inline;*zoom:1;padding:4px 12px;margin-bottom:0;font-size:14px;line-height:20px;text-align:center;vertical-align:middle;cursor:pointer;color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#f5f5f5;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#e6e6e6;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);border:1px solid #ccc;*border:0;border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*margin-left:.3em;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05)}.btn:hover,.btn:focus,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333;background-color:#e6e6e6;*background-color:#d9d9d9}.btn:active,.btn.active{background-color:#ccc \9}.btn:first-child{*margin-left:0}.btn:hover,.btn:focus{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05)}.btn.disabled,.btn[disabled]{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:11px 19px;font-size:17.5px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:4px}.btn-small{padding:2px 10px;font-size:11.9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0}.btn-mini [class^="icon-"],.btn-mini [class*=" icon-"]{margin-top:-1px}.btn-mini{padding:0 6px;font-size:10.5px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-block{display:block;width:100%;padding-left:0;padding-right:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75)}.btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#006dcc;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#04c;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#fff;background-color:#04c;*background-color:#003bb3}.btn-primary:active,.btn-primary.active{background-color:#039 \9}.btn-warning{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#faa732;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0);border-color:#f89406 #f89406 #ad6704;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#f89406;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#fff;background-color:#f89406;*background-color:#df8505}.btn-warning:active,.btn-warning.active{background-color:#c67605 \9}.btn-danger{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#da4f49;background-image:-moz-linear-gradient(top,#ee5f5b,#bd362f);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#bd362f));background-image:-webkit-linear-gradient(top,#ee5f5b,#bd362f);background-image:-o-linear-gradient(top,#ee5f5b,#bd362f);background-image:linear-gradient(to bottom,#ee5f5b,#bd362f);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffbd362f',GradientType=0);border-color:#bd362f #bd362f #802420;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#bd362f;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#fff;background-color:#bd362f;*background-color:#a9302a}.btn-danger:active,.btn-danger.active{background-color:#942a25 \9}.btn-success{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#5bb75b;background-image:-moz-linear-gradient(top,#62c462,#51a351);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#51a351));background-image:-webkit-linear-gradient(top,#62c462,#51a351);background-image:-o-linear-gradient(top,#62c462,#51a351);background-image:linear-gradient(to bottom,#62c462,#51a351);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff51a351',GradientType=0);border-color:#51a351 #51a351 #387038;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#51a351;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#fff;background-color:#51a351;*background-color:#499249}.btn-success:active,.btn-success.active{background-color:#408140 \9}.btn-info{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#49afcd;background-image:-moz-linear-gradient(top,#5bc0de,#2f96b4);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#2f96b4));background-image:-webkit-linear-gradient(top,#5bc0de,#2f96b4);background-image:-o-linear-gradient(top,#5bc0de,#2f96b4);background-image:linear-gradient(to bottom,#5bc0de,#2f96b4);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff2f96b4',GradientType=0);border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#2f96b4;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#fff;background-color:#2f96b4;*background-color:#2a85a0}.btn-info:active,.btn-info.active{background-color:#24748c \9}.btn-inverse{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#363636;background-image:-moz-linear-gradient(top,#444,#222);background-image:-webkit-gradient(linear,0 0,0 100%,from(#444),to(#222));background-image:-webkit-linear-gradient(top,#444,#222);background-image:-o-linear-gradient(top,#444,#222);background-image:linear-gradient(to bottom,#444,#222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444',endColorstr='#ff222222',GradientType=0);border-color:#222 #222 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#222;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.btn-inverse:hover,.btn-inverse:focus,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#fff;background-color:#222;*background-color:#151515}.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9}button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0}button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px}button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px}button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px}.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-link{border-color:transparent;cursor:pointer;color:#08c;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-link:hover,.btn-link:focus{color:#005580;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,.btn-link[disabled]:focus{color:#333;text-decoration:none}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:"";line-height:0}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}body{padding-top:60px}#navbar .navbar-inner{background-color:#ebebeb;background-image:-moz-linear-gradient(top,#fff,#ccc);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#ccc));background-image:-webkit-linear-gradient(top,#fff,#ccc);background-image:-o-linear-gradient(top,#fff,#ccc);background-image:linear-gradient(to bottom,#fff,#ccc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffcccccc',GradientType=0)}#navbar .navbar-inner .brand,#navbar .navbar-inner .nav>li>a{text-shadow:0 1px 0 #fff;color:#333}#navbar .navbar-inner .brand .caret,#navbar .navbar-inner .nav>li>a .caret{border-bottom-color:#939393;border-top-color:#939393}#navbar .navbar-inner .brand:hover .caret,#navbar .navbar-inner .nav>li>a:hover .caret,#navbar .navbar-inner .brand:focus .caret,#navbar .navbar-inner .nav>li>a:focus .caret{border-bottom-color:#636363;border-top-color:#636363}#navbar .navbar-inner .brand span{background-image:url(../img/tentacle-20x20.png)}@media(-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner .brand span{background-image:url(../img/tentacle-20x20@2x.png)}}#navbar .navbar-inner .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner .nav li.dropdown.open.active>.dropdown-toggle{background-color:#e0e0e0;background-image:-moz-linear-gradient(top,#ccc,#fff);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ccc),to(#fff));background-image:-webkit-linear-gradient(top,#ccc,#fff);background-image:-o-linear-gradient(top,#ccc,#fff);background-image:linear-gradient(to bottom,#ccc,#fff);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffcccccc',endColorstr='#ffffffff',GradientType=0)}#navbar .navbar-inner.red{background-color:#e36565;background-image:-moz-linear-gradient(top,#f9a0a0,#c20c0c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f9a0a0),to(#c20c0c));background-image:-webkit-linear-gradient(top,#f9a0a0,#c20c0c);background-image:-o-linear-gradient(top,#f9a0a0,#c20c0c);background-image:linear-gradient(to bottom,#f9a0a0,#c20c0c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9a0a0',endColorstr='#ffc20c0c',GradientType=0)}#navbar .navbar-inner.red .brand,#navbar .navbar-inner.red .nav>li>a{text-shadow:0 1px 0 #c20c0c;color:#f2f2f2}#navbar .navbar-inner.red .brand .caret,#navbar .navbar-inner.red .nav>li>a .caret{border-bottom-color:#f28d8d;border-top-color:#f28d8d}#navbar .navbar-inner.red .brand:hover .caret,#navbar .navbar-inner.red .nav>li>a:hover .caret,#navbar .navbar-inner.red .brand:focus .caret,#navbar .navbar-inner.red .nav>li>a:focus .caret{border-bottom-color:#f2c0c0;border-top-color:#f2c0c0}#navbar .navbar-inner.red .brand span{background-image:url(../img/tentacle-20x20-light.png)}@media(-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.red .brand span{background-image:url(../img/tentacle-20x20-light@2x.png)}}#navbar .navbar-inner.red .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.red .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.red .nav li.dropdown.open.active>.dropdown-toggle{background-color:#d84747;background-image:-moz-linear-gradient(top,#c20c0c,#f9a0a0);background-image:-webkit-gradient(linear,0 0,0 100%,from(#c20c0c),to(#f9a0a0));background-image:-webkit-linear-gradient(top,#c20c0c,#f9a0a0);background-image:-o-linear-gradient(top,#c20c0c,#f9a0a0);background-image:linear-gradient(to bottom,#c20c0c,#f9a0a0);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffc20c0c',endColorstr='#fff9a0a0',GradientType=0)}#navbar .navbar-inner.orange{background-color:#e39665;background-image:-moz-linear-gradient(top,#f9c3a0,#c2530c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f9c3a0),to(#c2530c));background-image:-webkit-linear-gradient(top,#f9c3a0,#c2530c);background-image:-o-linear-gradient(top,#f9c3a0,#c2530c);background-image:linear-gradient(to bottom,#f9c3a0,#c2530c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9c3a0',endColorstr='#ffc2530c',GradientType=0)}#navbar .navbar-inner.orange .brand,#navbar .navbar-inner.orange .nav>li>a{text-shadow:0 1px 0 #f6a570;color:#333}#navbar .navbar-inner.orange .brand .caret,#navbar .navbar-inner.orange .nav>li>a .caret{border-bottom-color:#93552e;border-top-color:#93552e}#navbar .navbar-inner.orange .brand:hover .caret,#navbar .navbar-inner.orange .nav>li>a:hover .caret,#navbar .navbar-inner.orange .brand:focus .caret,#navbar .navbar-inner.orange .nav>li>a:focus .caret{border-bottom-color:#634430;border-top-color:#634430}#navbar .navbar-inner.orange .brand span{background-image:url(../img/tentacle-20x20.png)}@media(-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.orange .brand span{background-image:url(../img/tentacle-20x20@2x.png)}}#navbar .navbar-inner.orange .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.orange .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.orange .nav li.dropdown.open.active>.dropdown-toggle{background-color:#d88047;background-image:-moz-linear-gradient(top,#c2530c,#f9c3a0);background-image:-webkit-gradient(linear,0 0,0 100%,from(#c2530c),to(#f9c3a0));background-image:-webkit-linear-gradient(top,#c2530c,#f9c3a0);background-image:-o-linear-gradient(top,#c2530c,#f9c3a0);background-image:linear-gradient(to bottom,#c2530c,#f9c3a0);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffc2530c',endColorstr='#fff9c3a0',GradientType=0)}#navbar .navbar-inner.yellow{background-color:#e3d765;background-image:-moz-linear-gradient(top,#f9f0a0,#c2b00c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f9f0a0),to(#c2b00c));background-image:-webkit-linear-gradient(top,#f9f0a0,#c2b00c);background-image:-o-linear-gradient(top,#f9f0a0,#c2b00c);background-image:linear-gradient(to bottom,#f9f0a0,#c2b00c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f0a0',endColorstr='#ffc2b00c',GradientType=0)}#navbar .navbar-inner.yellow .brand,#navbar .navbar-inner.yellow .nav>li>a{text-shadow:0 1px 0 #f6e970;color:#333}#navbar .navbar-inner.yellow .brand .caret,#navbar .navbar-inner.yellow .nav>li>a .caret{border-bottom-color:#93892e;border-top-color:#93892e}#navbar .navbar-inner.yellow .brand:hover .caret,#navbar .navbar-inner.yellow .nav>li>a:hover .caret,#navbar .navbar-inner.yellow .brand:focus .caret,#navbar .navbar-inner.yellow .nav>li>a:focus .caret{border-bottom-color:#635e30;border-top-color:#635e30}#navbar .navbar-inner.yellow .brand span{background-image:url(../img/tentacle-20x20.png)}@media(-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.yellow .brand span{background-image:url(../img/tentacle-20x20@2x.png)}}#navbar .navbar-inner.yellow .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.yellow .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.yellow .nav li.dropdown.open.active>.dropdown-toggle{background-color:#d8ca47;background-image:-moz-linear-gradient(top,#c2b00c,#f9f0a0);background-image:-webkit-gradient(linear,0 0,0 100%,from(#c2b00c),to(#f9f0a0));background-image:-webkit-linear-gradient(top,#c2b00c,#f9f0a0);background-image:-o-linear-gradient(top,#c2b00c,#f9f0a0);background-image:linear-gradient(to bottom,#c2b00c,#f9f0a0);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffc2b00c',endColorstr='#fff9f0a0',GradientType=0)}#navbar .navbar-inner.green{background-color:#98f064;background-image:-moz-linear-gradient(top,#c8ffa7,#50da00);background-image:-webkit-gradient(linear,0 0,0 100%,from(#c8ffa7),to(#50da00));background-image:-webkit-linear-gradient(top,#c8ffa7,#50da00);background-image:-o-linear-gradient(top,#c8ffa7,#50da00);background-image:linear-gradient(to bottom,#c8ffa7,#50da00);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffc8ffa7',endColorstr='#ff50da00',GradientType=0)}#navbar .navbar-inner.green .brand,#navbar .navbar-inner.green .nav>li>a{text-shadow:0 1px 0 #a7ff74;color:#333}#navbar .navbar-inner.green .brand .caret,#navbar .navbar-inner.green .nav>li>a .caret{border-bottom-color:#55992e;border-top-color:#55992e}#navbar .navbar-inner.green .brand:hover .caret,#navbar .navbar-inner.green .nav>li>a:hover .caret,#navbar .navbar-inner.green .brand:focus .caret,#navbar .navbar-inner.green .nav>li>a:focus .caret{border-bottom-color:#446630;border-top-color:#446630}#navbar .navbar-inner.green .brand span{background-image:url(../img/tentacle-20x20.png)}@media(-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.green .brand span{background-image:url(../img/tentacle-20x20@2x.png)}}#navbar .navbar-inner.green .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.green .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.green .nav li.dropdown.open.active>.dropdown-toggle{background-color:#80e943;background-image:-moz-linear-gradient(top,#50da00,#c8ffa7);background-image:-webkit-gradient(linear,0 0,0 100%,from(#50da00),to(#c8ffa7));background-image:-webkit-linear-gradient(top,#50da00,#c8ffa7);background-image:-o-linear-gradient(top,#50da00,#c8ffa7);background-image:linear-gradient(to bottom,#50da00,#c8ffa7);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff50da00',endColorstr='#ffc8ffa7',GradientType=0)}#navbar .navbar-inner.blue{background-color:#6498f0;background-image:-moz-linear-gradient(top,#a7c8ff,#0050da);background-image:-webkit-gradient(linear,0 0,0 100%,from(#a7c8ff),to(#0050da));background-image:-webkit-linear-gradient(top,#a7c8ff,#0050da);background-image:-o-linear-gradient(top,#a7c8ff,#0050da);background-image:linear-gradient(to bottom,#a7c8ff,#0050da);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffa7c8ff',endColorstr='#ff0050da',GradientType=0)}#navbar .navbar-inner.blue .brand,#navbar .navbar-inner.blue .nav>li>a{text-shadow:0 1px 0 #74a7ff;color:#333}#navbar .navbar-inner.blue .brand .caret,#navbar .navbar-inner.blue .nav>li>a .caret{border-bottom-color:#2e5599;border-top-color:#2e5599}#navbar .navbar-inner.blue .brand:hover .caret,#navbar .navbar-inner.blue .nav>li>a:hover .caret,#navbar .navbar-inner.blue .brand:focus .caret,#navbar .navbar-inner.blue .nav>li>a:focus .caret{border-bottom-color:#304466;border-top-color:#304466}#navbar .navbar-inner.blue .brand span{background-image:url(../img/tentacle-20x20.png)}@media(-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.blue .brand span{background-image:url(../img/tentacle-20x20@2x.png)}}#navbar .navbar-inner.blue .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.blue .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.blue .nav li.dropdown.open.active>.dropdown-toggle{background-color:#4380e9;background-image:-moz-linear-gradient(top,#0050da,#a7c8ff);background-image:-webkit-gradient(linear,0 0,0 100%,from(#0050da),to(#a7c8ff));background-image:-webkit-linear-gradient(top,#0050da,#a7c8ff);background-image:-o-linear-gradient(top,#0050da,#a7c8ff);background-image:linear-gradient(to bottom,#0050da,#a7c8ff);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0050da',endColorstr='#ffa7c8ff',GradientType=0)}#navbar .navbar-inner.violet{background-color:#9864f0;background-image:-moz-linear-gradient(top,#c8a7ff,#5000da);background-image:-webkit-gradient(linear,0 0,0 100%,from(#c8a7ff),to(#5000da));background-image:-webkit-linear-gradient(top,#c8a7ff,#5000da);background-image:-o-linear-gradient(top,#c8a7ff,#5000da);background-image:linear-gradient(to bottom,#c8a7ff,#5000da);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffc8a7ff',endColorstr='#ff5000da',GradientType=0)}#navbar .navbar-inner.violet .brand,#navbar .navbar-inner.violet .nav>li>a{text-shadow:0 1px 0 #5000da;color:#f2f2f2}#navbar .navbar-inner.violet .brand .caret,#navbar .navbar-inner.violet .nav>li>a .caret{border-bottom-color:#b58df9;border-top-color:#b58df9}#navbar .navbar-inner.violet .brand:hover .caret,#navbar .navbar-inner.violet .nav>li>a:hover .caret,#navbar .navbar-inner.violet .brand:focus .caret,#navbar .navbar-inner.violet .nav>li>a:focus .caret{border-bottom-color:#d3c0f5;border-top-color:#d3c0f5}#navbar .navbar-inner.violet .brand span{background-image:url(../img/tentacle-20x20-light.png)}@media(-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.violet .brand span{background-image:url(../img/tentacle-20x20-light@2x.png)}}#navbar .navbar-inner.violet .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.violet .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.violet .nav li.dropdown.open.active>.dropdown-toggle{background-color:#8043e9;background-image:-moz-linear-gradient(top,#5000da,#c8a7ff);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5000da),to(#c8a7ff));background-image:-webkit-linear-gradient(top,#5000da,#c8a7ff);background-image:-o-linear-gradient(top,#5000da,#c8a7ff);background-image:linear-gradient(to bottom,#5000da,#c8a7ff);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5000da',endColorstr='#ffc8a7ff',GradientType=0)}#navbar .navbar-inner.black{background-color:#4f4f4f;background-image:-moz-linear-gradient(top,#787878,#121212);background-image:-webkit-gradient(linear,0 0,0 100%,from(#787878),to(#121212));background-image:-webkit-linear-gradient(top,#787878,#121212);background-image:-o-linear-gradient(top,#787878,#121212);background-image:linear-gradient(to bottom,#787878,#121212);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff787878',endColorstr='#ff121212',GradientType=0)}#navbar .navbar-inner.black .brand,#navbar .navbar-inner.black .nav>li>a{text-shadow:0 1px 0 #121212;color:#f2f2f2}#navbar .navbar-inner.black .brand .caret,#navbar .navbar-inner.black .nav>li>a .caret{border-bottom-color:#959595;border-top-color:#959595}#navbar .navbar-inner.black .brand:hover .caret,#navbar .navbar-inner.black .nav>li>a:hover .caret,#navbar .navbar-inner.black .brand:focus .caret,#navbar .navbar-inner.black .nav>li>a:focus .caret{border-bottom-color:#c4c4c4;border-top-color:#c4c4c4}#navbar .navbar-inner.black .brand span{background-image:url(../img/tentacle-20x20-light.png)}@media(-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.black .brand span{background-image:url(../img/tentacle-20x20-light@2x.png)}}#navbar .navbar-inner.black .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.black .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.black .nav li.dropdown.open.active>.dropdown-toggle{background-color:#3b3b3b;background-image:-moz-linear-gradient(top,#121212,#787878);background-image:-webkit-gradient(linear,0 0,0 100%,from(#121212),to(#787878));background-image:-webkit-linear-gradient(top,#121212,#787878);background-image:-o-linear-gradient(top,#121212,#787878);background-image:linear-gradient(to bottom,#121212,#787878);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff121212',endColorstr='#ff787878',GradientType=0)}#navbar .navbar-inner .brand span{background-size:20px 20px;background-position:left center;padding-left:24px;background-repeat:no-repeat}.octoprint-container .tab-content{padding:9px 15px;border-left:1px solid #DDD;border-right:1px solid #DDD;border-bottom:1px solid #DDD;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px}.octoprint-container .nav{margin-bottom:0}.octoprint-container .tab-content h1{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333;border:0;border-bottom:1px solid #e5e5e5;font-weight:normal}.octoprint-container .accordion-heading .accordion-heading-button{float:right}.octoprint-container .accordion-heading .accordion-heading-button a{display:inline-block;padding:8px 15px;font-size:14px;line-height:20px;color:#000;text-decoration:none;background:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.octoprint-container .accordion-heading a.accordion-toggle{display:inline-block}.octoprint-container .accordion-heading [class^="icon-"],.octoprint-container .accordion-heading [class*=" icon-"]{color:#000}.print-control .btn{padding-left:4px;padding-right:4px}.upload-buttons .btn{margin-right:0}table{table-layout:fixed}table .popover-title{text-overflow:ellipsis;word-break:break-all}table th,table td{overflow:hidden}table th.gcode_files_name,table td.gcode_files_name{text-overflow:ellipsis;white-space:nowrap;text-align:left}table th.gcode_files_size,table td.gcode_files_size{text-align:right;width:55px}table th.gcode_files_action,table td.gcode_files_action{text-align:center;width:70px}table th.gcode_files_action a,table td.gcode_files_action a{text-decoration:none;color:#000}table th.gcode_files_action a.disabled,table td.gcode_files_action a.disabled{color:#ccc;cursor:default}table th.timelapse_files_name,table td.timelapse_files_name{text-overflow:ellipsis;text-align:left}table th.timelapse_files_size,table td.timelapse_files_size{text-align:right;width:55px}table th.timelapse_files_action,table td.timelapse_files_action{text-align:center;width:45px}table th.timelapse_files_action a,table td.timelapse_files_action a{text-decoration:none;color:#000}table th.timelapse_files_action a.disabled,table td.timelapse_files_action a.disabled{color:#ccc;cursor:default}table th.settings_users_name,table td.settings_users_name{text-overflow:ellipsis;text-align:left}table th.settings_users_active,table td.settings_users_active,table th.settings_users_admin,table td.settings_users_admin{text-align:center;width:55px}table th.settings_users_actions,table td.settings_users_actions{text-align:center;width:60px}table th.settings_users_actions a,table td.settings_users_actions a{text-decoration:none;color:#000}table th.settings_users_actions a.disabled,table td.settings_users_actions a.disabled{color:#ccc;cursor:default}#temperature-graph{height:350px;width:100%;background-image:url("../img/graph-background.png");background-position:center;background-repeat:no-repeat}.tab-content,.tab-pane{overflow:visible}.tempInput{width:50px}#temp_newTemp,#temp_newBedTemp,#speed_innerWall,#speed_outerWall,#speed_fill,#speed_support,#webcam_timelapse_interval{text-align:right}ul.dropdown-menu li a{cursor:pointer}#connection_ports,#connection_baudrates{width:100%}#offline_overlay{position:fixed;top:0;left:0;width:100%;height:100%;z-index:10000;display:none}#offline_overlay_background{position:fixed;top:0;left:0;width:100%;height:100%;background-color:#000;filter:alpha(opacity=50);-moz-opacity:.5;-khtml-opacity:.5;opacity:.5}#offline_overlay_wrapper{position:absolute;top:0;bottom:0;left:0;right:0;padding-top:60px}#offline_overlay_wrapper .container{margin:auto}#webcam_container{width:100%}#webcam_container .flipH{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1)}#webcam_container .flipV{-webkit-transform:scaleY(-1);-moz-transform:scaleY(-1)}#webcam_container .flipH.flipV{-webkit-transform:scaleX(-1) scaleY(-1);-moz-transform:scaleX(-1) scaleY(-1)}#files .popover{font-size:85%}#files .popover p:last-child{margin-bottom:0}#files table{margin-bottom:0}#control{overflow:hidden}#control .jog-panel{float:left;margin-right:19px}#control h1{text-align:left}#control .jog-panel>div{text-align:center}#control .jog-panel>div.distance{text-align:left}#control .box{width:30px;height:30px;margin-right:10px;margin-bottom:10px;padding-left:8px}#control .control-box{display:block;height:30px;margin-bottom:10px}#control .btn-group{margin-bottom:10px}#control .btn-group.distance>.btn{width:43px;padding:3px 0;height:30px}#gcode .progress{width:582px}#gcode .progress .bar{-webkit-transition:width 0s linear;-moz-transition:width 0s linear;-o-transition:width 0s linear;transition:width 0s linear}#gcode #gcode_layer_slider{height:568px;float:right}#gcode #gcode_layer_slider .slider-handle{width:14px;height:14px;margin-left:-3px;margin-top:-7px}#gcode #gcode_command_slider .slider-handle{width:14px;height:14px;margin-left:-7px;margin-top:-3px}#term #terminal-output{min-height:340px}.footer ul{margin:0}.footer ul li{display:inline;margin-left:1em;font-size:85%}.footer ul li:first-child{margin-left:0}.footer ul li a{color:#555}.text-right{text-align:right}.overflow_visible{overflow:visible!important}#drop_overlay{position:fixed;top:0;left:0;width:100%;height:100%;z-index:10000;display:none}#drop_overlay.in{display:block}#drop_overlay #drop_overlay_background{position:fixed;top:0;left:0;width:100%;height:100%;background-color:#000;filter:alpha(opacity=50);-moz-opacity:.5;-khtml-opacity:.5;opacity:.5}#drop_overlay #drop_overlay_wrapper{position:absolute;top:0;bottom:0;left:0;right:0;padding-top:60px}#drop_overlay #drop_overlay_wrapper #drop,#drop_overlay #drop_overlay_wrapper #drop_background{position:absolute;top:50%;left:50%;margin-left:-200px;margin-top:-200px}#drop_overlay #drop_overlay_wrapper #drop_locally,#drop_overlay #drop_overlay_wrapper #drop_locally_background{position:absolute;top:50%;left:50%;margin-left:-425px;margin-top:-200px}#drop_overlay #drop_overlay_wrapper #drop_sd,#drop_overlay #drop_overlay_wrapper #drop_sd_background{position:absolute;top:50%;left:50%;margin-left:25px;margin-top:-200px}#drop_overlay #drop_overlay_wrapper .dropzone{width:404px;height:404px;z-index:10001;color:#fff;font-size:30px}#drop_overlay #drop_overlay_wrapper .dropzone i{font-size:50px}#drop_overlay #drop_overlay_wrapper .dropzone .centered{display:table-cell;text-align:center;vertical-align:middle;width:400px;height:400px;line-height:40px;filter:alpha(opacity=100);-moz-opacity:1.0;-khtml-opacity:1.0;opacity:1.0}#drop_overlay #drop_overlay_wrapper .dropzone_background{width:400px;height:400px;border:2px dashed #eee;-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;background-color:#000;filter:alpha(opacity=25);-moz-opacity:.25;-khtml-opacity:.25;opacity:.25}#drop_overlay #drop_overlay_wrapper .dropzone_background.hover{background-color:#000;filter:alpha(opacity=50);-moz-opacity:.5;-khtml-opacity:.5;opacity:.5}#drop_overlay #drop_overlay_wrapper .dropzone_background.fade{-webkit-transition:all .3s ease-out;-moz-transition:all .3s ease-out;-ms-transition:all .3s ease-out;-o-transition:all .3s ease-out;transition:all .3s ease-out;opacity:1}.icon-sd-black-14{background:url("../img/icon-sd-black-14.png") 0 3px no-repeat;width:11px;height:17px}.slider .slider-selection{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#006dcc;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#04c;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.slider .slider-selection:hover,.slider .slider-selection:focus,.slider .slider-selection:active,.slider .slider-selection.active,.slider .slider-selection.disabled,.slider .slider-selection[disabled]{color:#fff;background-color:#04c;*background-color:#003bb3}.slider .slider-selection:active,.slider .slider-selection.active{background-color:#039 \9}.slider.slider-disabled .slider-selection{background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.slider .slider-track{background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.slider.slider-disabled .slider-track{background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.slider .slider-handle{display:inline-block;*display:inline;*zoom:1;padding:4px 12px;font-size:14px;line-height:20px;text-align:center;vertical-align:middle;cursor:pointer;color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#f5f5f5;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#e6e6e6;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);border:1px solid #ccc;*border:0;border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*margin-left:.3em;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);padding:0;margin-bottom:0;opacity:1;filter:alpha(opacity=100)}.slider .slider-handle:hover,.slider .slider-handle:focus,.slider .slider-handle:active,.slider .slider-handle.active,.slider .slider-handle.disabled,.slider .slider-handle[disabled]{color:#333;background-color:#e6e6e6;*background-color:#d9d9d9}.slider .slider-handle:active,.slider .slider-handle.active{background-color:#ccc \9}.slider .slider-handle:first-child{*margin-left:0}.slider .slider-handle:hover,.slider .slider-handle:focus{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.slider .slider-handle:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.slider .slider-handle.active,.slider .slider-handle:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05)}.slider .slider-handle.disabled,.slider .slider-handle[disabled]{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.slider .slider-handle.hide{display:none}.slider .slider-handle.round{-webkit-border-radius:50%;-moz-border-radius:50%;border-radius:50%} \ No newline at end of file +.btn{display:inline-block;*display:inline;*zoom:1;padding:4px 12px;margin-bottom:0;font-size:14px;line-height:20px;text-align:center;vertical-align:middle;cursor:pointer;color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#f5f5f5;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#e6e6e6;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);border:1px solid #ccc;*border:0;border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*margin-left:.3em;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05)}.btn:hover,.btn:focus,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333;background-color:#e6e6e6;*background-color:#d9d9d9}.btn:active,.btn.active{background-color:#ccc \9}.btn:first-child{*margin-left:0}.btn:hover,.btn:focus{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05)}.btn.disabled,.btn[disabled]{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:11px 19px;font-size:17.5px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:4px}.btn-small{padding:2px 10px;font-size:11.9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0}.btn-mini [class^="icon-"],.btn-mini [class*=" icon-"]{margin-top:-1px}.btn-mini{padding:0 6px;font-size:10.5px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-block{display:block;width:100%;padding-left:0;padding-right:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75)}.btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#006dcc;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#04c;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#fff;background-color:#04c;*background-color:#003bb3}.btn-primary:active,.btn-primary.active{background-color:#039 \9}.btn-warning{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#faa732;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0);border-color:#f89406 #f89406 #ad6704;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#f89406;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#fff;background-color:#f89406;*background-color:#df8505}.btn-warning:active,.btn-warning.active{background-color:#c67605 \9}.btn-danger{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#da4f49;background-image:-moz-linear-gradient(top,#ee5f5b,#bd362f);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#bd362f));background-image:-webkit-linear-gradient(top,#ee5f5b,#bd362f);background-image:-o-linear-gradient(top,#ee5f5b,#bd362f);background-image:linear-gradient(to bottom,#ee5f5b,#bd362f);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffbd362f',GradientType=0);border-color:#bd362f #bd362f #802420;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#bd362f;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#fff;background-color:#bd362f;*background-color:#a9302a}.btn-danger:active,.btn-danger.active{background-color:#942a25 \9}.btn-success{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#5bb75b;background-image:-moz-linear-gradient(top,#62c462,#51a351);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#51a351));background-image:-webkit-linear-gradient(top,#62c462,#51a351);background-image:-o-linear-gradient(top,#62c462,#51a351);background-image:linear-gradient(to bottom,#62c462,#51a351);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff51a351',GradientType=0);border-color:#51a351 #51a351 #387038;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#51a351;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#fff;background-color:#51a351;*background-color:#499249}.btn-success:active,.btn-success.active{background-color:#408140 \9}.btn-info{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#49afcd;background-image:-moz-linear-gradient(top,#5bc0de,#2f96b4);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#2f96b4));background-image:-webkit-linear-gradient(top,#5bc0de,#2f96b4);background-image:-o-linear-gradient(top,#5bc0de,#2f96b4);background-image:linear-gradient(to bottom,#5bc0de,#2f96b4);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff2f96b4',GradientType=0);border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#2f96b4;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#fff;background-color:#2f96b4;*background-color:#2a85a0}.btn-info:active,.btn-info.active{background-color:#24748c \9}.btn-inverse{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#363636;background-image:-moz-linear-gradient(top,#444,#222);background-image:-webkit-gradient(linear,0 0,0 100%,from(#444),to(#222));background-image:-webkit-linear-gradient(top,#444,#222);background-image:-o-linear-gradient(top,#444,#222);background-image:linear-gradient(to bottom,#444,#222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444',endColorstr='#ff222222',GradientType=0);border-color:#222 #222 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#222;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.btn-inverse:hover,.btn-inverse:focus,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#fff;background-color:#222;*background-color:#151515}.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9}button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0}button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px}button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px}button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px}.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-link{border-color:transparent;cursor:pointer;color:#08c;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-link:hover,.btn-link:focus{color:#005580;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,.btn-link[disabled]:focus{color:#333;text-decoration:none}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:"";line-height:0}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}body{padding-top:60px}#navbar .navbar-inner{background-color:#ebebeb;background-image:-moz-linear-gradient(top,#fff,#ccc);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#ccc));background-image:-webkit-linear-gradient(top,#fff,#ccc);background-image:-o-linear-gradient(top,#fff,#ccc);background-image:linear-gradient(to bottom,#fff,#ccc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffcccccc',GradientType=0)}#navbar .navbar-inner .brand,#navbar .navbar-inner .nav>li>a{text-shadow:0 1px 0 #fff;color:#333}#navbar .navbar-inner .brand .caret,#navbar .navbar-inner .nav>li>a .caret{border-bottom-color:#939393;border-top-color:#939393}#navbar .navbar-inner .brand:hover .caret,#navbar .navbar-inner .nav>li>a:hover .caret,#navbar .navbar-inner .brand:focus .caret,#navbar .navbar-inner .nav>li>a:focus .caret{border-bottom-color:#636363;border-top-color:#636363}#navbar .navbar-inner .brand span{background-image:url(../img/tentacle-20x20.png)}@media(-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner .brand span{background-image:url(../img/tentacle-20x20@2x.png)}}#navbar .navbar-inner .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner .nav li.dropdown.open.active>.dropdown-toggle{background-color:#e0e0e0;background-image:-moz-linear-gradient(top,#ccc,#fff);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ccc),to(#fff));background-image:-webkit-linear-gradient(top,#ccc,#fff);background-image:-o-linear-gradient(top,#ccc,#fff);background-image:linear-gradient(to bottom,#ccc,#fff);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffcccccc',endColorstr='#ffffffff',GradientType=0)}#navbar .navbar-inner.red{background-color:#e36565;background-image:-moz-linear-gradient(top,#f9a0a0,#c20c0c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f9a0a0),to(#c20c0c));background-image:-webkit-linear-gradient(top,#f9a0a0,#c20c0c);background-image:-o-linear-gradient(top,#f9a0a0,#c20c0c);background-image:linear-gradient(to bottom,#f9a0a0,#c20c0c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9a0a0',endColorstr='#ffc20c0c',GradientType=0)}#navbar .navbar-inner.red .brand,#navbar .navbar-inner.red .nav>li>a{text-shadow:0 1px 0 #c20c0c;color:#f2f2f2}#navbar .navbar-inner.red .brand .caret,#navbar .navbar-inner.red .nav>li>a .caret{border-bottom-color:#f28d8d;border-top-color:#f28d8d}#navbar .navbar-inner.red .brand:hover .caret,#navbar .navbar-inner.red .nav>li>a:hover .caret,#navbar .navbar-inner.red .brand:focus .caret,#navbar .navbar-inner.red .nav>li>a:focus .caret{border-bottom-color:#f2c0c0;border-top-color:#f2c0c0}#navbar .navbar-inner.red .brand span{background-image:url(../img/tentacle-20x20-light.png)}@media(-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.red .brand span{background-image:url(../img/tentacle-20x20-light@2x.png)}}#navbar .navbar-inner.red .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.red .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.red .nav li.dropdown.open.active>.dropdown-toggle{background-color:#d84747;background-image:-moz-linear-gradient(top,#c20c0c,#f9a0a0);background-image:-webkit-gradient(linear,0 0,0 100%,from(#c20c0c),to(#f9a0a0));background-image:-webkit-linear-gradient(top,#c20c0c,#f9a0a0);background-image:-o-linear-gradient(top,#c20c0c,#f9a0a0);background-image:linear-gradient(to bottom,#c20c0c,#f9a0a0);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffc20c0c',endColorstr='#fff9a0a0',GradientType=0)}#navbar .navbar-inner.orange{background-color:#e39665;background-image:-moz-linear-gradient(top,#f9c3a0,#c2530c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f9c3a0),to(#c2530c));background-image:-webkit-linear-gradient(top,#f9c3a0,#c2530c);background-image:-o-linear-gradient(top,#f9c3a0,#c2530c);background-image:linear-gradient(to bottom,#f9c3a0,#c2530c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9c3a0',endColorstr='#ffc2530c',GradientType=0)}#navbar .navbar-inner.orange .brand,#navbar .navbar-inner.orange .nav>li>a{text-shadow:0 1px 0 #f6a570;color:#333}#navbar .navbar-inner.orange .brand .caret,#navbar .navbar-inner.orange .nav>li>a .caret{border-bottom-color:#93552e;border-top-color:#93552e}#navbar .navbar-inner.orange .brand:hover .caret,#navbar .navbar-inner.orange .nav>li>a:hover .caret,#navbar .navbar-inner.orange .brand:focus .caret,#navbar .navbar-inner.orange .nav>li>a:focus .caret{border-bottom-color:#634430;border-top-color:#634430}#navbar .navbar-inner.orange .brand span{background-image:url(../img/tentacle-20x20.png)}@media(-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.orange .brand span{background-image:url(../img/tentacle-20x20@2x.png)}}#navbar .navbar-inner.orange .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.orange .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.orange .nav li.dropdown.open.active>.dropdown-toggle{background-color:#d88047;background-image:-moz-linear-gradient(top,#c2530c,#f9c3a0);background-image:-webkit-gradient(linear,0 0,0 100%,from(#c2530c),to(#f9c3a0));background-image:-webkit-linear-gradient(top,#c2530c,#f9c3a0);background-image:-o-linear-gradient(top,#c2530c,#f9c3a0);background-image:linear-gradient(to bottom,#c2530c,#f9c3a0);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffc2530c',endColorstr='#fff9c3a0',GradientType=0)}#navbar .navbar-inner.yellow{background-color:#e3d765;background-image:-moz-linear-gradient(top,#f9f0a0,#c2b00c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f9f0a0),to(#c2b00c));background-image:-webkit-linear-gradient(top,#f9f0a0,#c2b00c);background-image:-o-linear-gradient(top,#f9f0a0,#c2b00c);background-image:linear-gradient(to bottom,#f9f0a0,#c2b00c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f0a0',endColorstr='#ffc2b00c',GradientType=0)}#navbar .navbar-inner.yellow .brand,#navbar .navbar-inner.yellow .nav>li>a{text-shadow:0 1px 0 #f6e970;color:#333}#navbar .navbar-inner.yellow .brand .caret,#navbar .navbar-inner.yellow .nav>li>a .caret{border-bottom-color:#93892e;border-top-color:#93892e}#navbar .navbar-inner.yellow .brand:hover .caret,#navbar .navbar-inner.yellow .nav>li>a:hover .caret,#navbar .navbar-inner.yellow .brand:focus .caret,#navbar .navbar-inner.yellow .nav>li>a:focus .caret{border-bottom-color:#635e30;border-top-color:#635e30}#navbar .navbar-inner.yellow .brand span{background-image:url(../img/tentacle-20x20.png)}@media(-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.yellow .brand span{background-image:url(../img/tentacle-20x20@2x.png)}}#navbar .navbar-inner.yellow .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.yellow .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.yellow .nav li.dropdown.open.active>.dropdown-toggle{background-color:#d8ca47;background-image:-moz-linear-gradient(top,#c2b00c,#f9f0a0);background-image:-webkit-gradient(linear,0 0,0 100%,from(#c2b00c),to(#f9f0a0));background-image:-webkit-linear-gradient(top,#c2b00c,#f9f0a0);background-image:-o-linear-gradient(top,#c2b00c,#f9f0a0);background-image:linear-gradient(to bottom,#c2b00c,#f9f0a0);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffc2b00c',endColorstr='#fff9f0a0',GradientType=0)}#navbar .navbar-inner.green{background-color:#98f064;background-image:-moz-linear-gradient(top,#c8ffa7,#50da00);background-image:-webkit-gradient(linear,0 0,0 100%,from(#c8ffa7),to(#50da00));background-image:-webkit-linear-gradient(top,#c8ffa7,#50da00);background-image:-o-linear-gradient(top,#c8ffa7,#50da00);background-image:linear-gradient(to bottom,#c8ffa7,#50da00);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffc8ffa7',endColorstr='#ff50da00',GradientType=0)}#navbar .navbar-inner.green .brand,#navbar .navbar-inner.green .nav>li>a{text-shadow:0 1px 0 #a7ff74;color:#333}#navbar .navbar-inner.green .brand .caret,#navbar .navbar-inner.green .nav>li>a .caret{border-bottom-color:#55992e;border-top-color:#55992e}#navbar .navbar-inner.green .brand:hover .caret,#navbar .navbar-inner.green .nav>li>a:hover .caret,#navbar .navbar-inner.green .brand:focus .caret,#navbar .navbar-inner.green .nav>li>a:focus .caret{border-bottom-color:#446630;border-top-color:#446630}#navbar .navbar-inner.green .brand span{background-image:url(../img/tentacle-20x20.png)}@media(-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.green .brand span{background-image:url(../img/tentacle-20x20@2x.png)}}#navbar .navbar-inner.green .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.green .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.green .nav li.dropdown.open.active>.dropdown-toggle{background-color:#80e943;background-image:-moz-linear-gradient(top,#50da00,#c8ffa7);background-image:-webkit-gradient(linear,0 0,0 100%,from(#50da00),to(#c8ffa7));background-image:-webkit-linear-gradient(top,#50da00,#c8ffa7);background-image:-o-linear-gradient(top,#50da00,#c8ffa7);background-image:linear-gradient(to bottom,#50da00,#c8ffa7);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff50da00',endColorstr='#ffc8ffa7',GradientType=0)}#navbar .navbar-inner.blue{background-color:#6498f0;background-image:-moz-linear-gradient(top,#a7c8ff,#0050da);background-image:-webkit-gradient(linear,0 0,0 100%,from(#a7c8ff),to(#0050da));background-image:-webkit-linear-gradient(top,#a7c8ff,#0050da);background-image:-o-linear-gradient(top,#a7c8ff,#0050da);background-image:linear-gradient(to bottom,#a7c8ff,#0050da);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffa7c8ff',endColorstr='#ff0050da',GradientType=0)}#navbar .navbar-inner.blue .brand,#navbar .navbar-inner.blue .nav>li>a{text-shadow:0 1px 0 #74a7ff;color:#333}#navbar .navbar-inner.blue .brand .caret,#navbar .navbar-inner.blue .nav>li>a .caret{border-bottom-color:#2e5599;border-top-color:#2e5599}#navbar .navbar-inner.blue .brand:hover .caret,#navbar .navbar-inner.blue .nav>li>a:hover .caret,#navbar .navbar-inner.blue .brand:focus .caret,#navbar .navbar-inner.blue .nav>li>a:focus .caret{border-bottom-color:#304466;border-top-color:#304466}#navbar .navbar-inner.blue .brand span{background-image:url(../img/tentacle-20x20.png)}@media(-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.blue .brand span{background-image:url(../img/tentacle-20x20@2x.png)}}#navbar .navbar-inner.blue .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.blue .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.blue .nav li.dropdown.open.active>.dropdown-toggle{background-color:#4380e9;background-image:-moz-linear-gradient(top,#0050da,#a7c8ff);background-image:-webkit-gradient(linear,0 0,0 100%,from(#0050da),to(#a7c8ff));background-image:-webkit-linear-gradient(top,#0050da,#a7c8ff);background-image:-o-linear-gradient(top,#0050da,#a7c8ff);background-image:linear-gradient(to bottom,#0050da,#a7c8ff);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0050da',endColorstr='#ffa7c8ff',GradientType=0)}#navbar .navbar-inner.violet{background-color:#9864f0;background-image:-moz-linear-gradient(top,#c8a7ff,#5000da);background-image:-webkit-gradient(linear,0 0,0 100%,from(#c8a7ff),to(#5000da));background-image:-webkit-linear-gradient(top,#c8a7ff,#5000da);background-image:-o-linear-gradient(top,#c8a7ff,#5000da);background-image:linear-gradient(to bottom,#c8a7ff,#5000da);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffc8a7ff',endColorstr='#ff5000da',GradientType=0)}#navbar .navbar-inner.violet .brand,#navbar .navbar-inner.violet .nav>li>a{text-shadow:0 1px 0 #5000da;color:#f2f2f2}#navbar .navbar-inner.violet .brand .caret,#navbar .navbar-inner.violet .nav>li>a .caret{border-bottom-color:#b58df9;border-top-color:#b58df9}#navbar .navbar-inner.violet .brand:hover .caret,#navbar .navbar-inner.violet .nav>li>a:hover .caret,#navbar .navbar-inner.violet .brand:focus .caret,#navbar .navbar-inner.violet .nav>li>a:focus .caret{border-bottom-color:#d3c0f5;border-top-color:#d3c0f5}#navbar .navbar-inner.violet .brand span{background-image:url(../img/tentacle-20x20-light.png)}@media(-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.violet .brand span{background-image:url(../img/tentacle-20x20-light@2x.png)}}#navbar .navbar-inner.violet .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.violet .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.violet .nav li.dropdown.open.active>.dropdown-toggle{background-color:#8043e9;background-image:-moz-linear-gradient(top,#5000da,#c8a7ff);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5000da),to(#c8a7ff));background-image:-webkit-linear-gradient(top,#5000da,#c8a7ff);background-image:-o-linear-gradient(top,#5000da,#c8a7ff);background-image:linear-gradient(to bottom,#5000da,#c8a7ff);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5000da',endColorstr='#ffc8a7ff',GradientType=0)}#navbar .navbar-inner.black{background-color:#4f4f4f;background-image:-moz-linear-gradient(top,#787878,#121212);background-image:-webkit-gradient(linear,0 0,0 100%,from(#787878),to(#121212));background-image:-webkit-linear-gradient(top,#787878,#121212);background-image:-o-linear-gradient(top,#787878,#121212);background-image:linear-gradient(to bottom,#787878,#121212);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff787878',endColorstr='#ff121212',GradientType=0)}#navbar .navbar-inner.black .brand,#navbar .navbar-inner.black .nav>li>a{text-shadow:0 1px 0 #121212;color:#f2f2f2}#navbar .navbar-inner.black .brand .caret,#navbar .navbar-inner.black .nav>li>a .caret{border-bottom-color:#959595;border-top-color:#959595}#navbar .navbar-inner.black .brand:hover .caret,#navbar .navbar-inner.black .nav>li>a:hover .caret,#navbar .navbar-inner.black .brand:focus .caret,#navbar .navbar-inner.black .nav>li>a:focus .caret{border-bottom-color:#c4c4c4;border-top-color:#c4c4c4}#navbar .navbar-inner.black .brand span{background-image:url(../img/tentacle-20x20-light.png)}@media(-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.black .brand span{background-image:url(../img/tentacle-20x20-light@2x.png)}}#navbar .navbar-inner.black .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.black .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.black .nav li.dropdown.open.active>.dropdown-toggle{background-color:#3b3b3b;background-image:-moz-linear-gradient(top,#121212,#787878);background-image:-webkit-gradient(linear,0 0,0 100%,from(#121212),to(#787878));background-image:-webkit-linear-gradient(top,#121212,#787878);background-image:-o-linear-gradient(top,#121212,#787878);background-image:linear-gradient(to bottom,#121212,#787878);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff121212',endColorstr='#ff787878',GradientType=0)}#navbar .navbar-inner .brand span{background-size:20px 20px;background-position:left center;padding-left:24px;background-repeat:no-repeat}.octoprint-container .tab-content{padding:9px 15px;border-left:1px solid #DDD;border-right:1px solid #DDD;border-bottom:1px solid #DDD;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px}.octoprint-container .nav{margin-bottom:0}.octoprint-container .tab-content h1{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333;border:0;border-bottom:1px solid #e5e5e5;font-weight:normal}.octoprint-container .accordion-heading .accordion-heading-button{float:right}.octoprint-container .accordion-heading .accordion-heading-button a{display:inline-block;padding:8px 15px;font-size:14px;line-height:20px;color:#000;text-decoration:none;background:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.octoprint-container .accordion-heading a.accordion-toggle{display:inline-block}.octoprint-container .accordion-heading [class^="icon-"],.octoprint-container .accordion-heading [class*=" icon-"]{color:#000}.print-control .btn{padding-left:4px;padding-right:4px}.upload-buttons .btn{margin-right:0}table{table-layout:fixed}table .popover-title{text-overflow:ellipsis;word-break:break-all}table th,table td{overflow:hidden}table th.gcode_files_name,table td.gcode_files_name{text-overflow:ellipsis;white-space:nowrap;text-align:left}table th.gcode_files_size,table td.gcode_files_size{text-align:right;width:55px}table th.gcode_files_action,table td.gcode_files_action{text-align:center;width:70px}table th.gcode_files_action a,table td.gcode_files_action a{text-decoration:none;color:#000}table th.gcode_files_action a.disabled,table td.gcode_files_action a.disabled{color:#ccc;cursor:default}table th.timelapse_files_name,table td.timelapse_files_name{text-overflow:ellipsis;text-align:left}table th.timelapse_files_size,table td.timelapse_files_size{text-align:right;width:55px}table th.timelapse_files_action,table td.timelapse_files_action{text-align:center;width:45px}table th.timelapse_files_action a,table td.timelapse_files_action a{text-decoration:none;color:#000}table th.timelapse_files_action a.disabled,table td.timelapse_files_action a.disabled{color:#ccc;cursor:default}table th.settings_users_name,table td.settings_users_name{text-overflow:ellipsis;text-align:left}table th.settings_users_active,table td.settings_users_active,table th.settings_users_admin,table td.settings_users_admin{text-align:center;width:55px}table th.settings_users_actions,table td.settings_users_actions{text-align:center;width:60px}table th.settings_users_actions a,table td.settings_users_actions a{text-decoration:none;color:#000}table th.settings_users_actions a.disabled,table td.settings_users_actions a.disabled{color:#ccc;cursor:default}table th.settings_logs_name,table td.settings_logs_name{text-overflow:ellipsis;text-align:left}table th.settings_logs_size,table td.settings_logs_size{text-align:right;width:70px}table th.settings_logs_date,table td.settings_logs_date{text-align:left;width:130px}table th.settings_logs_action,table td.settings_logs_action{text-align:center;width:70px}table th.settings_logs_action a,table td.settings_logs_action a{text-decoration:none;color:#000}table th.settings_logs_action a.disabled,table td.settings_logs_action a.disabled{color:#ccc;cursor:default}#temperature-graph{height:350px;width:100%;background-image:url("../img/graph-background.png");background-position:center;background-repeat:no-repeat}.tab-content,.tab-pane{overflow:visible}.tempInput{width:50px}#temp_newTemp,#temp_newBedTemp,#speed_innerWall,#speed_outerWall,#speed_fill,#speed_support,#webcam_timelapse_interval{text-align:right}ul.dropdown-menu li a{cursor:pointer}#connection_ports,#connection_baudrates{width:100%}#offline_overlay{position:fixed;top:0;left:0;width:100%;height:100%;z-index:10000;display:none}#offline_overlay_background{position:fixed;top:0;left:0;width:100%;height:100%;background-color:#000;filter:alpha(opacity=50);-moz-opacity:.5;-khtml-opacity:.5;opacity:.5}#offline_overlay_wrapper{position:absolute;top:0;bottom:0;left:0;right:0;padding-top:60px}#offline_overlay_wrapper .container{margin:auto}#webcam_container{width:100%}#webcam_container .flipH{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1)}#webcam_container .flipV{-webkit-transform:scaleY(-1);-moz-transform:scaleY(-1)}#webcam_container .flipH.flipV{-webkit-transform:scaleX(-1) scaleY(-1);-moz-transform:scaleX(-1) scaleY(-1)}#files .popover{font-size:85%}#files .popover p:last-child{margin-bottom:0}#files table{margin-bottom:0}#control{overflow:hidden}#control .jog-panel{float:left;margin-right:19px}#control h1{text-align:left}#control .jog-panel>div{text-align:center}#control .jog-panel>div.distance{text-align:left}#control .box{width:30px;height:30px;margin-right:10px;margin-bottom:10px;padding-left:8px}#control .control-box{display:block;height:30px;margin-bottom:10px}#control .btn-group{margin-bottom:10px}#control .btn-group.distance>.btn{width:43px;padding:3px 0;height:30px}#gcode .progress{width:582px}#gcode .progress .bar{-webkit-transition:width 0s linear;-moz-transition:width 0s linear;-o-transition:width 0s linear;transition:width 0s linear}#gcode #gcode_layer_slider{height:568px;float:right}#gcode #gcode_layer_slider .slider-handle{width:14px;height:14px;margin-left:-3px;margin-top:-7px}#gcode #gcode_command_slider .slider-handle{width:14px;height:14px;margin-left:-7px;margin-top:-3px}#term #terminal-output{min-height:340px}.footer ul{margin:0}.footer ul li{display:inline;margin-left:1em;font-size:85%}.footer ul li:first-child{margin-left:0}.footer ul li a{color:#555}.text-right{text-align:right}.overflow_visible{overflow:visible!important}#drop_overlay{position:fixed;top:0;left:0;width:100%;height:100%;z-index:10000;display:none}#drop_overlay.in{display:block}#drop_overlay #drop_overlay_background{position:fixed;top:0;left:0;width:100%;height:100%;background-color:#000;filter:alpha(opacity=50);-moz-opacity:.5;-khtml-opacity:.5;opacity:.5}#drop_overlay #drop_overlay_wrapper{position:absolute;top:0;bottom:0;left:0;right:0;padding-top:60px}#drop_overlay #drop_overlay_wrapper #drop,#drop_overlay #drop_overlay_wrapper #drop_background{position:absolute;top:50%;left:50%;margin-left:-200px;margin-top:-200px}#drop_overlay #drop_overlay_wrapper #drop_locally,#drop_overlay #drop_overlay_wrapper #drop_locally_background{position:absolute;top:50%;left:50%;margin-left:-425px;margin-top:-200px}#drop_overlay #drop_overlay_wrapper #drop_sd,#drop_overlay #drop_overlay_wrapper #drop_sd_background{position:absolute;top:50%;left:50%;margin-left:25px;margin-top:-200px}#drop_overlay #drop_overlay_wrapper .dropzone{width:404px;height:404px;z-index:10001;color:#fff;font-size:30px}#drop_overlay #drop_overlay_wrapper .dropzone i{font-size:50px}#drop_overlay #drop_overlay_wrapper .dropzone .centered{display:table-cell;text-align:center;vertical-align:middle;width:400px;height:400px;line-height:40px;filter:alpha(opacity=100);-moz-opacity:1.0;-khtml-opacity:1.0;opacity:1.0}#drop_overlay #drop_overlay_wrapper .dropzone_background{width:400px;height:400px;border:2px dashed #eee;-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;background-color:#000;filter:alpha(opacity=25);-moz-opacity:.25;-khtml-opacity:.25;opacity:.25}#drop_overlay #drop_overlay_wrapper .dropzone_background.hover{background-color:#000;filter:alpha(opacity=50);-moz-opacity:.5;-khtml-opacity:.5;opacity:.5}#drop_overlay #drop_overlay_wrapper .dropzone_background.fade{-webkit-transition:all .3s ease-out;-moz-transition:all .3s ease-out;-ms-transition:all .3s ease-out;-o-transition:all .3s ease-out;transition:all .3s ease-out;opacity:1}.icon-sd-black-14{background:url("../img/icon-sd-black-14.png") 0 3px no-repeat;width:11px;height:17px}.slider .slider-selection{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#006dcc;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#04c;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.slider .slider-selection:hover,.slider .slider-selection:focus,.slider .slider-selection:active,.slider .slider-selection.active,.slider .slider-selection.disabled,.slider .slider-selection[disabled]{color:#fff;background-color:#04c;*background-color:#003bb3}.slider .slider-selection:active,.slider .slider-selection.active{background-color:#039 \9}.slider.slider-disabled .slider-selection{background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.slider .slider-track{background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.slider.slider-disabled .slider-track{background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.slider .slider-handle{display:inline-block;*display:inline;*zoom:1;padding:4px 12px;font-size:14px;line-height:20px;text-align:center;vertical-align:middle;cursor:pointer;color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#f5f5f5;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#e6e6e6;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);border:1px solid #ccc;*border:0;border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*margin-left:.3em;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);padding:0;margin-bottom:0;opacity:1;filter:alpha(opacity=100)}.slider .slider-handle:hover,.slider .slider-handle:focus,.slider .slider-handle:active,.slider .slider-handle.active,.slider .slider-handle.disabled,.slider .slider-handle[disabled]{color:#333;background-color:#e6e6e6;*background-color:#d9d9d9}.slider .slider-handle:active,.slider .slider-handle.active{background-color:#ccc \9}.slider .slider-handle:first-child{*margin-left:0}.slider .slider-handle:hover,.slider .slider-handle:focus{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.slider .slider-handle:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.slider .slider-handle.active,.slider .slider-handle:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05)}.slider .slider-handle.disabled,.slider .slider-handle[disabled]{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.slider .slider-handle.hide{display:none}.slider .slider-handle.round{-webkit-border-radius:50%;-moz-border-radius:50%;border-radius:50%} \ No newline at end of file diff --git a/src/octoprint/static/js/app/viewmodels/log.js b/src/octoprint/static/js/app/viewmodels/log.js index 1f8af2d5..4f407303 100644 --- a/src/octoprint/static/js/app/viewmodels/log.js +++ b/src/octoprint/static/js/app/viewmodels/log.js @@ -13,7 +13,7 @@ function LogViewModel(loginStateViewModel) { if (a["name"].toLocaleLowerCase() > b["name"].toLocaleLowerCase()) return 1; return 0; }, - "creation": function(a, b) { + "modification": function(a, b) { // sorts descending if (a["date"] > b["date"]) return -1; if (a["date"] < b["date"]) return 1; diff --git a/src/octoprint/static/less/octoprint.less b/src/octoprint/static/less/octoprint.less index 8b9c7222..bbff59ce 100644 --- a/src/octoprint/static/less/octoprint.less +++ b/src/octoprint/static/less/octoprint.less @@ -279,6 +279,38 @@ table { } } } + + // log files + &.settings_logs_name { + text-overflow: ellipsis; + text-align: left; + } + + &.settings_logs_size { + text-align: right; + width: 70px; + } + + &.settings_logs_date { + text-align: left; + width: 130px; + } + + &.settings_logs_action { + text-align: center; + width: 70px; + + a { + text-decoration: none; + color: #000; + + &.disabled { + color: #ccc; + cursor: default; + } + } + } + } } diff --git a/src/octoprint/templates/settings.jinja2 b/src/octoprint/templates/settings.jinja2 index 340a12e7..6b373253 100644 --- a/src/octoprint/templates/settings.jinja2 +++ b/src/octoprint/templates/settings.jinja2 @@ -542,22 +542,24 @@ - - - + + + + - - - + + + From ace102e1c18499f409380b23973387dfa570f986 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Mon, 24 Feb 2014 09:57:51 +0100 Subject: [PATCH 5/6] Added API documentation, adjusted API and returned data model slightly to be consistent with other parts of the API, made log file management restricted to admins. TODO: Make downloading of logs also restricted to admins --- docs/api/index.rst | 2 +- docs/api/logs.rst | 177 ++++++++++++++++++++++++ src/octoprint/server/__init__.py | 2 +- src/octoprint/server/api/files.py | 8 +- src/octoprint/server/api/log.py | 34 +++-- src/octoprint/server/util.py | 55 +++++++- src/octoprint/templates/settings.jinja2 | 2 +- 7 files changed, 253 insertions(+), 27 deletions(-) create mode 100644 docs/api/logs.rst diff --git a/docs/api/index.rst b/docs/api/index.rst index 8d10a202..7d296a1b 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -12,4 +12,4 @@ API Documentation connection.rst printer.rst job.rst - + logs.rst diff --git a/docs/api/logs.rst b/docs/api/logs.rst new file mode 100644 index 00000000..abdb9ceb --- /dev/null +++ b/docs/api/logs.rst @@ -0,0 +1,177 @@ +.. _sec-api-logs: + +******************* +Log file management +******************* + +.. note:: + + All log file management operations require admin rights. + +.. contents:: + +.. _sec-api-logs-list: + +Retrieve a list of available log files +====================================== + +.. http:post:: /api/logs + + Retrieve information regarding all log files currently available and regarding the disk space still available + in the system on the location the log files are being stored. + + Returns a :ref:`Logfile Retrieve response `. + + **Example Request** + + .. sourcecode:: http + + GET /api/logs HTTP/1.1 + Host: example.com + X-Api-Key: abcdef... + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "files" : [ + { + "date" : 1393158814, + "name" : "octoprint.log", + "size" : 43712, + "refs": { + "resource": "http://example.com/api/logs/octoprint.log", + "download": "http://example.com/downloads/logs/octoprint.log" + } + }, + { + "date" : 1392628936, + "name" : "octoprint.log.2014-02-17", + "size" : 13205, + "refs": { + "resource": "http://example.com/api/logs/octoprint.log.2014-02-17", + "download": "http://example.com/downloads/logs/octoprint.log.2014-02-17" + } + }, + { + "date" : 1393158814, + "name" : "serial.log", + "size" : 1798419, + "refs": { + "resource": "http://example.com/api/logs/serial.log", + "download": "http://example.com/downloads/logs/serial.log" + } + } + ], + "free": 12237201408 + } + + :statuscode 200: No error + :statuscode 403: If the given API token did not have admin rights associated with it + +.. _sec-api-logs-delete: + +Delete a specific logfile +========================= + +.. http:delete:: /api/logs/(path:filename) + + Delete the selected log file with name `filename`. + + Returns a :http:statuscode:`204` after successful deletion. + + **Example Request** + + .. sourcecode:: http + + DELETE /api/logs/octoprint.log.2014-02-17 HTTP/1.1 + Host: example.com + X-Api-Key: abcdef... + + :param filename: The filename of the log file to delete + :statuscode 204: No error + :statuscode 403: If the given API token did not have admin rights associated with it + :statuscode 404: If the file was not found + +.. _sec-api-logs-datamodel: + +Datamodel +========= + +.. _sec-api-logs-datamodel-retrieveresponse: + +Logfile Retrieve Response +------------------------- + +.. list-table:: + :widths: 15 5 10 30 + :header-rows: 1 + + * - Name + - Multiplicity + - Type + - Description + * - ``files`` + - 0..* + - Array of :ref:`File information items ` + - The list of requested files. Might be an empty list if no files are available + * - ``free`` + - 1 + - String + - The amount of disk space in bytes available in the local disk space (refers to OctoPrint's ``logs`` folder). + +.. _sec-api-logs-datamodel-fileinfo: + +File information +---------------- + +.. list-table:: + :widths: 15 5 10 30 + :header-rows: 1 + + * - Name + - Multiplicity + - Type + - Description + * - ``name`` + - 1 + - String + - The name of the file + * - ``size`` + - 1 + - Number + - The size of the file in bytes. + * - ``date`` + - 1 + - Unix timestamp + - The timestamp when this file was last modified. + * - ``refs`` + - 1 + - :ref:`sec-api-logs-datamodel-ref` + - References relevant to this file + +.. _sec-api-logs-datamodel-ref: + +References +---------- + +.. list-table:: + :widths: 15 5 10 30 + :header-rows: 1 + + * - Name + - Multiplicity + - Type + - Description + * - ``resource`` + - 1 + - URL + - The resource that represents the file (e.g. for deleting) + * - ``download`` + - 1 + - URL + - The download URL for the file diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py index aa6af8de..79689adc 100644 --- a/src/octoprint/server/__init__.py +++ b/src/octoprint/server/__init__.py @@ -28,7 +28,7 @@ admin_permission = Permission(RoleNeed("admin")) user_permission = Permission(RoleNeed("user")) -from octoprint.server.util import LargeResponseHandler, ReverseProxied, restricted_access, PrinterStateConnection +from octoprint.server.util import LargeResponseHandler, ReverseProxied, restricted_access, PrinterStateConnection, admin_validator from octoprint.printer import Printer, getConnectionOptions from octoprint.settings import settings import octoprint.gcodefiles as gcodefiles diff --git a/src/octoprint/server/api/files.py b/src/octoprint/server/api/files.py index c704faf2..bba7b260 100644 --- a/src/octoprint/server/api/files.py +++ b/src/octoprint/server/api/files.py @@ -11,7 +11,6 @@ import octoprint.util as util from octoprint.filemanager.destinations import FileDestinations from octoprint.settings import settings, valid_boolean_trues from octoprint.server import printer, gcodeManager, eventManager, restricted_access, NO_CONTENT -from octoprint.server.util import urlForDownload from octoprint.server.api import api @@ -66,7 +65,7 @@ def _getFileList(origin): file.update({ "refs": { "resource": url_for(".readGcodeFile", target=FileDestinations.LOCAL, filename=file["name"], _external=True), - "download": urlForDownload(FileDestinations.LOCAL, file["name"]) + "download": url_for("index", _external=True) + "downloads/files/" + FileDestinations.LOCAL + "/" + file["name"] } }) return files @@ -169,7 +168,7 @@ def uploadGcodeFile(target): "origin": FileDestinations.LOCAL, "refs": { "resource": url_for(".readGcodeFile", target=FileDestinations.LOCAL, filename=filename, _external=True), - "download": urlForDownload(FileDestinations.LOCAL, filename) + "download": url_for("index", _external=True) + "downloads/files/" + FileDestinations.LOCAL + "/" + filename } } }) @@ -269,6 +268,5 @@ def deleteGcodeFile(filename, target): else: gcodeManager.removeFile(filename) - # return an updated list of files - return readGcodeFiles() + return NO_CONTENT diff --git a/src/octoprint/server/api/log.py b/src/octoprint/server/api/log.py index c2699628..44159d27 100644 --- a/src/octoprint/server/api/log.py +++ b/src/octoprint/server/api/log.py @@ -4,35 +4,43 @@ __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agp import os -from flask import request, jsonify, make_response, url_for +from flask import request, jsonify, url_for, make_response from werkzeug.utils import secure_filename from octoprint.settings import settings -from octoprint.server import restricted_access +from octoprint.server import restricted_access, NO_CONTENT, admin_permission from octoprint.server.util import redirectToTornado from octoprint.server.api import api +from octoprint.util import getFreeBytes @api.route("/logs", methods=["GET"]) -def getLogData(): +@restricted_access +@admin_permission.require(403) +def getLogFiles(): files = _getLogFiles() - return jsonify(files=files) + return jsonify(files=files, free=getFreeBytes(settings().getBaseFolder("logs"))) -@api.route("/logs/", methods=["GET"]) +@api.route("/logs/", methods=["GET"]) +@restricted_access +@admin_permission.require(403) def downloadLog(filename): return redirectToTornado(request, url_for("index") + "downloads/logs/" + filename) -@api.route("/logs/", methods=["DELETE"]) +@api.route("/logs/", methods=["DELETE"]) @restricted_access +@admin_permission.require(403) def deleteLog(filename): secure = os.path.join(settings().getBaseFolder("logs"), secure_filename(filename)) - if os.path.exists(secure): - os.remove(secure) + if not os.path.exists(secure): + return make_response("File not found: %s" % filename, 404) - return getLogData() + os.remove(secure) + + return NO_CONTENT def _getLogFiles(): @@ -42,10 +50,12 @@ def _getLogFiles(): statResult = os.stat(os.path.join(basedir, osFile)) files.append({ "name": osFile, - "size": statResult.st_size, - "bytes": statResult.st_size, "date": int(statResult.st_mtime), - "url": url_for("index") + "downloads/logs/" + osFile + "size": statResult.st_size, + "refs": { + "resource": url_for(".downloadLog", filename=osFile, _external=True), + "download": url_for("index", _external=True) + "downloads/logs/" + osFile + } }) return files diff --git a/src/octoprint/server/util.py b/src/octoprint/server/util.py index 3ff9389d..2741441e 100644 --- a/src/octoprint/server/util.py +++ b/src/octoprint/server/util.py @@ -1,7 +1,11 @@ +# coding=utf-8 +__author__ = "Gina Häußge " +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + from flask.ext.principal import identity_changed, Identity from tornado.web import StaticFileHandler, HTTPError from flask import url_for, make_response, request, current_app -from flask.ext.login import login_required, login_user +from flask.ext.login import login_required, login_user, current_user from werkzeug.utils import redirect from sockjs.tornado import SockJSConnection @@ -21,6 +25,7 @@ import octoprint.server from octoprint.users import ApiUser from octoprint.events import Events + def restricted_access(func, apiEnabled=True): """ If you decorate a view with this, it will ensure that first setup has been @@ -80,11 +85,32 @@ def api_access(func): return decorated_view +def _getUserForApiKey(apikey): + if settings().get(["api", "enabled"]) and apikey is not None: + if apikey == settings().get(["api", "key"]): + # master key was used + return ApiUser() + else: + # user key might have been used + return octoprint.server.userManager.findUser(apikey=apikey) + else: + return None + + def _getApiKey(request): - if "apikey" in request.values: + # Check Flask GET/POST arguments + if hasattr(request, "values") and "apikey" in request.values: return request.values["apikey"] + + # Check Tornado GET/POST arguments + if hasattr(request, "arguments") and "apikey" in request.arguments \ + and len(request.arguments["apikey"]) > 0 and len(request.arguments["apikey"].strip()) > 0: + return request.arguments["apikey"] + + # Check Tornado and Flask headers if "X-Api-Key" in request.headers.keys(): return request.headers.get("X-Api-Key") + return None @@ -204,11 +230,15 @@ class LargeResponseHandler(StaticFileHandler): CHUNK_SIZE = 16 * 1024 - def initialize(self, path, default_filename=None, as_attachment=False): + def initialize(self, path, default_filename=None, as_attachment=False, access_validation=None): StaticFileHandler.initialize(self, path, default_filename) self._as_attachment = as_attachment + self._access_validation = access_validation def get(self, path, include_body=True): + if self._access_validation is not None: + self._access_validation(self.request) + path = self.parse_url_path(path) abspath = os.path.abspath(os.path.join(self.root, path)) # os.path.abspath strips a trailing / @@ -273,6 +303,21 @@ class LargeResponseHandler(StaticFileHandler): self.set_header("Content-Disposition", "attachment") +#~~ admin access validator for tornado + + +# TODO doesnt work for flask right now (no app context of course), try to figure out something +def admin_validator(request): + apikey = _getApiKey(request) + if settings().get(["api", "enabled"]) and apikey is not None: + user = _getUserForApiKey(apikey) + else: + user = current_user + + if user is None or not user.is_authenticated() or not user.is_admin(): + raise HTTPError(403) + + #~~ reverse proxy compatible wsgi middleware @@ -333,7 +378,3 @@ def redirectToTornado(request, target): redirectUrl += fragment return redirect(redirectUrl) - -def urlForDownload(origin, filename): - return url_for("index", _external=True) + "downloads/files/" + origin + "/" + filename - diff --git a/src/octoprint/templates/settings.jinja2 b/src/octoprint/templates/settings.jinja2 index 6b373253..802f1ac8 100644 --- a/src/octoprint/templates/settings.jinja2 +++ b/src/octoprint/templates/settings.jinja2 @@ -560,7 +560,7 @@ From f844bd99c7242d8a7817e43c55a40e1d3ed83fbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Mon, 24 Feb 2014 14:13:55 +0100 Subject: [PATCH 6/6] Properly authenticate log file downloads on Tornado as well Only allow for users with role admin. --- src/octoprint/server/__init__.py | 19 +++++++++++++++++-- src/octoprint/server/util.py | 12 ++++++++++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py index 79689adc..51bf09f9 100644 --- a/src/octoprint/server/__init__.py +++ b/src/octoprint/server/__init__.py @@ -2,6 +2,8 @@ __author__ = "Gina Häußge " __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' +import flask +import tornado.wsgi from sockjs.tornado import SockJSRouter from flask import Flask, render_template, send_from_directory, make_response from flask.ext.login import LoginManager @@ -27,7 +29,7 @@ principals = Principal(app) admin_permission = Permission(RoleNeed("admin")) user_permission = Permission(RoleNeed("user")) - +# only import the octoprint stuff down here, as it might depend on things defined above to be initialized already from octoprint.server.util import LargeResponseHandler, ReverseProxied, restricted_access, PrinterStateConnection, admin_validator from octoprint.printer import Printer, getConnectionOptions from octoprint.settings import settings @@ -174,10 +176,23 @@ class Server(): self._router = SockJSRouter(self._createSocketConnection, "/sockjs") + def admin_access_validation(request): + """ + Creates a custom wsgi and Flask request context in order to be able to process user information + stored in the current session. + + :param request: The Tornado request for which to create the environment and context + """ + wsgi_environ = tornado.wsgi.WSGIContainer.environ(request) + with app.request_context(wsgi_environ): + app.session_interface.open_session(app, flask.request) + loginManager.reload_user() + admin_validator(flask.request) + self._tornado_app = Application(self._router.urls + [ (r"/downloads/timelapse/([^/]*\.mpg)", LargeResponseHandler, {"path": settings().getBaseFolder("timelapse"), "as_attachment": True}), (r"/downloads/files/local/([^/]*\.(gco|gcode))", LargeResponseHandler, {"path": settings().getBaseFolder("uploads"), "as_attachment": True}), - (r"/downloads/logs/([^/]*)", LargeResponseHandler, {"path": settings().getBaseFolder("logs"), "as_attachment": True}), + (r"/downloads/logs/([^/]*)", LargeResponseHandler, {"path": settings().getBaseFolder("logs"), "as_attachment": True, "access_validation": admin_access_validation}), (r".*", FallbackHandler, {"fallback": WSGIContainer(app.wsgi_app)}) ]) self._server = HTTPServer(self._tornado_app) diff --git a/src/octoprint/server/util.py b/src/octoprint/server/util.py index 2741441e..71149686 100644 --- a/src/octoprint/server/util.py +++ b/src/octoprint/server/util.py @@ -303,11 +303,19 @@ class LargeResponseHandler(StaticFileHandler): self.set_header("Content-Disposition", "attachment") -#~~ admin access validator for tornado +#~~ admin access validator for use with tornado -# TODO doesnt work for flask right now (no app context of course), try to figure out something def admin_validator(request): + """ + Validates that the given request is made by an admin user, identified either by API key or existing Flask + session. + + Must be executed in an existing Flask request context! + + :param request: The Flask request object + """ + apikey = _getApiKey(request) if settings().get(["api", "enabled"]) and apikey is not None: user = _getUserForApiKey(apikey)
    NameSizeActionNameSizeDateAction
    +  | 
    -  |  +  |