From 26747417971d8b543583063175a9e91cca5eecd6 Mon Sep 17 00:00:00 2001 From: Nicanor Romero Venier Date: Tue, 29 Sep 2015 19:08:22 +0200 Subject: [PATCH 01/20] Change the max temp of chart dynamically --- .../static/js/app/viewmodels/temperature.js | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/octoprint/static/js/app/viewmodels/temperature.js b/src/octoprint/static/js/app/viewmodels/temperature.js index 9b58d2ec..964e7b29 100644 --- a/src/octoprint/static/js/app/viewmodels/temperature.js +++ b/src/octoprint/static/js/app/viewmodels/temperature.js @@ -232,6 +232,8 @@ $(function() { var heaterOptions = self.heaterOptions(); if (!heaterOptions) return; + var maxTemps = [310]; + _.each(_.keys(heaterOptions), function(type) { if (type == "bed" && !self.hasBed()) { return; @@ -258,12 +260,32 @@ $(function() { color: pusher.color(heaterOptions[type].color).tint(0.5).html(), data: targets }); + + maxTemps.push(self.getMaxTemp(actuals, targets)); }); + self.plotOptions.yaxis.max = Math.max.apply(null, maxTemps); + self.plotOptions.yaxis.ticks = self.plotOptions.yaxis.max / 30; $.plot(graph, data, self.plotOptions); } }; + self.getMaxTemp = function(actuals, targets) { + var pair; + var maxTemp = 0; + actuals.forEach(function(pair) { + if (pair[1] > maxTemp){ + maxTemp = pair[1]; + } + }); + targets.forEach(function(pair) { + if (pair[1] > maxTemp){ + maxTemp = pair[1]; + } + }); + return maxTemp; + } + self.setTarget = function(item) { var value = item.newTarget(); if (!value) return; From 4a7c81d1c6aa196bec63bc7fd41894419c2a1745 Mon Sep 17 00:00:00 2001 From: Nicanor Romero Venier Date: Wed, 30 Sep 2015 09:20:40 +0200 Subject: [PATCH 02/20] Added offset to max Y in temperature chart --- src/octoprint/static/js/app/viewmodels/temperature.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/octoprint/static/js/app/viewmodels/temperature.js b/src/octoprint/static/js/app/viewmodels/temperature.js index 964e7b29..dc75c9f8 100644 --- a/src/octoprint/static/js/app/viewmodels/temperature.js +++ b/src/octoprint/static/js/app/viewmodels/temperature.js @@ -232,7 +232,7 @@ $(function() { var heaterOptions = self.heaterOptions(); if (!heaterOptions) return; - var maxTemps = [310]; + var maxTemps = [310/1.1]; _.each(_.keys(heaterOptions), function(type) { if (type == "bed" && !self.hasBed()) { @@ -264,8 +264,7 @@ $(function() { maxTemps.push(self.getMaxTemp(actuals, targets)); }); - self.plotOptions.yaxis.max = Math.max.apply(null, maxTemps); - self.plotOptions.yaxis.ticks = self.plotOptions.yaxis.max / 30; + self.plotOptions.yaxis.max = Math.max.apply(null, maxTemps) * 1.1; $.plot(graph, data, self.plotOptions); } }; From 918ffa2557d9bde56e7c3a50e66ac40210796658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Mon, 20 Jul 2015 16:42:28 +0200 Subject: [PATCH 03/20] Refactored timelapse core Capturing is now queue based, rendering will not start until all images have been captured, and timed postroll does not depend on system time anymore. Also refactored some of the names to be python naming compliant while at it. (cherry picked from commit 4f5dc70) --- src/octoprint/events.py | 3 + src/octoprint/server/api/timelapse.py | 10 +- src/octoprint/static/js/app/dataupdater.js | 19 +- src/octoprint/timelapse.py | 332 ++++++++++++--------- 4 files changed, 225 insertions(+), 139 deletions(-) diff --git a/src/octoprint/events.py b/src/octoprint/events.py index 82886233..2cc765d9 100644 --- a/src/octoprint/events.py +++ b/src/octoprint/events.py @@ -74,6 +74,9 @@ class Events(object): # Timelapse CAPTURE_START = "CaptureStart" CAPTURE_DONE = "CaptureDone" + CAPTURE_FAILED = "CaptureFailed" + POSTROLL_START = "PostRollStart" + POSTROLL_END = "PostRollEnd" MOVIE_RENDERING = "MovieRendering" MOVIE_DONE = "MovieDone" MOVIE_FAILED = "MovieFailed" diff --git a/src/octoprint/server/api/timelapse.py b/src/octoprint/server/api/timelapse.py index bef85536..63b5be34 100644 --- a/src/octoprint/server/api/timelapse.py +++ b/src/octoprint/server/api/timelapse.py @@ -29,14 +29,14 @@ def getTimelapseData(): config = {"type": "off"} if timelapse is not None and isinstance(timelapse, octoprint.timelapse.ZTimelapse): config["type"] = "zchange" - config["postRoll"] = timelapse.postRoll() - config["fps"] = timelapse.fps() + config["postRoll"] = timelapse.post_roll + config["fps"] = timelapse.fps elif timelapse is not None and isinstance(timelapse, octoprint.timelapse.TimedTimelapse): config["type"] = "timed" - config["postRoll"] = timelapse.postRoll() - config["fps"] = timelapse.fps() + config["postRoll"] = timelapse.post_roll + config["fps"] = timelapse.fps config.update({ - "interval": timelapse.interval() + "interval": timelapse.interval }) files = octoprint.timelapse.getFinishedTimelapses() diff --git a/src/octoprint/static/js/app/dataupdater.js b/src/octoprint/static/js/app/dataupdater.js index 53f5ef70..6a63cd28 100644 --- a/src/octoprint/static/js/app/dataupdater.js +++ b/src/octoprint/static/js/app/dataupdater.js @@ -177,6 +177,7 @@ function DataUpdater(allViewModels) { var type = data["type"]; var payload = data["payload"]; var html = ""; + var format = {}; log.debug("Got event " + type + " with payload: " + JSON.stringify(payload)); @@ -187,7 +188,23 @@ function DataUpdater(allViewModels) { } else if (type == "MovieFailed") { html = "

" + _.sprintf(gettext("Rendering of timelapse %(movie_basename)s failed with return code %(returncode)s"), payload) + "

"; html += pnotifyAdditionalInfo('
' + payload.error + '
'); - new PNotify({title: gettext("Rendering failed"), text: html, type: "error", hide: false}); + new PNotify({ + title: gettext("Rendering failed"), + text: html, + type: "error", + hide: false + }); + } else if (type == "PostRollStart") { + if (payload.postroll_duration > 60) { + format = {duration: _.sprintf(gettext("%(minutes)d min"), {minutes: payload.postroll_duration / 60})}; + } else { + format = {duration: _.sprintf(gettext("%(seconds)d sec"), {seconds: payload.postroll_duration})}; + } + + new PNotify({ + title: gettext("Capturing timelapse postroll"), + text: _.sprintf(gettext("Now capturing timelapse post roll, this will take approximately %(duration)s..."), format) + }); } else if (type == "SlicingStarted") { gcodeUploadProgress.addClass("progress-striped").addClass("active"); gcodeUploadProgressBar.css("width", "100%"); diff --git a/src/octoprint/timelapse.py b/src/octoprint/timelapse.py index e1e0b87f..f4862b73 100644 --- a/src/octoprint/timelapse.py +++ b/src/octoprint/timelapse.py @@ -6,13 +6,13 @@ __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agp import logging import os import threading -import urllib import time -import subprocess import fnmatch import datetime import sys import shutil +import Queue +import requests import octoprint.util as util @@ -58,7 +58,7 @@ def notifyCallbacks(timelapse): if timelapse is None: config = None else: - config = timelapse.configData() + config = timelapse.config_data() for callback in updateCallbacks: try: callback.sendTimelapseConfig(config) except: logging.getLogger(__name__).exception("Exception while pushing timelapse configuration") @@ -86,12 +86,12 @@ def configureTimelapse(config=None, persist=False): if type is None or "off" == type: current = None elif "zchange" == type: - current = ZTimelapse(postRoll=postRoll, fps=fps) + current = ZTimelapse(post_roll=postRoll, fps=fps) elif "timed" == type: interval = 10 if "options" in config and "interval" in config["options"] and config["options"]["interval"] > 0: interval = config["options"]["interval"] - current = TimedTimelapse(postRoll=postRoll, interval=interval, fps=fps) + current = TimedTimelapse(post_roll=postRoll, interval=interval, fps=fps) notifyCallbacks(current) @@ -101,72 +101,83 @@ def configureTimelapse(config=None, persist=False): class Timelapse(object): - def __init__(self, postRoll=0, fps=25): + QUEUE_ENTRY_TYPE_CAPTURE = "capture" + QUEUE_ENTRY_TYPE_CALLBACK = "callback" + + def __init__(self, post_roll=0, fps=25): self._logger = logging.getLogger(__name__) - self._imageNumber = None - self._inTimelapse = False - self._gcodeFile = None + self._image_number = None + self._in_timelapse = False + self._gcode_file = None - self._postRoll = postRoll - self._postRollStart = None - self._onPostRollDone = None + self._post_roll = post_roll + self._on_post_roll_done = None - self._captureDir = settings().getBaseFolder("timelapse_tmp") - self._movieDir = settings().getBaseFolder("timelapse") - self._snapshotUrl = settings().get(["webcam", "snapshot"]) - self._ffmpegThreads = settings().get(["webcam", "ffmpegThreads"]) + self._capture_dir = settings().getBaseFolder("timelapse_tmp") + self._movie_dir = settings().getBaseFolder("timelapse") + self._snapshot_url = settings().get(["webcam", "snapshot"]) + self._ffmpeg_threads = settings().get(["webcam", "ffmpegThreads"]) self._fps = fps - self._renderThread = None - self._captureMutex = threading.Lock() + self._render_thread = None + + self._capture_mutex = threading.Lock() + self._capture_queue = Queue.Queue() + self._capture_queue_active = True + + self._capture_queue_thread = threading.Thread(target=self._capture_queue_worker) + self._capture_queue_thread.daemon = True + self._capture_queue_thread.start() # subscribe events - eventManager().subscribe(Events.PRINT_STARTED, self.onPrintStarted) - eventManager().subscribe(Events.PRINT_FAILED, self.onPrintDone) - eventManager().subscribe(Events.PRINT_DONE, self.onPrintDone) - eventManager().subscribe(Events.PRINT_RESUMED, self.onPrintResumed) - for (event, callback) in self.eventSubscriptions(): + eventManager().subscribe(Events.PRINT_STARTED, self.on_print_started) + eventManager().subscribe(Events.PRINT_FAILED, self.on_print_done) + eventManager().subscribe(Events.PRINT_DONE, self.on_print_done) + eventManager().subscribe(Events.PRINT_RESUMED, self.on_print_resumed) + for (event, callback) in self.event_subscriptions(): eventManager().subscribe(event, callback) - def postRoll(self): - return self._postRoll + @property + def post_roll(self): + return self._post_roll + @property def fps(self): return self._fps def unload(self): - if self._inTimelapse: - self.stopTimelapse(doCreateMovie=False) + if self._in_timelapse: + self.stop_timelapse(doCreateMovie=False) # unsubscribe events - eventManager().unsubscribe(Events.PRINT_STARTED, self.onPrintStarted) - eventManager().unsubscribe(Events.PRINT_FAILED, self.onPrintDone) - eventManager().unsubscribe(Events.PRINT_DONE, self.onPrintDone) - eventManager().unsubscribe(Events.PRINT_RESUMED, self.onPrintResumed) - for (event, callback) in self.eventSubscriptions(): + eventManager().unsubscribe(Events.PRINT_STARTED, self.on_print_started) + eventManager().unsubscribe(Events.PRINT_FAILED, self.on_print_done) + eventManager().unsubscribe(Events.PRINT_DONE, self.on_print_done) + eventManager().unsubscribe(Events.PRINT_RESUMED, self.on_print_resumed) + for (event, callback) in self.event_subscriptions(): eventManager().unsubscribe(event, callback) - def onPrintStarted(self, event, payload): + def on_print_started(self, event, payload): """ Override this to perform additional actions upon start of a print job. """ - self.startTimelapse(payload["file"]) + self.start_timelapse(payload["file"]) - def onPrintDone(self, event, payload): + def on_print_done(self, event, payload): """ Override this to perform additional actions upon the stop of a print job. """ - self.stopTimelapse(success=(event==Events.PRINT_DONE)) + self.stop_timelapse(success=(event==Events.PRINT_DONE)) - def onPrintResumed(self, event, payload): + def on_print_resumed(self, event, payload): """ Override this to perform additional actions upon the pausing of a print job. """ - if not self._inTimelapse: - self.startTimelapse(payload["file"]) + if not self._in_timelapse: + self.start_timelapse(payload["file"]) - def eventSubscriptions(self): + def event_subscriptions(self): """ Override this method to subscribe to additional events by returning an array of (event, callback) tuples. @@ -178,7 +189,7 @@ class Timelapse(object): """ return [] - def configData(self): + def config_data(self): """ Override this method to return the current timelapse configuration data. The data should have the following form: @@ -188,94 +199,139 @@ class Timelapse(object): """ return None - def startTimelapse(self, gcodeFile): + def start_timelapse(self, gcodeFile): self._logger.debug("Starting timelapse for %s" % gcodeFile) - self.cleanCaptureDir() + self.clean_capture_dir() - self._imageNumber = 0 - self._inTimelapse = True - self._gcodeFile = os.path.basename(gcodeFile) + self._image_number = 0 + self._in_timelapse = True + self._gcode_file = os.path.basename(gcodeFile) - def stopTimelapse(self, doCreateMovie=True, success=True): + def stop_timelapse(self, doCreateMovie=True, success=True): self._logger.debug("Stopping timelapse") - self._inTimelapse = False + self._in_timelapse = False def resetImageNumber(): - self._imageNumber = None + self._image_number = None def createMovie(): - self._renderThread = threading.Thread(target=self._createMovie, kwargs={"success": success}) - self._renderThread.daemon = True - self._renderThread.start() + self._render_thread = threading.Thread(target=self._create_movie, kwargs={"success": success}) + self._render_thread.daemon = True + self._render_thread.start() def resetAndCreate(): resetImageNumber() createMovie() - if self._postRoll > 0: - self._postRollStart = time.time() + def waitForCaptures(callback): + self._capture_queue.put(dict(type=self.__class__.QUEUE_ENTRY_TYPE_CALLBACK, callback=callback)) + + def getWaitForCaptures(callback): + def f(): + waitForCaptures(callback) + return f + + if self._post_roll > 0: + eventManager().fire(Events.POSTROLL_START, dict(postroll_duration=self.post_roll * self.fps, postroll_length=self.post_roll, postroll_fps=self.fps)) + self._post_roll_start = time.time() if doCreateMovie: - self._onPostRollDone = resetAndCreate + self._on_post_roll_done = getWaitForCaptures(resetAndCreate) else: - self._onPostRollDone = resetImageNumber - self.processPostRoll() + self._on_post_roll_done = resetImageNumber + self.process_post_roll() else: - self._postRollStart = None + self._post_roll_start = None if doCreateMovie: - resetAndCreate() + waitForCaptures(resetAndCreate) else: resetImageNumber() - def processPostRoll(self): - pass + def process_post_roll(self): + self.post_roll_finished() + + def post_roll_finished(self): + if self.post_roll: + eventManager().fire(Events.POSTROLL_END) + if self._on_post_roll_done is not None: + self._on_post_roll_done() def captureImage(self): - if self._captureDir is None: + if self._capture_dir is None: self._logger.warn("Cannot capture image, capture directory is unset") return - if self._imageNumber is None: - self._logger.warn("Cannot capture image, image number is unset") - return + with self._capture_mutex: + if self._image_number is None: + self._logger.warn("Cannot capture image, image number is unset") + return + + filename = os.path.join(self._capture_dir, "tmp_%05d.jpg" % self._image_number) + self._image_number += 1 - with self._captureMutex: - filename = os.path.join(self._captureDir, "tmp_%05d.jpg" % self._imageNumber) - self._imageNumber += 1 self._logger.debug("Capturing image to %s" % filename) - captureThread = threading.Thread(target=self._captureWorker, kwargs={"filename": filename}) - captureThread.daemon = True - captureThread.start() + entry = dict(type=self.__class__.QUEUE_ENTRY_TYPE_CAPTURE, + filename=filename, + onerror=self._on_capture_error) + self._capture_queue.put(entry) return filename - def _captureWorker(self, filename): + def _on_capture_error(self): + with self._capture_mutex: + if self._image_number is not None and self._image_number > 0: + self._image_number -= 1 + + def _capture_queue_worker(self): + while self._capture_queue_active: + entry = self._capture_queue.get(block=True) + + if entry["type"] == self.__class__.QUEUE_ENTRY_TYPE_CAPTURE and "filename" in entry: + filename = entry["filename"] + onerror = entry.pop("onerror", None) + self._perform_capture(filename, onerror=onerror) + + elif entry["type"] == self.__class__.QUEUE_ENTRY_TYPE_CALLBACK and "callback" in entry: + args = entry.pop("args", []) + kwargs = entry.pop("kwargs", dict()) + entry["callback"](*args, **kwargs) + + def _perform_capture(self, filename, onerror=None): eventManager().fire(Events.CAPTURE_START, {"file": filename}) try: - urllib.urlretrieve(self._snapshotUrl, filename) - self._logger.debug("Image %s captured from %s" % (filename, self._snapshotUrl)) + self._logger.debug("Going to capture %s from %s" % (filename, self._snapshot_url)) + r = requests.get(self._snapshot_url, stream=True) + with open (filename, "wb") as f: + for chunk in r.iter_content(chunk_size=1024): + if chunk: + f.write(chunk) + f.flush() + self._logger.debug("Image %s captured from %s" % (filename, self._snapshot_url)) except: - self._logger.exception("Could not capture image %s from %s, decreasing image counter again" % (filename, self._snapshotUrl)) - with self._captureMutex: - if self._imageNumber is not None and self._imageNumber > 0: - self._imageNumber -= 1 - eventManager().fire(Events.CAPTURE_DONE, {"file": filename}) + self._logger.exception("Could not capture image %s from %s" % (filename, self._snapshot_url)) + if callable(onerror): + onerror() + eventManager().fire(Events.CAPTURE_FAILED, {"file": filename}) + return False + else: + eventManager().fire(Events.CAPTURE_DONE, {"file": filename}) + return True - def _createMovie(self, success=True): + def _create_movie(self, success=True): ffmpeg = settings().get(["webcam", "ffmpeg"]) bitrate = settings().get(["webcam", "bitrate"]) if ffmpeg is None or bitrate is None: self._logger.warn("Cannot create movie, path to ffmpeg or desired bitrate is unset") return - input = os.path.join(self._captureDir, "tmp_%05d.jpg") + input = os.path.join(self._capture_dir, "tmp_%05d.jpg") if success: - output = os.path.join(self._movieDir, "%s_%s.mpg" % (os.path.splitext(self._gcodeFile)[0], time.strftime("%Y%m%d%H%M%S"))) + output = os.path.join(self._movie_dir, "%s_%s.mpg" % (os.path.splitext(self._gcode_file)[0], time.strftime("%Y%m%d%H%M%S"))) else: - output = os.path.join(self._movieDir, "%s_%s-failed.mpg" % (os.path.splitext(self._gcodeFile)[0], time.strftime("%Y%m%d%H%M%S"))) + output = os.path.join(self._movie_dir, "%s_%s-failed.mpg" % (os.path.splitext(self._gcode_file)[0], time.strftime("%Y%m%d%H%M%S"))) # prepare ffmpeg command command = [ - ffmpeg, '-framerate', str(self._fps), '-loglevel', 'error', '-i', input, '-vcodec', 'mpeg2video', '-threads', str(self._ffmpegThreads), '-pix_fmt', 'yuv420p', '-r', str(self._fps), '-y', '-b', bitrate, + ffmpeg, '-framerate', str(self._fps), '-loglevel', 'error', '-i', input, '-vcodec', 'mpeg2video', '-threads', str(self._ffmpeg_threads), '-pix_fmt', 'yuv420p', '-r', str(self._fps), '-y', '-b', bitrate, '-f', 'vob'] filters = [] @@ -315,7 +371,7 @@ class Timelapse(object): # finalize command with output file self._logger.debug("Rendering movie to %s" % output) command.append("\"" + output + "\"") - eventManager().fire(Events.MOVIE_RENDERING, {"gcode": self._gcodeFile, "movie": output, "movie_basename": os.path.basename(output)}) + eventManager().fire(Events.MOVIE_RENDERING, {"gcode": self._gcode_file, "movie": output, "movie_basename": os.path.basename(output)}) command_str = " ".join(command) self._logger.debug("Executing command: %s" % command_str) @@ -323,75 +379,74 @@ class Timelapse(object): try: p = sarge.run(command_str, stderr=sarge.Capture()) if p.returncode == 0: - eventManager().fire(Events.MOVIE_DONE, {"gcode": self._gcodeFile, "movie": output, "movie_basename": os.path.basename(output)}) + eventManager().fire(Events.MOVIE_DONE, {"gcode": self._gcode_file, "movie": output, "movie_basename": os.path.basename(output)}) else: returncode = p.returncode stderr_text = p.stderr.text self._logger.warn("Could not render movie, got return code %r: %s" % (returncode, stderr_text)) - eventManager().fire(Events.MOVIE_FAILED, {"gcode": self._gcodeFile, "movie": output, "movie_basename": os.path.basename(output), "returncode": returncode, "error": stderr_text}) + eventManager().fire(Events.MOVIE_FAILED, {"gcode": self._gcode_file, "movie": output, "movie_basename": os.path.basename(output), "returncode": returncode, "error": stderr_text}) except: self._logger.exception("Could not render movie due to unknown error") - eventManager().fire(Events.MOVIE_FAILED, {"gcode": self._gcodeFile, "movie": output, "movie_basename": os.path.basename(output), "returncode": 255, "error": "Unknown error"}) + eventManager().fire(Events.MOVIE_FAILED, {"gcode": self._gcode_file, "movie": output, "movie_basename": os.path.basename(output), "returncode": 255, "error": "Unknown error"}) - def cleanCaptureDir(self): - if not os.path.isdir(self._captureDir): + def clean_capture_dir(self): + if not os.path.isdir(self._capture_dir): self._logger.warn("Cannot clean capture directory, it is unset") return - for filename in os.listdir(self._captureDir): + for filename in os.listdir(self._capture_dir): if not fnmatch.fnmatch(filename, "*.jpg"): continue - os.remove(os.path.join(self._captureDir, filename)) + os.remove(os.path.join(self._capture_dir, filename)) class ZTimelapse(Timelapse): - def __init__(self, postRoll=0, fps=25): - Timelapse.__init__(self, postRoll=postRoll, fps=fps) + def __init__(self, post_roll=0, fps=25): + Timelapse.__init__(self, post_roll=post_roll, fps=fps) self._logger.debug("ZTimelapse initialized") - def eventSubscriptions(self): + def event_subscriptions(self): return [ - (Events.Z_CHANGE, self._onZChange) + (Events.Z_CHANGE, self._on_z_change) ] - def configData(self): + def config_data(self): return { "type": "zchange" } - def processPostRoll(self): - Timelapse.processPostRoll(self) + def process_post_roll(self): + with self._capture_mutex: + filename = os.path.join(self._capture_dir, "tmp_%05d.jpg" % self._image_number) + self._image_number += 1 - filename = os.path.join(self._captureDir, "tmp_%05d.jpg" % self._imageNumber) - self._imageNumber += 1 - with self._captureMutex: - self._captureWorker(filename) + if self._perform_capture(filename): + for _ in range(self._post_roll * self._fps): + newFile = os.path.join(self._capture_dir, "tmp_%05d.jpg" % self._image_number) + self._image_number += 1 + shutil.copyfile(filename, newFile) - for i in range(self._postRoll * self._fps): - newFile = os.path.join(self._captureDir, "tmp_%05d.jpg" % (self._imageNumber)) - self._imageNumber += 1 - shutil.copyfile(filename, newFile) + Timelapse.process_post_roll(self) - if self._onPostRollDone is not None: - self._onPostRollDone() - - def _onZChange(self, event, payload): + def _on_z_change(self, event, payload): self.captureImage() class TimedTimelapse(Timelapse): - def __init__(self, postRoll=0, interval=1, fps=25): - Timelapse.__init__(self, postRoll=postRoll, fps=fps) + def __init__(self, post_roll=0, interval=1, fps=25): + Timelapse.__init__(self, post_roll=post_roll, fps=fps) self._interval = interval if self._interval < 1: self._interval = 1 # force minimum interval of 1s - self._timerThread = None + self._postroll_captures = 0 + self._timer = None self._logger.debug("TimedTimelapse initialized") + @property def interval(self): return self._interval - def configData(self): + def config_data(self): return { "type": "timed", "options": { @@ -399,25 +454,36 @@ class TimedTimelapse(Timelapse): } } - def onPrintStarted(self, event, payload): - Timelapse.onPrintStarted(self, event, payload) - if self._timerThread is not None: + def on_print_started(self, event, payload): + Timelapse.on_print_started(self, event, payload) + if self._timer is not None: return - self._timerThread = threading.Thread(target=self._timerWorker) - self._timerThread.daemon = True - self._timerThread.start() - - def onPrintDone(self, event, payload): - Timelapse.onPrintDone(self, event, payload) - self._timerThread = None - - def _timerWorker(self): self._logger.debug("Starting timer for interval based timelapse") - while self._inTimelapse or (self._postRollStart and time.time() - self._postRollStart <= self._postRoll * self._fps): - self.captureImage() - time.sleep(self._interval) + from octoprint.util import RepeatedTimer + self._timer = RepeatedTimer(self._interval, self._timer_task, + run_first=True, condition=self._timer_active, + on_finish=self._on_timer_finished) + self._timer.start() - if self._postRollStart is not None and self._onPostRollDone is not None: - self._onPostRollDone() - self._postRollStart = None + def on_print_done(self, event, payload): + self._postroll_captures = self.post_roll * self.fps + Timelapse.on_print_done(self, event, payload) + + def process_post_roll(self): + pass + + def post_roll_finished(self): + Timelapse.post_roll_finished(self) + self._timer = None + + def _timer_active(self): + return self._in_timelapse or self._postroll_captures > 0 + + def _timer_task(self): + self.captureImage() + if self._postroll_captures > 0: + self._postroll_captures -= 1 + + def _on_timer_finished(self): + self.post_roll_finished() From abf073340f563cf8f25215e035cd1574dd2c237f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 2 Oct 2015 15:14:05 +0200 Subject: [PATCH 04/20] Fixed reporting of duration needed for capturing timelapse postroll Needs to be calculated differently for time based and z-triggered. Capture interval was not taken properly into account. (cherry picked from commit 9284ff4) --- src/octoprint/static/js/app/dataupdater.js | 18 +++++++++++++----- src/octoprint/timelapse.py | 8 +++++++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/octoprint/static/js/app/dataupdater.js b/src/octoprint/static/js/app/dataupdater.js index 6a63cd28..26bd5f7e 100644 --- a/src/octoprint/static/js/app/dataupdater.js +++ b/src/octoprint/static/js/app/dataupdater.js @@ -195,15 +195,23 @@ function DataUpdater(allViewModels) { hide: false }); } else if (type == "PostRollStart") { - if (payload.postroll_duration > 60) { - format = {duration: _.sprintf(gettext("%(minutes)d min"), {minutes: payload.postroll_duration / 60})}; + var title = gettext("Capturing timelapse postroll"); + + var text; + if (!payload.postroll_duration) { + text = _.sprintf(gettext("Now capturing timelapse post roll, this will take only a moment..."), format); } else { - format = {duration: _.sprintf(gettext("%(seconds)d sec"), {seconds: payload.postroll_duration})}; + if (payload.postroll_duration > 60) { + format = {duration: _.sprintf(gettext("%(minutes)d min"), {minutes: payload.postroll_duration / 60})}; + } else { + format = {duration: _.sprintf(gettext("%(seconds)d sec"), {seconds: payload.postroll_duration})}; + } + text = _.sprintf(gettext("Now capturing timelapse post roll, this will take approximately %(duration)s..."), format); } new PNotify({ - title: gettext("Capturing timelapse postroll"), - text: _.sprintf(gettext("Now capturing timelapse post roll, this will take approximately %(duration)s..."), format) + title: title, + text: text }); } else if (type == "SlicingStarted") { gcodeUploadProgress.addClass("progress-striped").addClass("active"); diff --git a/src/octoprint/timelapse.py b/src/octoprint/timelapse.py index f4862b73..9ce2e3fc 100644 --- a/src/octoprint/timelapse.py +++ b/src/octoprint/timelapse.py @@ -233,7 +233,7 @@ class Timelapse(object): return f if self._post_roll > 0: - eventManager().fire(Events.POSTROLL_START, dict(postroll_duration=self.post_roll * self.fps, postroll_length=self.post_roll, postroll_fps=self.fps)) + eventManager().fire(Events.POSTROLL_START, dict(postroll_duration=self.calculate_post_roll(), postroll_length=self.post_roll, postroll_fps=self.fps)) self._post_roll_start = time.time() if doCreateMovie: self._on_post_roll_done = getWaitForCaptures(resetAndCreate) @@ -247,6 +247,9 @@ class Timelapse(object): else: resetImageNumber() + def calculate_post_roll(self): + return None + def process_post_roll(self): self.post_roll_finished() @@ -470,6 +473,9 @@ class TimedTimelapse(Timelapse): self._postroll_captures = self.post_roll * self.fps Timelapse.on_print_done(self, event, payload) + def calculate_post_roll(self): + return self.post_roll * self.fps * self.interval + def process_post_roll(self): pass From f83d5aa89f23d64927ced85756f44d52bb0d16a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Mon, 5 Oct 2015 18:07:43 +0200 Subject: [PATCH 05/20] Fix: Use atomic writes for all save processes That includes uploaded files, profiles, caching files, settings and user directories. --- src/octoprint/filemanager/util.py | 4 +++- src/octoprint/plugins/cura/__init__.py | 2 +- src/octoprint/plugins/pluginmanager/__init__.py | 2 +- src/octoprint/settings.py | 4 +++- src/octoprint/users.py | 4 +++- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/octoprint/filemanager/util.py b/src/octoprint/filemanager/util.py index 105987f3..f3fe20fb 100644 --- a/src/octoprint/filemanager/util.py +++ b/src/octoprint/filemanager/util.py @@ -7,6 +7,8 @@ __copyright__ = "Copyright (C) 2015 The OctoPrint Project - Released under terms import io +from octoprint.util import atomic_write + class AbstractFileWrapper(object): """ Wrapper for file representations to save to storages. @@ -85,7 +87,7 @@ class StreamWrapper(AbstractFileWrapper): """ import shutil - with open(path, "wb") as dest: + with atomic_write(path, "wb") as dest: with self.stream() as source: shutil.copyfileobj(source, dest) diff --git a/src/octoprint/plugins/cura/__init__.py b/src/octoprint/plugins/cura/__init__.py index 30de8874..fd326a10 100644 --- a/src/octoprint/plugins/cura/__init__.py +++ b/src/octoprint/plugins/cura/__init__.py @@ -393,7 +393,7 @@ class CuraPlugin(octoprint.plugin.SlicerPlugin, def _save_profile(self, path, profile, allow_overwrite=True): import yaml - with open(path, "wb") as f: + with octoprint.util.atomic_write(path, "wb") as f: yaml.safe_dump(profile, f, default_flow_style=False, indent=" ", allow_unicode=True) def _convert_to_engine(self, profile_path, printer_profile, posX, posY): diff --git a/src/octoprint/plugins/pluginmanager/__init__.py b/src/octoprint/plugins/pluginmanager/__init__.py index a93b6cc2..0c40562a 100644 --- a/src/octoprint/plugins/pluginmanager/__init__.py +++ b/src/octoprint/plugins/pluginmanager/__init__.py @@ -536,7 +536,7 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin, try: import json - with open(self._repository_cache_path, "w+b") as f: + with octoprint.util.atomic_write(self._repository_cache_path, "wb") as f: json.dump(repo_data, f) except Exception as e: self._logger.exception("Error while saving repository data to {}: {}".format(self._repository_cache_path, str(e))) diff --git a/src/octoprint/settings.py b/src/octoprint/settings.py index fa3f0e5f..733d24b8 100644 --- a/src/octoprint/settings.py +++ b/src/octoprint/settings.py @@ -30,6 +30,8 @@ import logging import re import uuid +from octoprint.util import atomic_write + _APPNAME = "OctoPrint" _instance = None @@ -1057,7 +1059,7 @@ class Settings(object): path, _ = os.path.split(filename) if not os.path.exists(path): os.makedirs(path) - with open(filename, "w+") as f: + with atomic_write(filename, "wb") as f: f.write(script) def _default_basedir(applicationName): diff --git a/src/octoprint/users.py b/src/octoprint/users.py index 76bf23bd..d4890a64 100644 --- a/src/octoprint/users.py +++ b/src/octoprint/users.py @@ -17,6 +17,8 @@ import logging from octoprint.settings import settings +from octoprint.util import atomic_write + class UserManager(object): valid_roles = ["user", "admin"] @@ -217,7 +219,7 @@ class FilebasedUserManager(UserManager): "settings": user._settings } - with open(self._userfile, "wb") as f: + with atomic_write(self._userfile, "wb") as f: yaml.safe_dump(data, f, default_flow_style=False, indent=" ", allow_unicode=True) self._dirty = False self._load() From 45c92cb1f4243febc9b5996e71ed2cde7de00825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Mon, 5 Oct 2015 18:04:05 +0200 Subject: [PATCH 06/20] Fix: Open GCODE files als utf-8, replacing encoding errors Also detect files that contain a BOM and strip it. Internal handling of GCODE file contents switched to unicode. Should take care of #1077 --- src/octoprint/util/__init__.py | 23 +++++++++++++++++++++++ src/octoprint/util/comm.py | 13 +++++++------ src/octoprint/util/gcodeInterpreter.py | 4 +++- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/octoprint/util/__init__.py b/src/octoprint/util/__init__.py index 07f80b81..58c750a3 100644 --- a/src/octoprint/util/__init__.py +++ b/src/octoprint/util/__init__.py @@ -505,6 +505,29 @@ def atomic_write(filename, mode="w+b", prefix="tmp", suffix=""): shutil.move(temp_config.name, filename) +def bom_aware_open(filename, encoding="ascii", mode="r", **kwargs): + import codecs + + codec = codecs.lookup(encoding) + encoding = codec.name + + if kwargs is None: + kwargs = dict() + + potential_bom_attribute = "BOM_" + codec.name.replace("utf-", "utf").upper() + if "r" in mode and hasattr(codecs, potential_bom_attribute): + # these encodings might have a BOM, so let's see if there is one + bom = getattr(codecs, potential_bom_attribute) + + with open(filename, "rb") as f: + header = f.read(4) + + if header.startswith(bom): + encoding += "-sig" + + return codecs.open(filename, encoding=encoding, **kwargs) + + class RepeatedTimer(threading.Thread): """ This class represents an action that should be run repeatedly in an interval. It is similar to python's diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index 49c8bc6c..9bc2c702 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -24,7 +24,7 @@ from octoprint.settings import settings, default_settings from octoprint.events import eventManager, Events from octoprint.filemanager import valid_file_type from octoprint.filemanager.destinations import FileDestinations -from octoprint.util import get_exception_string, sanitize_ascii, filter_non_ascii, CountedEvent, RepeatedTimer +from octoprint.util import get_exception_string, sanitize_ascii, filter_non_ascii, CountedEvent, RepeatedTimer, to_unicode, bom_aware_open try: import _winreg @@ -491,7 +491,7 @@ class MachineCom(object): self._clear_to_send.set() def sendCommand(self, cmd, cmd_type=None, processed=False): - cmd = cmd.encode('ascii', 'replace') + cmd = to_unicode(cmd, errors="replace") if not processed: cmd = process_gcode_line(cmd) if not cmd: @@ -1549,10 +1549,11 @@ class MachineCom(object): continue # now comes the part where we increase line numbers and send stuff - no turning back now + command_to_send = command.encode("ascii", errors="replace") if (gcode is not None or self._sendChecksumWithUnknownCommands) and (self.isPrinting() or self._alwaysSendChecksum): - self._doIncrementAndSendWithChecksum(command) + self._doIncrementAndSendWithChecksum(command_to_send) else: - self._doSendWithoutChecksum(command) + self._doSendWithoutChecksum(command_to_send) # trigger "sent" phase and use up one "ok" self._process_command_phase("sent", command, command_type, gcode=gcode) @@ -1952,7 +1953,7 @@ class PrintingGcodeFileInformation(PrintingFileInformation): Opens the file for reading and determines the file size. """ PrintingFileInformation.start(self) - self._handle = open(self._filename, "r") + self._handle = bom_aware_open(self._filename, encoding="utf-8", errors="replace") def close(self): """ @@ -1982,7 +1983,7 @@ class PrintingGcodeFileInformation(PrintingFileInformation): if self._handle is None: # file got closed just now return None - line = self._handle.readline() + line = to_unicode(self._handle.readline()) if not line: self.close() processed = process_gcode_line(line, offsets=offsets, current_tool=current_tool) diff --git a/src/octoprint/util/gcodeInterpreter.py b/src/octoprint/util/gcodeInterpreter.py index b0b69b66..853cbfd6 100644 --- a/src/octoprint/util/gcodeInterpreter.py +++ b/src/octoprint/util/gcodeInterpreter.py @@ -35,7 +35,9 @@ class gcode(object): if os.path.isfile(filename): self.filename = filename self._fileSize = os.stat(filename).st_size - with open(filename, "r") as f: + + import codecs + with codecs.open(filename, encoding="utf-8", errors="replace") as f: self._load(f, printer_profile, throttle=throttle) def abort(self): From 548f976d35fd9831420bb884b9dee04ec28d7047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 6 Oct 2015 13:41:54 +0200 Subject: [PATCH 07/20] Some more defensive escaping for various settings in the UI Entering HTML fragments into the webcam stream URL could cause issues, anything injected via Jinja should now be escaped properly. --- src/octoprint/server/views.py | 9 +++++++++ src/octoprint/templates/dialogs/settings.jinja2 | 2 +- .../templates/dialogs/usersettings.jinja2 | 2 +- src/octoprint/templates/index.jinja2 | 6 +++--- src/octoprint/templates/initscript.jinja2 | 16 ++++++++-------- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/octoprint/server/views.py b/src/octoprint/server/views.py index 905e1436..f89bd9da 100644 --- a/src/octoprint/server/views.py +++ b/src/octoprint/server/views.py @@ -16,11 +16,16 @@ from octoprint.server import app, userManager, pluginManager, gettext, \ debug, LOCALES, VERSION, DISPLAY_VERSION, UI_API_KEY, BRANCH from octoprint.settings import settings +import re + from . import util import logging _logger = logging.getLogger(__name__) +_valid_id_re = re.compile("[a-z_]+") +_valid_div_re = re.compile("[a-zA-Z_-]+") + @app.route("/") @util.flask.cached(refreshif=lambda: util.flask.cache_check_headers() or "_refresh" in request.values, key=lambda: "view/%s/%s" % (request.path, g.locale), @@ -367,6 +372,9 @@ def _process_template_config(name, implementation, rule, config=None, counter=1) data["_div"] = rule["div"](name) if "suffix" in data: data["_div"] = data["_div"] + data["suffix"] + if not _valid_div_re.match(data["_div"]): + _logger.warn("Template config {} contains invalid div identifier {}, skipping it".format(name, data["_div"])) + return None if not "template" in data: data["template"] = rule["template"](name) @@ -378,6 +386,7 @@ def _process_template_config(name, implementation, rule, config=None, counter=1) data_bind = "allowBindings: true" if "data_bind" in data: data_bind = data_bind + ", " + data["data_bind"] + data_bind = data_bind.replace("\"", "\\\"") data["data_bind"] = data_bind data["_key"] = "plugin_" + name diff --git a/src/octoprint/templates/dialogs/settings.jinja2 b/src/octoprint/templates/dialogs/settings.jinja2 index f3d3f708..a87fa6a6 100644 --- a/src/octoprint/templates/dialogs/settings.jinja2 +++ b/src/octoprint/templates/dialogs/settings.jinja2 @@ -20,7 +20,7 @@ class="{% if mark_active %}active{% set mark_active = False %}{% endif %} {% if "classes_link" in data %}{{ data.classes_link|join(' ') }}{% elif "classes" in data %}{{ data.classes|join(' ') }}{% endif %}" {% if "styles_link" in data %} style="{{ data.styles_link|join(', ') }}" {% elif "styles" in data %} style="{{ data.styles|join(', ') }}" {% endif %} > - {{ entry }} + {{ entry|e }} {% if "custom_bindings" not in data or data["custom_bindings"] %}{% endif %} {% endif %} diff --git a/src/octoprint/templates/dialogs/usersettings.jinja2 b/src/octoprint/templates/dialogs/usersettings.jinja2 index 00cbcc6c..be405c15 100644 --- a/src/octoprint/templates/dialogs/usersettings.jinja2 +++ b/src/octoprint/templates/dialogs/usersettings.jinja2 @@ -17,7 +17,7 @@ class="{% if mark_active %}active{% set mark_active = False %}{% endif %} {% if "classes_link" in data %}{{ data.classes_link|join(' ') }}{% elif "classes" in data %}{{ data.classes|join(' ') }}{% endif %}" {% if "styles_link" in data %} style="{{ data.styles_link|join(', ') }}" {% elif "styles" in data %} style="{{ data.styles|join(', ') }}" {% endif %} > - {{ entry }} + {{ entry|e }} {% if "custom_bindings" not in data or data["custom_bindings"] %}{% endif %} {% endif %} diff --git a/src/octoprint/templates/index.jinja2 b/src/octoprint/templates/index.jinja2 index 1a58a82b..a5de49c1 100644 --- a/src/octoprint/templates/index.jinja2 +++ b/src/octoprint/templates/index.jinja2 @@ -55,7 +55,7 @@ >
- {% if "icon" in data %} {% endif %}{{ entry }} + {% if "icon" in data %} {% endif %}{{ entry|e }} {% if "template_header" in data %} {% include data.template_header ignore missing %} @@ -87,7 +87,7 @@ {% if "data_bind" in data %}data-bind="{{ data.data_bind }}"{% endif %} {% if "styles_link" in data %} style="{{ data.styles_link|join(', ') }}" {% elif "styles" in data %} style="{{ data.styles|join(', ') }}" {% endif %} > - {{ entry }} + {{ entry|e }} {% if "custom_bindings" not in data or data["custom_bindings"] %}{% endif %} {% endfor %} @@ -112,7 +112,7 @@
From 9cf21aa03696ae89ebdeb3139f7eebe2a8941110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Wed, 7 Oct 2015 12:25:25 +0200 Subject: [PATCH 10/20] Also set caching headers for downloads to discourage client-side caching At least if necessary (added new boolean flag to the constructor of LargeResponseHandler for that). While at it also rename UrlForwardHandler to UrlProxyHandler (it does not forward, it proxies) and make it aware of ETag and Expires headers. --- src/octoprint/server/__init__.py | 24 ++++++++++++++++++++---- src/octoprint/server/util/tornado.py | 15 +++++++++++---- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py index 646e573d..a6a6aa24 100644 --- a/src/octoprint/server/__init__.py +++ b/src/octoprint/server/__init__.py @@ -330,13 +330,29 @@ class Server(): upload_suffixes = dict(name=s.get(["server", "uploads", "nameSuffix"]), path=s.get(["server", "uploads", "pathSuffix"])) + download_handler_kwargs = dict( + as_attachment=True, + allow_client_caching=False + ) + admin_validator = dict(access_validation=util.tornado.access_validation_factory(app, loginManager, util.flask.user_validator)) + no_hidden_files_validator = dict(path_validation=util.tornado.path_validation_factory(lambda path: not os.path.basename(path).startswith("."), status_code=404)) + + def joined_dict(*dicts): + if not len(dicts): + return dict() + + joined = dict() + for d in dicts: + joined.update(d) + return joined + server_routes = self._router.urls + [ # various downloads - (r"/downloads/timelapse/([^/]*\.mpg)", util.tornado.LargeResponseHandler, dict(path=s.getBaseFolder("timelapse"), as_attachment=True)), - (r"/downloads/files/local/(.*)", util.tornado.LargeResponseHandler, dict(path=s.getBaseFolder("uploads"), as_attachment=True, path_validation=util.tornado.path_validation_factory(lambda path: not os.path.basename(path).startswith("."), status_code=404))), - (r"/downloads/logs/([^/]*)", util.tornado.LargeResponseHandler, dict(path=s.getBaseFolder("logs"), as_attachment=True, access_validation=util.tornado.access_validation_factory(app, loginManager, util.flask.admin_validator))), + (r"/downloads/timelapse/([^/]*\.mpg)", util.tornado.LargeResponseHandler, joined_dict(dict(path=s.getBaseFolder("timelapse")), download_handler_kwargs, no_hidden_files_validator)), + (r"/downloads/files/local/(.*)", util.tornado.LargeResponseHandler, joined_dict(dict(path=s.getBaseFolder("uploads")), download_handler_kwargs, no_hidden_files_validator)), + (r"/downloads/logs/([^/]*)", util.tornado.LargeResponseHandler, joined_dict(dict(path=s.getBaseFolder("logs")), download_handler_kwargs, admin_validator)), # camera snapshot - (r"/downloads/camera/current", util.tornado.UrlForwardHandler, dict(url=s.get(["webcam", "snapshot"]), as_attachment=True, access_validation=util.tornado.access_validation_factory(app, loginManager, util.flask.user_validator))), + (r"/downloads/camera/current", util.tornado.UrlProxyHandler, dict(url=s.get(["webcam", "snapshot"]), as_attachment=True, access_validation=util.tornado.access_validation_factory(app, loginManager, util.flask.user_validator))), # generated webassets (r"/static/webassets/(.*)", util.tornado.LargeResponseHandler, dict(path=os.path.join(s.getBaseFolder("generated"), "webassets"))) ] diff --git a/src/octoprint/server/util/tornado.py b/src/octoprint/server/util/tornado.py index 14ace0da..d69f3036 100644 --- a/src/octoprint/server/util/tornado.py +++ b/src/octoprint/server/util/tornado.py @@ -768,6 +768,8 @@ class LargeResponseHandler(tornado.web.StaticFileHandler): :class:``~tornado.web.StaticFileHandler`` as the ``default_filename`` keyword parameter). Defaults to ``None``. as_attachment (bool): Whether to serve requested files with ``Content-Disposition: attachment`` header (``True``) or not. Defaults to ``False``. + allow_client_caching (bool): Whether to allow the client to cache (by not setting any ``Cache-Control`` or + ``Expires`` headers on the response) or not. access_validation (function): Callback to call in the ``get`` method to validate access to the resource. Will be called with ``self.request`` as parameter which contains the full tornado request object. Should raise a ``tornado.web.HTTPError`` if access is not allowed in which case the request will not be further processed. @@ -782,10 +784,11 @@ class LargeResponseHandler(tornado.web.StaticFileHandler): by ``get_content_version``. """ - def initialize(self, path, default_filename=None, as_attachment=False, + def initialize(self, path, default_filename=None, as_attachment=False, allow_client_caching=True, access_validation=None, path_validation=None, etag_generator=None): tornado.web.StaticFileHandler.initialize(self, os.path.abspath(path), default_filename) self._as_attachment = as_attachment + self._allow_client_caching = allow_client_caching self._access_validation = access_validation self._path_validation = path_validation self._etag_generator = etag_generator @@ -802,6 +805,10 @@ class LargeResponseHandler(tornado.web.StaticFileHandler): if self._as_attachment: self.set_header("Content-Disposition", "attachment") + if not self._allow_client_caching: + self.set_header("Cache-Control", "max-age=0, must-revalidate, private") + self.set_header("Expires", "-1") + def compute_etag(self): if self._etag_generator is not None: return self._etag_generator(self) @@ -817,7 +824,7 @@ class LargeResponseHandler(tornado.web.StaticFileHandler): ##~~ URL Forward Handler for forwarding requests to a preconfigured static URL -class UrlForwardHandler(tornado.web.RequestHandler): +class UrlProxyHandler(tornado.web.RequestHandler): """ `tornado.web.RequestHandler `_ that proxies requests to a preconfigured url and returns the response. Allows delivery of the requested content as attachment @@ -827,7 +834,7 @@ class UrlForwardHandler(tornado.web.RequestHandler): for making the request to the configured endpoint and return the body of the client response with the status code from the client response and the following headers: - * ``Date``, ``Cache-Control``, ``Server``, ``Content-Type`` and ``Location`` will be copied over. + * ``Date``, ``Cache-Control``, ``Expires``, ``ETag``, ``Server``, ``Content-Type`` and ``Location`` will be copied over. * If ``as_attachment`` is set to True, ``Content-Disposition`` will be set to ``attachment``. If ``basename`` is set including the attachement's ``filename`` attribute will be set to the base name followed by the extension guessed based on the MIME type from the ``Content-Type`` header of the response. If no extension can be guessed @@ -877,7 +884,7 @@ class UrlForwardHandler(tornado.web.RequestHandler): filename = None self.set_status(response.code) - for name in ("Date", "Cache-Control", "Server", "Content-Type", "Location"): + for name in ("Date", "Cache-Control", "Server", "Content-Type", "Location", "Expires", "ETag"): value = response.headers.get(name) if value: self.set_header(name, value) From 859ea0d0e486082ed04cad398b21770756ea11e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Wed, 7 Oct 2015 17:19:18 +0200 Subject: [PATCH 11/20] Added mime type guessing for downloads to Tornado --- src/octoprint/filemanager/__init__.py | 47 ++++++++++++- src/octoprint/server/__init__.py | 7 +- src/octoprint/server/util/tornado.py | 12 +++- tests/filemanager/__init__.py | 96 +++++++++++++++++++++++++++ 4 files changed, 157 insertions(+), 5 deletions(-) diff --git a/src/octoprint/filemanager/__init__.py b/src/octoprint/filemanager/__init__.py index fad1fc41..34f31680 100644 --- a/src/octoprint/filemanager/__init__.py +++ b/src/octoprint/filemanager/__init__.py @@ -18,6 +18,11 @@ from .analysis import QueueEntry, AnalysisQueue from .storage import LocalFileStorage from .util import AbstractFileWrapper, StreamWrapper, DiskFileWrapper +from collections import namedtuple + +ContentTypeMapping = namedtuple("ContentTypeMapping", "extensions, content_type") +ContentTypeDetector = namedtuple("ContentTypeDetector", "extensions, detector") + extensions = dict( ) @@ -25,11 +30,11 @@ def full_extension_tree(): result = dict( # extensions for 3d model files model=dict( - stl=["stl"] + stl=ContentTypeMapping(["stl"], "application/sla") ), # extensions for printable machine code machinecode=dict( - gcode=["gcode", "gco", "g"] + gcode=ContentTypeMapping(["gcode", "gco", "g"], "text/plain") ) ) @@ -68,8 +73,12 @@ def get_all_extensions(subtree=None): for key, value in subtree.items(): if isinstance(value, dict): result += get_all_extensions(value) + elif isinstance(value, (ContentTypeMapping, ContentTypeDetector)): + result += value.extensions elif isinstance(value, (list, tuple)): result += value + elif isinstance(subtree, (ContentTypeMapping, ContentTypeDetector)): + result = subtree.extensions elif isinstance(subtree, (list, tuple)): result = subtree return result @@ -79,7 +88,9 @@ def get_path_for_extension(extension, subtree=None): subtree = full_extension_tree() for key, value in subtree.items(): - if isinstance(value, (list, tuple)) and extension in value: + if isinstance(value, (ContentTypeMapping, ContentTypeDetector)) and extension in value.extensions: + return [key] + elif isinstance(value, (list, tuple)) and extension in value: return [key] elif isinstance(value, dict): path = get_path_for_extension(extension, subtree=value) @@ -88,6 +99,23 @@ def get_path_for_extension(extension, subtree=None): return None +def get_content_type_mapping_for_extension(extension, subtree=None): + if not subtree: + subtree = full_extension_tree() + + for key, value in subtree.items(): + content_extension_matches = isinstance(value, (ContentTypeMapping, ContentTypeDetector)) and extension in value. extensions + list_extension_matches = isinstance(value, (list, tuple)) and extension in value + + if content_extension_matches or list_extension_matches: + return value + elif isinstance(value, dict): + result = get_content_type_mapping_for_extension(extension, subtree=value) + if result is not None: + return result + + return None + def valid_extension(extension, type=None): if not type: return extension in get_all_extensions() @@ -106,6 +134,19 @@ def get_file_type(filename): extension = extension[1:].lower() return get_path_for_extension(extension) +def get_mime_type(filename): + _, extension = os.path.splitext(filename) + extension = extension[1:].lower() + mapping = get_content_type_mapping_for_extension(extension) + if mapping: + if isinstance(mapping, ContentTypeMapping) and mapping.content_type is not None: + return mapping.content_type + elif isinstance(mapping, ContentTypeDetector) and callable(mapping.detector): + result = mapping.detector(filename) + if result is not None: + return result + return "application/octet-stream" + class NoSuchStorage(Exception): pass diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py index a6a6aa24..f76f9cb8 100644 --- a/src/octoprint/server/__init__.py +++ b/src/octoprint/server/__init__.py @@ -330,10 +330,15 @@ class Server(): upload_suffixes = dict(name=s.get(["server", "uploads", "nameSuffix"]), path=s.get(["server", "uploads", "pathSuffix"])) + def mime_type_guesser(path): + from octoprint.filemanager import get_mime_type + return get_mime_type(path) + download_handler_kwargs = dict( as_attachment=True, allow_client_caching=False ) + additional_mime_types=dict(mime_type_guesser=mime_type_guesser) admin_validator = dict(access_validation=util.tornado.access_validation_factory(app, loginManager, util.flask.user_validator)) no_hidden_files_validator = dict(path_validation=util.tornado.path_validation_factory(lambda path: not os.path.basename(path).startswith("."), status_code=404)) @@ -349,7 +354,7 @@ class Server(): server_routes = self._router.urls + [ # various downloads (r"/downloads/timelapse/([^/]*\.mpg)", util.tornado.LargeResponseHandler, joined_dict(dict(path=s.getBaseFolder("timelapse")), download_handler_kwargs, no_hidden_files_validator)), - (r"/downloads/files/local/(.*)", util.tornado.LargeResponseHandler, joined_dict(dict(path=s.getBaseFolder("uploads")), download_handler_kwargs, no_hidden_files_validator)), + (r"/downloads/files/local/(.*)", util.tornado.LargeResponseHandler, joined_dict(dict(path=s.getBaseFolder("uploads")), download_handler_kwargs, no_hidden_files_validator, additional_mime_types)), (r"/downloads/logs/([^/]*)", util.tornado.LargeResponseHandler, joined_dict(dict(path=s.getBaseFolder("logs")), download_handler_kwargs, admin_validator)), # camera snapshot (r"/downloads/camera/current", util.tornado.UrlProxyHandler, dict(url=s.get(["webcam", "snapshot"]), as_attachment=True, access_validation=util.tornado.access_validation_factory(app, loginManager, util.flask.user_validator))), diff --git a/src/octoprint/server/util/tornado.py b/src/octoprint/server/util/tornado.py index d69f3036..30753f34 100644 --- a/src/octoprint/server/util/tornado.py +++ b/src/octoprint/server/util/tornado.py @@ -785,13 +785,15 @@ class LargeResponseHandler(tornado.web.StaticFileHandler): """ def initialize(self, path, default_filename=None, as_attachment=False, allow_client_caching=True, - access_validation=None, path_validation=None, etag_generator=None): + access_validation=None, path_validation=None, etag_generator=None, + mime_type_guesser=None): tornado.web.StaticFileHandler.initialize(self, os.path.abspath(path), default_filename) self._as_attachment = as_attachment self._allow_client_caching = allow_client_caching self._access_validation = access_validation self._path_validation = path_validation self._etag_generator = etag_generator + self._mime_type_guesser = mime_type_guesser def get(self, path, include_body=True): if self._access_validation is not None: @@ -815,6 +817,14 @@ class LargeResponseHandler(tornado.web.StaticFileHandler): else: return self.get_content_version(self.absolute_path) + def get_content_type(self): + if self._mime_type_guesser is not None: + type = self._mime_type_guesser(self.absolute_path) + if type is not None: + return type + + return tornado.web.StaticFileHandler.get_content_type(self) + @classmethod def get_content_version(cls, abspath): import os diff --git a/tests/filemanager/__init__.py b/tests/filemanager/__init__.py index 65f33057..107fc063 100644 --- a/tests/filemanager/__init__.py +++ b/tests/filemanager/__init__.py @@ -9,3 +9,99 @@ __author__ = "Gina Häußge " __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' __copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms of the AGPLv3 License" +import unittest +import mock + +import octoprint.filemanager + +class FilemanagerUtilTest(unittest.TestCase): + + def setUp(self): + # mock plugin manager + self.plugin_manager_patcher = mock.patch("octoprint.plugin.plugin_manager") + self.plugin_manager_getter = self.plugin_manager_patcher.start() + + self.plugin_manager = mock.MagicMock() + + hook_extensions = dict( + some_plugin=lambda: dict(dict(machinecode=dict(foo=["foo", "f"]))), + other_plugin=lambda: dict(dict(model=dict(amf=["amf"]))), + mime_map=lambda: dict( + mime_map=dict( + mime_map_yes=octoprint.filemanager.ContentTypeMapping(["mime_map_yes"], "application/mime_map_yes") + ) + ), + mime_detect=lambda: dict( + dict( + machinecode=dict( + mime_detect_yes=octoprint.filemanager.ContentTypeDetector(["mime_detect_yes"], lambda x: "application/mime_detect_yes"), + mime_detect_no=octoprint.filemanager.ContentTypeDetector(["mime_detect_no"], lambda x: None) + ) + ) + ) + ) + self.plugin_manager.get_hooks.return_value = hook_extensions + + self.plugin_manager_getter.return_value = self.plugin_manager + + def tearDown(self): + self.plugin_manager_patcher.stop() + + def test_full_extension_tree(self): + full = octoprint.filemanager.full_extension_tree() + self.assertTrue("machinecode" in full) + self.assertTrue("gcode" in full["machinecode"]) + self.assertTrue(isinstance(full["machinecode"]["gcode"], octoprint.filemanager.ContentTypeMapping)) + self.assertItemsEqual(["gcode", "gco", "g"], full["machinecode"]["gcode"].extensions) + self.assertTrue("foo" in full["machinecode"]) + self.assertTrue(isinstance(full["machinecode"]["foo"], list)) + self.assertItemsEqual(["f", "foo"], full["machinecode"]["foo"]) + + self.assertTrue("model" in full) + self.assertTrue("stl" in full["model"]) + self.assertTrue(isinstance(full["model"]["stl"], octoprint.filemanager.ContentTypeMapping)) + self.assertItemsEqual(["stl"], full["model"]["stl"].extensions) + self.assertTrue("amf" in full["model"]) + self.assertTrue(isinstance(full["model"]["amf"], list)) + self.assertItemsEqual(["amf"], full["model"]["amf"]) + + def test_get_mimetype(self): + self.assertEquals(octoprint.filemanager.get_mime_type("foo.stl"), "application/sla") + self.assertEquals(octoprint.filemanager.get_mime_type("foo.gcode"), "text/plain") + self.assertEquals(octoprint.filemanager.get_mime_type("foo.unknown"), "application/octet-stream") + self.assertEquals(octoprint.filemanager.get_mime_type("foo.mime_map_yes"), "application/mime_map_yes") + self.assertEquals(octoprint.filemanager.get_mime_type("foo.mime_map_no"), "application/octet-stream") + self.assertEquals(octoprint.filemanager.get_mime_type("foo.mime_detect_yes"), "application/mime_detect_yes") + self.assertEquals(octoprint.filemanager.get_mime_type("foo.mime_detect_no"), "application/octet-stream") + + def test_valid_file_type(self): + self.assertTrue(octoprint.filemanager.valid_file_type("foo.stl", type="model")) + self.assertTrue(octoprint.filemanager.valid_file_type("foo.stl", type="stl")) + self.assertFalse(octoprint.filemanager.valid_file_type("foo.stl", type="machinecode")) + self.assertTrue(octoprint.filemanager.valid_file_type("foo.foo", type="machinecode")) + self.assertTrue(octoprint.filemanager.valid_file_type("foo.foo", type="foo")) + self.assertTrue(octoprint.filemanager.valid_file_type("foo.foo")) + self.assertTrue(octoprint.filemanager.valid_file_type("foo.mime_map_yes")) + self.assertTrue(octoprint.filemanager.valid_file_type("foo.mime_detect_yes")) + self.assertFalse(octoprint.filemanager.valid_file_type("foo.unknown")) + + def test_get_file_type(self): + self.assertEquals(["machinecode", "gcode"], octoprint.filemanager.get_file_type("foo.gcode")) + self.assertEquals(["machinecode", "gcode"], octoprint.filemanager.get_file_type("foo.gco")) + self.assertEquals(["machinecode", "foo"], octoprint.filemanager.get_file_type("foo.f")) + self.assertEquals(["model", "stl"], octoprint.filemanager.get_file_type("foo.stl")) + self.assertEquals(["model", "amf"], octoprint.filemanager.get_file_type("foo.amf")) + self.assertIsNone(octoprint.filemanager.get_file_type("foo.unknown")) + + def test_hook_failure(self): + def hook(): + raise RuntimeError("Boo!") + self.plugin_manager.get_hooks.return_value = dict(hook=hook) + + with mock.patch("octoprint.filemanager.logging") as patched_logging: + logger = mock.MagicMock() + patched_logging.getLogger.return_value = logger + + octoprint.filemanager.get_all_extensions() + + self.assertEquals(1, len(logger.mock_calls)) From 85e6ae8e8278721affae4524c9827d63de8d8986 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Wed, 7 Oct 2015 17:45:07 +0200 Subject: [PATCH 12/20] Fixed a unit test that broke by switching to atomic_write --- tests/filemanager/test_filemanager.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/filemanager/test_filemanager.py b/tests/filemanager/test_filemanager.py index 110df373..1da5f39f 100644 --- a/tests/filemanager/test_filemanager.py +++ b/tests/filemanager/test_filemanager.py @@ -109,13 +109,13 @@ class FileManagerTest(unittest.TestCase): self.assertEquals(metadata, expected) self.local_storage.get_metadata.assert_called_once_with("test.file") - @mock.patch("__builtin__.open", new_callable=mock.mock_open) + @mock.patch("octoprint.filemanager.util.atomic_write") @mock.patch("io.FileIO") @mock.patch("shutil.copyfileobj") @mock.patch("os.remove") @mock.patch("tempfile.NamedTemporaryFile") @mock.patch("time.time", side_effect=[1411979916.422, 1411979932.116]) - def test_slice(self, mocked_time, mocked_tempfile, mocked_os, mocked_shutil, mocked_fileio, mocked_open): + def test_slice(self, mocked_time, mocked_tempfile, mocked_os, mocked_shutil, mocked_fileio, mocked_atomic_write): callback = mock.MagicMock() callback_args = ("one", "two", "three") @@ -187,8 +187,8 @@ class FileManagerTest(unittest.TestCase): self.local_storage.add_file.assert_called_once_with("dest.file", mock.ANY, printer_profile=expected_printer_profile, allow_overwrite=True, links=expected_links) # assert that the generated gcode was manipulated as required - expected_open_calls = [mock.call("prefix/dest.file", "wb")] - self.assertEquals(mocked_open.call_args_list, expected_open_calls) + expected_atomic_write_calls = [mock.call("prefix/dest.file", "wb")] + self.assertEquals(mocked_atomic_write.call_args_list, expected_atomic_write_calls) #mocked_open.return_value.write.assert_called_once_with(";Generated from source.file aabbccddeeff\r") # assert that shutil was asked to copy the concatenated multistream From dcf2e51b21c53d52cf1539881c2523e90d4be4e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Wed, 7 Oct 2015 17:50:08 +0200 Subject: [PATCH 13/20] Moved filemanager method tests to test_filemanager.py --- tests/filemanager/__init__.py | 97 --------------------------- tests/filemanager/test_filemanager.py | 92 +++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 97 deletions(-) diff --git a/tests/filemanager/__init__.py b/tests/filemanager/__init__.py index 107fc063..92a8db72 100644 --- a/tests/filemanager/__init__.py +++ b/tests/filemanager/__init__.py @@ -8,100 +8,3 @@ from __future__ import absolute_import __author__ = "Gina Häußge " __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' __copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms of the AGPLv3 License" - -import unittest -import mock - -import octoprint.filemanager - -class FilemanagerUtilTest(unittest.TestCase): - - def setUp(self): - # mock plugin manager - self.plugin_manager_patcher = mock.patch("octoprint.plugin.plugin_manager") - self.plugin_manager_getter = self.plugin_manager_patcher.start() - - self.plugin_manager = mock.MagicMock() - - hook_extensions = dict( - some_plugin=lambda: dict(dict(machinecode=dict(foo=["foo", "f"]))), - other_plugin=lambda: dict(dict(model=dict(amf=["amf"]))), - mime_map=lambda: dict( - mime_map=dict( - mime_map_yes=octoprint.filemanager.ContentTypeMapping(["mime_map_yes"], "application/mime_map_yes") - ) - ), - mime_detect=lambda: dict( - dict( - machinecode=dict( - mime_detect_yes=octoprint.filemanager.ContentTypeDetector(["mime_detect_yes"], lambda x: "application/mime_detect_yes"), - mime_detect_no=octoprint.filemanager.ContentTypeDetector(["mime_detect_no"], lambda x: None) - ) - ) - ) - ) - self.plugin_manager.get_hooks.return_value = hook_extensions - - self.plugin_manager_getter.return_value = self.plugin_manager - - def tearDown(self): - self.plugin_manager_patcher.stop() - - def test_full_extension_tree(self): - full = octoprint.filemanager.full_extension_tree() - self.assertTrue("machinecode" in full) - self.assertTrue("gcode" in full["machinecode"]) - self.assertTrue(isinstance(full["machinecode"]["gcode"], octoprint.filemanager.ContentTypeMapping)) - self.assertItemsEqual(["gcode", "gco", "g"], full["machinecode"]["gcode"].extensions) - self.assertTrue("foo" in full["machinecode"]) - self.assertTrue(isinstance(full["machinecode"]["foo"], list)) - self.assertItemsEqual(["f", "foo"], full["machinecode"]["foo"]) - - self.assertTrue("model" in full) - self.assertTrue("stl" in full["model"]) - self.assertTrue(isinstance(full["model"]["stl"], octoprint.filemanager.ContentTypeMapping)) - self.assertItemsEqual(["stl"], full["model"]["stl"].extensions) - self.assertTrue("amf" in full["model"]) - self.assertTrue(isinstance(full["model"]["amf"], list)) - self.assertItemsEqual(["amf"], full["model"]["amf"]) - - def test_get_mimetype(self): - self.assertEquals(octoprint.filemanager.get_mime_type("foo.stl"), "application/sla") - self.assertEquals(octoprint.filemanager.get_mime_type("foo.gcode"), "text/plain") - self.assertEquals(octoprint.filemanager.get_mime_type("foo.unknown"), "application/octet-stream") - self.assertEquals(octoprint.filemanager.get_mime_type("foo.mime_map_yes"), "application/mime_map_yes") - self.assertEquals(octoprint.filemanager.get_mime_type("foo.mime_map_no"), "application/octet-stream") - self.assertEquals(octoprint.filemanager.get_mime_type("foo.mime_detect_yes"), "application/mime_detect_yes") - self.assertEquals(octoprint.filemanager.get_mime_type("foo.mime_detect_no"), "application/octet-stream") - - def test_valid_file_type(self): - self.assertTrue(octoprint.filemanager.valid_file_type("foo.stl", type="model")) - self.assertTrue(octoprint.filemanager.valid_file_type("foo.stl", type="stl")) - self.assertFalse(octoprint.filemanager.valid_file_type("foo.stl", type="machinecode")) - self.assertTrue(octoprint.filemanager.valid_file_type("foo.foo", type="machinecode")) - self.assertTrue(octoprint.filemanager.valid_file_type("foo.foo", type="foo")) - self.assertTrue(octoprint.filemanager.valid_file_type("foo.foo")) - self.assertTrue(octoprint.filemanager.valid_file_type("foo.mime_map_yes")) - self.assertTrue(octoprint.filemanager.valid_file_type("foo.mime_detect_yes")) - self.assertFalse(octoprint.filemanager.valid_file_type("foo.unknown")) - - def test_get_file_type(self): - self.assertEquals(["machinecode", "gcode"], octoprint.filemanager.get_file_type("foo.gcode")) - self.assertEquals(["machinecode", "gcode"], octoprint.filemanager.get_file_type("foo.gco")) - self.assertEquals(["machinecode", "foo"], octoprint.filemanager.get_file_type("foo.f")) - self.assertEquals(["model", "stl"], octoprint.filemanager.get_file_type("foo.stl")) - self.assertEquals(["model", "amf"], octoprint.filemanager.get_file_type("foo.amf")) - self.assertIsNone(octoprint.filemanager.get_file_type("foo.unknown")) - - def test_hook_failure(self): - def hook(): - raise RuntimeError("Boo!") - self.plugin_manager.get_hooks.return_value = dict(hook=hook) - - with mock.patch("octoprint.filemanager.logging") as patched_logging: - logger = mock.MagicMock() - patched_logging.getLogger.return_value = logger - - octoprint.filemanager.get_all_extensions() - - self.assertEquals(1, len(logger.mock_calls)) diff --git a/tests/filemanager/test_filemanager.py b/tests/filemanager/test_filemanager.py index 110df373..1bb512c1 100644 --- a/tests/filemanager/test_filemanager.py +++ b/tests/filemanager/test_filemanager.py @@ -13,6 +13,98 @@ import mock import octoprint.filemanager import octoprint.filemanager.util +class FilemanagerMethodTest(unittest.TestCase): + + def setUp(self): + # mock plugin manager + self.plugin_manager_patcher = mock.patch("octoprint.plugin.plugin_manager") + self.plugin_manager_getter = self.plugin_manager_patcher.start() + + self.plugin_manager = mock.MagicMock() + + hook_extensions = dict( + some_plugin=lambda: dict(dict(machinecode=dict(foo=["foo", "f"]))), + other_plugin=lambda: dict(dict(model=dict(amf=["amf"]))), + mime_map=lambda: dict( + mime_map=dict( + mime_map_yes=octoprint.filemanager.ContentTypeMapping(["mime_map_yes"], "application/mime_map_yes") + ) + ), + mime_detect=lambda: dict( + dict( + machinecode=dict( + mime_detect_yes=octoprint.filemanager.ContentTypeDetector(["mime_detect_yes"], lambda x: "application/mime_detect_yes"), + mime_detect_no=octoprint.filemanager.ContentTypeDetector(["mime_detect_no"], lambda x: None) + ) + ) + ) + ) + self.plugin_manager.get_hooks.return_value = hook_extensions + + self.plugin_manager_getter.return_value = self.plugin_manager + + def tearDown(self): + self.plugin_manager_patcher.stop() + + def test_full_extension_tree(self): + full = octoprint.filemanager.full_extension_tree() + self.assertTrue("machinecode" in full) + self.assertTrue("gcode" in full["machinecode"]) + self.assertTrue(isinstance(full["machinecode"]["gcode"], octoprint.filemanager.ContentTypeMapping)) + self.assertItemsEqual(["gcode", "gco", "g"], full["machinecode"]["gcode"].extensions) + self.assertTrue("foo" in full["machinecode"]) + self.assertTrue(isinstance(full["machinecode"]["foo"], list)) + self.assertItemsEqual(["f", "foo"], full["machinecode"]["foo"]) + + self.assertTrue("model" in full) + self.assertTrue("stl" in full["model"]) + self.assertTrue(isinstance(full["model"]["stl"], octoprint.filemanager.ContentTypeMapping)) + self.assertItemsEqual(["stl"], full["model"]["stl"].extensions) + self.assertTrue("amf" in full["model"]) + self.assertTrue(isinstance(full["model"]["amf"], list)) + self.assertItemsEqual(["amf"], full["model"]["amf"]) + + def test_get_mimetype(self): + self.assertEquals(octoprint.filemanager.get_mime_type("foo.stl"), "application/sla") + self.assertEquals(octoprint.filemanager.get_mime_type("foo.gcode"), "text/plain") + self.assertEquals(octoprint.filemanager.get_mime_type("foo.unknown"), "application/octet-stream") + self.assertEquals(octoprint.filemanager.get_mime_type("foo.mime_map_yes"), "application/mime_map_yes") + self.assertEquals(octoprint.filemanager.get_mime_type("foo.mime_map_no"), "application/octet-stream") + self.assertEquals(octoprint.filemanager.get_mime_type("foo.mime_detect_yes"), "application/mime_detect_yes") + self.assertEquals(octoprint.filemanager.get_mime_type("foo.mime_detect_no"), "application/octet-stream") + + def test_valid_file_type(self): + self.assertTrue(octoprint.filemanager.valid_file_type("foo.stl", type="model")) + self.assertTrue(octoprint.filemanager.valid_file_type("foo.stl", type="stl")) + self.assertFalse(octoprint.filemanager.valid_file_type("foo.stl", type="machinecode")) + self.assertTrue(octoprint.filemanager.valid_file_type("foo.foo", type="machinecode")) + self.assertTrue(octoprint.filemanager.valid_file_type("foo.foo", type="foo")) + self.assertTrue(octoprint.filemanager.valid_file_type("foo.foo")) + self.assertTrue(octoprint.filemanager.valid_file_type("foo.mime_map_yes")) + self.assertTrue(octoprint.filemanager.valid_file_type("foo.mime_detect_yes")) + self.assertFalse(octoprint.filemanager.valid_file_type("foo.unknown")) + + def test_get_file_type(self): + self.assertEquals(["machinecode", "gcode"], octoprint.filemanager.get_file_type("foo.gcode")) + self.assertEquals(["machinecode", "gcode"], octoprint.filemanager.get_file_type("foo.gco")) + self.assertEquals(["machinecode", "foo"], octoprint.filemanager.get_file_type("foo.f")) + self.assertEquals(["model", "stl"], octoprint.filemanager.get_file_type("foo.stl")) + self.assertEquals(["model", "amf"], octoprint.filemanager.get_file_type("foo.amf")) + self.assertIsNone(octoprint.filemanager.get_file_type("foo.unknown")) + + def test_hook_failure(self): + def hook(): + raise RuntimeError("Boo!") + self.plugin_manager.get_hooks.return_value = dict(hook=hook) + + with mock.patch("octoprint.filemanager.logging") as patched_logging: + logger = mock.MagicMock() + patched_logging.getLogger.return_value = logger + + octoprint.filemanager.get_all_extensions() + + self.assertEquals(1, len(logger.mock_calls)) + class FileManagerTest(unittest.TestCase): def setUp(self): From 947f4c89cc588ab7b5d9a18ca23ce7cba6d989cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Wed, 7 Oct 2015 18:13:17 +0200 Subject: [PATCH 14/20] Fixed a win-only issue causing the gcode view not to reload on file modification --- src/octoprint/printer/standard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/octoprint/printer/standard.py b/src/octoprint/printer/standard.py index c332182c..8e11b642 100644 --- a/src/octoprint/printer/standard.py +++ b/src/octoprint/printer/standard.py @@ -680,7 +680,7 @@ class Printer(PrinterInterface, comm.MachineComPrintCallback): # Use a string for mtime because it could be float and the # javascript needs to exact match if not sd: - date = int(os.stat(path_on_disk).st_ctime) + date = int(os.stat(path_on_disk).st_mtime) try: fileData = self._fileManager.get_metadata(FileDestinations.SDCARD if sd else FileDestinations.LOCAL, path_on_disk) From db05632677368a9911b5a27db24e60aaf512c76c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Wed, 7 Oct 2015 18:58:42 +0200 Subject: [PATCH 15/20] Updated compiled CSS file --- src/octoprint/static/css/octoprint.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/octoprint/static/css/octoprint.css b/src/octoprint/static/css/octoprint.css index e4ccb8c7..e2475d4f 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}.nowrap{white-space:nowrap}.actioncol{text-align:center;white-space:nowrap}.actioncol a{text-decoration:none;color:#000}.actioncol a.disabled{color:#ccc;cursor:default}#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 #ccc;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.transparent{background-color:rgba(235,235,235,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top, rgba(255,255,255,0.6), rgba(204,204,204,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear, 0 0, 0 100%, from(rgba(255,255,255,0.6)), to(rgba(204,204,204,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top, rgba(255,255,255,0.6), rgba(204,204,204,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top, rgba(255,255,255,0.6), rgba(204,204,204,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom, rgba(255,255,255,0.6), rgba(204,204,204,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#99ffffff', endColorstr='#99cccccc', GradientType=0)}#navbar .navbar-inner.transparent .brand,#navbar .navbar-inner.transparent .nav>li>a{text-shadow:0 1px 0 #ccc;color:#333}#navbar .navbar-inner.transparent .brand .caret,#navbar .navbar-inner.transparent .nav>li>a .caret{border-bottom-color:#939393;border-top-color:#939393}#navbar .navbar-inner.transparent .brand:hover .caret,#navbar .navbar-inner.transparent .nav>li>a:hover .caret,#navbar .navbar-inner.transparent .brand:focus .caret,#navbar .navbar-inner.transparent .nav>li>a:focus .caret{border-bottom-color:#636363;border-top-color:#636363}#navbar .navbar-inner.transparent .brand span{background-image:url(../img/tentacle-20x20.png)}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.transparent .brand span{background-image:url(../img/tentacle-20x20@2x.png)}}#navbar .navbar-inner.transparent .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.transparent .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.transparent .nav li.dropdown.open.active>.dropdown-toggle{background-color:rgba(224,224,224,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top, rgba(204,204,204,0.6), rgba(255,255,255,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear, 0 0, 0 100%, from(rgba(204,204,204,0.6)), to(rgba(255,255,255,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top, rgba(204,204,204,0.6), rgba(255,255,255,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top, rgba(204,204,204,0.6), rgba(255,255,255,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom, rgba(204,204,204,0.6), rgba(255,255,255,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#99cccccc', endColorstr='#99ffffff', GradientType=0)}#navbar .navbar-inner.red{background-color:#bb645f;background-image:-moz-linear-gradient(top, #e28e8a, #802420);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#e28e8a), to(#802420));background-image:-webkit-linear-gradient(top, #e28e8a, #802420);background-image:-o-linear-gradient(top, #e28e8a, #802420);background-image:linear-gradient(to bottom, #e28e8a, #802420);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe28e8a', endColorstr='#ff802420', GradientType=0)}#navbar .navbar-inner.red .brand,#navbar .navbar-inner.red .nav>li>a{text-shadow:0 1px 0 #d86761;color:#f2f2f2}#navbar .navbar-inner.red .brand .caret,#navbar .navbar-inner.red .nav>li>a .caret{border-bottom-color:#d89491;border-top-color:#d89491}#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:#e5c3c1;border-top-color:#e5c3c1}#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:#a74f4a;background-image:-moz-linear-gradient(top, #802420, #e28e8a);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#802420), to(#e28e8a));background-image:-webkit-linear-gradient(top, #802420, #e28e8a);background-image:-o-linear-gradient(top, #802420, #e28e8a);background-image:linear-gradient(to bottom, #802420, #e28e8a);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff802420', endColorstr='#ffe28e8a', GradientType=0)}#navbar .navbar-inner.red.transparent{background-color:rgba(187,100,95,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top, rgba(226,142,138,0.6), rgba(128,36,32,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear, 0 0, 0 100%, from(rgba(226,142,138,0.6)), to(rgba(128,36,32,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top, rgba(226,142,138,0.6), rgba(128,36,32,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top, rgba(226,142,138,0.6), rgba(128,36,32,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom, rgba(226,142,138,0.6), rgba(128,36,32,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#99e28e8a', endColorstr='#99802420', GradientType=0)}#navbar .navbar-inner.red.transparent .brand,#navbar .navbar-inner.red.transparent .nav>li>a{text-shadow:0 1px 0 #d86761;color:#f2f2f2}#navbar .navbar-inner.red.transparent .brand .caret,#navbar .navbar-inner.red.transparent .nav>li>a .caret{border-bottom-color:#d89491;border-top-color:#d89491}#navbar .navbar-inner.red.transparent .brand:hover .caret,#navbar .navbar-inner.red.transparent .nav>li>a:hover .caret,#navbar .navbar-inner.red.transparent .brand:focus .caret,#navbar .navbar-inner.red.transparent .nav>li>a:focus .caret{border-bottom-color:#e5c3c1;border-top-color:#e5c3c1}#navbar .navbar-inner.red.transparent .brand span{background-image:url(../img/tentacle-20x20-light.png)}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.red.transparent .brand span{background-image:url(../img/tentacle-20x20-light@2x.png)}}#navbar .navbar-inner.red.transparent .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.red.transparent .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.red.transparent .nav li.dropdown.open.active>.dropdown-toggle{background-color:rgba(167,79,74,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top, rgba(128,36,32,0.6), rgba(226,142,138,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear, 0 0, 0 100%, from(rgba(128,36,32,0.6)), to(rgba(226,142,138,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top, rgba(128,36,32,0.6), rgba(226,142,138,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top, rgba(128,36,32,0.6), rgba(226,142,138,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom, rgba(128,36,32,0.6), rgba(226,142,138,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#99802420', endColorstr='#99e28e8a', 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:#f2f2f2}#navbar .navbar-inner.orange .brand .caret,#navbar .navbar-inner.orange .nav>li>a .caret{border-bottom-color:#f2b58d;border-top-color:#f2b58d}#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:#f2d3bf;border-top-color:#f2d3bf}#navbar .navbar-inner.orange .brand span{background-image:url(../img/tentacle-20x20-light.png)}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.orange .brand span{background-image:url(../img/tentacle-20x20-light@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.orange.transparent{background-color:rgba(227,150,101,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top, rgba(249,195,160,0.6), rgba(194,83,12,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear, 0 0, 0 100%, from(rgba(249,195,160,0.6)), to(rgba(194,83,12,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top, rgba(249,195,160,0.6), rgba(194,83,12,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top, rgba(249,195,160,0.6), rgba(194,83,12,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom, rgba(249,195,160,0.6), rgba(194,83,12,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#99f9c3a0', endColorstr='#99c2530c', GradientType=0)}#navbar .navbar-inner.orange.transparent .brand,#navbar .navbar-inner.orange.transparent .nav>li>a{text-shadow:0 1px 0 #f6a570;color:#f2f2f2}#navbar .navbar-inner.orange.transparent .brand .caret,#navbar .navbar-inner.orange.transparent .nav>li>a .caret{border-bottom-color:#f2b58d;border-top-color:#f2b58d}#navbar .navbar-inner.orange.transparent .brand:hover .caret,#navbar .navbar-inner.orange.transparent .nav>li>a:hover .caret,#navbar .navbar-inner.orange.transparent .brand:focus .caret,#navbar .navbar-inner.orange.transparent .nav>li>a:focus .caret{border-bottom-color:#f2d3bf;border-top-color:#f2d3bf}#navbar .navbar-inner.orange.transparent .brand span{background-image:url(../img/tentacle-20x20-light.png)}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.orange.transparent .brand span{background-image:url(../img/tentacle-20x20-light@2x.png)}}#navbar .navbar-inner.orange.transparent .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.orange.transparent .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.orange.transparent .nav li.dropdown.open.active>.dropdown-toggle{background-color:rgba(216,128,71,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top, rgba(194,83,12,0.6), rgba(249,195,160,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear, 0 0, 0 100%, from(rgba(194,83,12,0.6)), to(rgba(249,195,160,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top, rgba(194,83,12,0.6), rgba(249,195,160,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top, rgba(194,83,12,0.6), rgba(249,195,160,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom, rgba(194,83,12,0.6), rgba(249,195,160,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#99c2530c', endColorstr='#99f9c3a0', 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 #c2b00c;color:#f2f2f2}#navbar .navbar-inner.yellow .brand .caret,#navbar .navbar-inner.yellow .nav>li>a .caret{border-bottom-color:#f2e88d;border-top-color:#f2e88d}#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:#f2edbf;border-top-color:#f2edbf}#navbar .navbar-inner.yellow .brand span{background-image:url(../img/tentacle-20x20-light.png)}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.yellow .brand span{background-image:url(../img/tentacle-20x20-light@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.yellow.transparent{background-color:rgba(227,215,101,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top, rgba(249,240,160,0.6), rgba(194,176,12,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear, 0 0, 0 100%, from(rgba(249,240,160,0.6)), to(rgba(194,176,12,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top, rgba(249,240,160,0.6), rgba(194,176,12,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top, rgba(249,240,160,0.6), rgba(194,176,12,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom, rgba(249,240,160,0.6), rgba(194,176,12,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#99f9f0a0', endColorstr='#99c2b00c', GradientType=0)}#navbar .navbar-inner.yellow.transparent .brand,#navbar .navbar-inner.yellow.transparent .nav>li>a{text-shadow:0 1px 0 #c2b00c;color:#f2f2f2}#navbar .navbar-inner.yellow.transparent .brand .caret,#navbar .navbar-inner.yellow.transparent .nav>li>a .caret{border-bottom-color:#f2e88d;border-top-color:#f2e88d}#navbar .navbar-inner.yellow.transparent .brand:hover .caret,#navbar .navbar-inner.yellow.transparent .nav>li>a:hover .caret,#navbar .navbar-inner.yellow.transparent .brand:focus .caret,#navbar .navbar-inner.yellow.transparent .nav>li>a:focus .caret{border-bottom-color:#f2edbf;border-top-color:#f2edbf}#navbar .navbar-inner.yellow.transparent .brand span{background-image:url(../img/tentacle-20x20-light.png)}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.yellow.transparent .brand span{background-image:url(../img/tentacle-20x20-light@2x.png)}}#navbar .navbar-inner.yellow.transparent .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.yellow.transparent .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.yellow.transparent .nav li.dropdown.open.active>.dropdown-toggle{background-color:rgba(216,202,71,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top, rgba(194,176,12,0.6), rgba(249,240,160,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear, 0 0, 0 100%, from(rgba(194,176,12,0.6)), to(rgba(249,240,160,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top, rgba(194,176,12,0.6), rgba(249,240,160,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top, rgba(194,176,12,0.6), rgba(249,240,160,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom, rgba(194,176,12,0.6), rgba(249,240,160,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#99c2b00c', endColorstr='#99f9f0a0', 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 #50da00;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.green.transparent{background-color:rgba(152,240,100,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top, rgba(200,255,167,0.6), rgba(80,218,0,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear, 0 0, 0 100%, from(rgba(200,255,167,0.6)), to(rgba(80,218,0,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top, rgba(200,255,167,0.6), rgba(80,218,0,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top, rgba(200,255,167,0.6), rgba(80,218,0,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom, rgba(200,255,167,0.6), rgba(80,218,0,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#99c8ffa7', endColorstr='#9950da00', GradientType=0)}#navbar .navbar-inner.green.transparent .brand,#navbar .navbar-inner.green.transparent .nav>li>a{text-shadow:0 1px 0 #50da00;color:#333}#navbar .navbar-inner.green.transparent .brand .caret,#navbar .navbar-inner.green.transparent .nav>li>a .caret{border-bottom-color:#55992e;border-top-color:#55992e}#navbar .navbar-inner.green.transparent .brand:hover .caret,#navbar .navbar-inner.green.transparent .nav>li>a:hover .caret,#navbar .navbar-inner.green.transparent .brand:focus .caret,#navbar .navbar-inner.green.transparent .nav>li>a:focus .caret{border-bottom-color:#446630;border-top-color:#446630}#navbar .navbar-inner.green.transparent .brand span{background-image:url(../img/tentacle-20x20.png)}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.green.transparent .brand span{background-image:url(../img/tentacle-20x20@2x.png)}}#navbar .navbar-inner.green.transparent .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.green.transparent .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.green.transparent .nav li.dropdown.open.active>.dropdown-toggle{background-color:rgba(128,233,67,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top, rgba(80,218,0,0.6), rgba(200,255,167,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear, 0 0, 0 100%, from(rgba(80,218,0,0.6)), to(rgba(200,255,167,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top, rgba(80,218,0,0.6), rgba(200,255,167,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top, rgba(80,218,0,0.6), rgba(200,255,167,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom, rgba(80,218,0,0.6), rgba(200,255,167,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#9950da00', endColorstr='#99c8ffa7', GradientType=0)}#navbar .navbar-inner.blue{background-color:#2e63cc;background-image:-moz-linear-gradient(top, #4d88ff, #002b80);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#4d88ff), to(#002b80));background-image:-webkit-linear-gradient(top, #4d88ff, #002b80);background-image:-o-linear-gradient(top, #4d88ff, #002b80);background-image:linear-gradient(to bottom, #4d88ff, #002b80);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff4d88ff', endColorstr='#ff002b80', GradientType=0)}#navbar .navbar-inner.blue .brand,#navbar .navbar-inner.blue .nav>li>a{text-shadow:0 1px 0 #1a66ff;color:#f2f2f2}#navbar .navbar-inner.blue .brand .caret,#navbar .navbar-inner.blue .nav>li>a .caret{border-bottom-color:#799bdf;border-top-color:#799bdf}#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:#b6c7e9;border-top-color:#b6c7e9}#navbar .navbar-inner.blue .brand span{background-image:url(../img/tentacle-20x20-light.png)}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.blue .brand span{background-image:url(../img/tentacle-20x20-light@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:#1f50b3;background-image:-moz-linear-gradient(top, #002b80, #4d88ff);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#002b80), to(#4d88ff));background-image:-webkit-linear-gradient(top, #002b80, #4d88ff);background-image:-o-linear-gradient(top, #002b80, #4d88ff);background-image:linear-gradient(to bottom, #002b80, #4d88ff);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff002b80', endColorstr='#ff4d88ff', GradientType=0)}#navbar .navbar-inner.blue.transparent{background-color:rgba(46,99,204,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top, rgba(77,136,255,0.6), rgba(0,43,128,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear, 0 0, 0 100%, from(rgba(77,136,255,0.6)), to(rgba(0,43,128,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top, rgba(77,136,255,0.6), rgba(0,43,128,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top, rgba(77,136,255,0.6), rgba(0,43,128,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom, rgba(77,136,255,0.6), rgba(0,43,128,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#994d88ff', endColorstr='#99002b80', GradientType=0)}#navbar .navbar-inner.blue.transparent .brand,#navbar .navbar-inner.blue.transparent .nav>li>a{text-shadow:0 1px 0 #1a66ff;color:#f2f2f2}#navbar .navbar-inner.blue.transparent .brand .caret,#navbar .navbar-inner.blue.transparent .nav>li>a .caret{border-bottom-color:#799bdf;border-top-color:#799bdf}#navbar .navbar-inner.blue.transparent .brand:hover .caret,#navbar .navbar-inner.blue.transparent .nav>li>a:hover .caret,#navbar .navbar-inner.blue.transparent .brand:focus .caret,#navbar .navbar-inner.blue.transparent .nav>li>a:focus .caret{border-bottom-color:#b6c7e9;border-top-color:#b6c7e9}#navbar .navbar-inner.blue.transparent .brand span{background-image:url(../img/tentacle-20x20-light.png)}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.blue.transparent .brand span{background-image:url(../img/tentacle-20x20-light@2x.png)}}#navbar .navbar-inner.blue.transparent .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.blue.transparent .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.blue.transparent .nav li.dropdown.open.active>.dropdown-toggle{background-color:rgba(31,80,179,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top, rgba(0,43,128,0.6), rgba(77,136,255,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear, 0 0, 0 100%, from(rgba(0,43,128,0.6)), to(rgba(77,136,255,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top, rgba(0,43,128,0.6), rgba(77,136,255,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top, rgba(0,43,128,0.6), rgba(77,136,255,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom, rgba(0,43,128,0.6), rgba(77,136,255,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#99002b80', endColorstr='#994d88ff', 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 #a774ff;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:#d3bff5;border-top-color:#d3bff5}#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.violet.transparent{background-color:rgba(152,100,240,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top, rgba(200,167,255,0.6), rgba(80,0,218,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear, 0 0, 0 100%, from(rgba(200,167,255,0.6)), to(rgba(80,0,218,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top, rgba(200,167,255,0.6), rgba(80,0,218,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top, rgba(200,167,255,0.6), rgba(80,0,218,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom, rgba(200,167,255,0.6), rgba(80,0,218,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#99c8a7ff', endColorstr='#995000da', GradientType=0)}#navbar .navbar-inner.violet.transparent .brand,#navbar .navbar-inner.violet.transparent .nav>li>a{text-shadow:0 1px 0 #a774ff;color:#f2f2f2}#navbar .navbar-inner.violet.transparent .brand .caret,#navbar .navbar-inner.violet.transparent .nav>li>a .caret{border-bottom-color:#b58df9;border-top-color:#b58df9}#navbar .navbar-inner.violet.transparent .brand:hover .caret,#navbar .navbar-inner.violet.transparent .nav>li>a:hover .caret,#navbar .navbar-inner.violet.transparent .brand:focus .caret,#navbar .navbar-inner.violet.transparent .nav>li>a:focus .caret{border-bottom-color:#d3bff5;border-top-color:#d3bff5}#navbar .navbar-inner.violet.transparent .brand span{background-image:url(../img/tentacle-20x20-light.png)}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.violet.transparent .brand span{background-image:url(../img/tentacle-20x20-light@2x.png)}}#navbar .navbar-inner.violet.transparent .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.violet.transparent .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.violet.transparent .nav li.dropdown.open.active>.dropdown-toggle{background-color:rgba(128,67,233,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top, rgba(80,0,218,0.6), rgba(200,167,255,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear, 0 0, 0 100%, from(rgba(80,0,218,0.6)), to(rgba(200,167,255,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top, rgba(80,0,218,0.6), rgba(200,167,255,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top, rgba(80,0,218,0.6), rgba(200,167,255,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom, rgba(80,0,218,0.6), rgba(200,167,255,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#995000da', endColorstr='#99c8a7ff', 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 #5e5e5e;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.black.transparent{background-color:rgba(79,79,79,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top, rgba(120,120,120,0.6), rgba(18,18,18,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear, 0 0, 0 100%, from(rgba(120,120,120,0.6)), to(rgba(18,18,18,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top, rgba(120,120,120,0.6), rgba(18,18,18,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top, rgba(120,120,120,0.6), rgba(18,18,18,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom, rgba(120,120,120,0.6), rgba(18,18,18,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#99787878', endColorstr='#99121212', GradientType=0)}#navbar .navbar-inner.black.transparent .brand,#navbar .navbar-inner.black.transparent .nav>li>a{text-shadow:0 1px 0 #5e5e5e;color:#f2f2f2}#navbar .navbar-inner.black.transparent .brand .caret,#navbar .navbar-inner.black.transparent .nav>li>a .caret{border-bottom-color:#959595;border-top-color:#959595}#navbar .navbar-inner.black.transparent .brand:hover .caret,#navbar .navbar-inner.black.transparent .nav>li>a:hover .caret,#navbar .navbar-inner.black.transparent .brand:focus .caret,#navbar .navbar-inner.black.transparent .nav>li>a:focus .caret{border-bottom-color:#c4c4c4;border-top-color:#c4c4c4}#navbar .navbar-inner.black.transparent .brand span{background-image:url(../img/tentacle-20x20-light.png)}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.black.transparent .brand span{background-image:url(../img/tentacle-20x20-light@2x.png)}}#navbar .navbar-inner.black.transparent .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.black.transparent .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.black.transparent .nav li.dropdown.open.active>.dropdown-toggle{background-color:rgba(59,59,59,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top, rgba(18,18,18,0.6), rgba(120,120,120,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear, 0 0, 0 100%, from(rgba(18,18,18,0.6)), to(rgba(120,120,120,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top, rgba(18,18,18,0.6), rgba(120,120,120,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top, rgba(18,18,18,0.6), rgba(120,120,120,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom, rgba(18,18,18,0.6), rgba(120,120,120,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#99121212', endColorstr='#99787878', GradientType=0)}#navbar .navbar-inner.white{background-color:#e9e9e9;background-image:-moz-linear-gradient(top, #fff, #c8c8c8);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#c8c8c8));background-image:-webkit-linear-gradient(top, #fff, #c8c8c8);background-image:-o-linear-gradient(top, #fff, #c8c8c8);background-image:linear-gradient(to bottom, #fff, #c8c8c8);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffc8c8c8', GradientType=0)}#navbar .navbar-inner.white .brand,#navbar .navbar-inner.white .nav>li>a{text-shadow:0 1px 0 #c8c8c8;color:#333}#navbar .navbar-inner.white .brand .caret,#navbar .navbar-inner.white .nav>li>a .caret{border-bottom-color:#919191;border-top-color:#919191}#navbar .navbar-inner.white .brand:hover .caret,#navbar .navbar-inner.white .nav>li>a:hover .caret,#navbar .navbar-inner.white .brand:focus .caret,#navbar .navbar-inner.white .nav>li>a:focus .caret{border-bottom-color:#626262;border-top-color:#626262}#navbar .navbar-inner.white .brand span{background-image:url(../img/tentacle-20x20.png)}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.white .brand span{background-image:url(../img/tentacle-20x20@2x.png)}}#navbar .navbar-inner.white .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.white .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.white .nav li.dropdown.open.active>.dropdown-toggle{background-color:#dedede;background-image:-moz-linear-gradient(top, #c8c8c8, #fff);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#c8c8c8), to(#fff));background-image:-webkit-linear-gradient(top, #c8c8c8, #fff);background-image:-o-linear-gradient(top, #c8c8c8, #fff);background-image:linear-gradient(to bottom, #c8c8c8, #fff);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffc8c8c8', endColorstr='#ffffffff', GradientType=0)}#navbar .navbar-inner.white.transparent{background-color:rgba(233,233,233,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top, rgba(255,255,255,0.6), rgba(200,200,200,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear, 0 0, 0 100%, from(rgba(255,255,255,0.6)), to(rgba(200,200,200,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top, rgba(255,255,255,0.6), rgba(200,200,200,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top, rgba(255,255,255,0.6), rgba(200,200,200,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom, rgba(255,255,255,0.6), rgba(200,200,200,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#99ffffff', endColorstr='#99c8c8c8', GradientType=0)}#navbar .navbar-inner.white.transparent .brand,#navbar .navbar-inner.white.transparent .nav>li>a{text-shadow:0 1px 0 #c8c8c8;color:#333}#navbar .navbar-inner.white.transparent .brand .caret,#navbar .navbar-inner.white.transparent .nav>li>a .caret{border-bottom-color:#919191;border-top-color:#919191}#navbar .navbar-inner.white.transparent .brand:hover .caret,#navbar .navbar-inner.white.transparent .nav>li>a:hover .caret,#navbar .navbar-inner.white.transparent .brand:focus .caret,#navbar .navbar-inner.white.transparent .nav>li>a:focus .caret{border-bottom-color:#626262;border-top-color:#626262}#navbar .navbar-inner.white.transparent .brand span{background-image:url(../img/tentacle-20x20.png)}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.white.transparent .brand span{background-image:url(../img/tentacle-20x20@2x.png)}}#navbar .navbar-inner.white.transparent .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.white.transparent .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.white.transparent .nav li.dropdown.open.active>.dropdown-toggle{background-color:rgba(222,222,222,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top, rgba(200,200,200,0.6), rgba(255,255,255,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear, 0 0, 0 100%, from(rgba(200,200,200,0.6)), to(rgba(255,255,255,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top, rgba(200,200,200,0.6), rgba(255,255,255,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top, rgba(200,200,200,0.6), rgba(255,255,255,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom, rgba(200,200,200,0.6), rgba(255,255,255,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#99c8c8c8', endColorstr='#99ffffff', GradientType=0)}#navbar .navbar-inner .brand span{background-size:20px 20px;background-position:left center;padding-left:24px;background-repeat:no-repeat}.octoprint-container{margin-top:20px}.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:none;-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;text-align:left;white-space:nowrap}table th.gcode_files_action,table td.gcode_files_action{width:90px;text-align:center;white-space:nowrap}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{width:45px;text-align:center;white-space:nowrap}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{width:60px;text-align:center;white-space:nowrap}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{width:70px;text-align:center;white-space:nowrap}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}table th.settings_printerProfiles_profiles_name,table td.settings_printerProfiles_profiles_name{text-overflow:ellipsis;text-align:left}table th.settings_printerProfiles_profiles_model,table td.settings_printerProfiles_profiles_model{text-align:left;width:250px}table th.settings_printerProfiles_profiles_action,table td.settings_printerProfiles_profiles_action{width:80px;text-align:center;white-space:nowrap}table th.settings_printerProfiles_profiles_action a,table td.settings_printerProfiles_profiles_action a{text-decoration:none;color:#000}table th.settings_printerProfiles_profiles_action a.disabled,table td.settings_printerProfiles_profiles_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,#webcam_timelapse_postRoll,#webcam_timelapse_fps{text-align:right}ul.dropdown-menu li a{cursor:pointer}#connection_ports,#connection_baudrates,#connection_printers{width:100%}#offline_overlay,#reloadui_overlay{position:fixed;top:0;left:0;width:100%;height:100%;z-index:10001;display:none}#offline_overlay_background,#reloadui_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,#reloadui_overlay_wrapper{position:absolute;top:0;bottom:0;left:0;right:0;padding-top:60px}#offline_overlay_wrapper .container,#reloadui_overlay_wrapper .container{margin:auto}#webcam_container{width:100%;position:relative;outline:none}#webcam_container .keycontrol_overlay{position:absolute;left:10px;right:10px;bottom:10px;background:rgba(0,0,0,0.5);font-size:85%;color:white;padding:0}#webcam_container .keycontrol_overlay kbd{border:1px solid #eee;border-radius:3px;margin-left:2px;margin-right:2px;font-size:90%;padding:2px;min-width:1em}#webcam_container .keycontrol_overlay .keycontrol_overlay_heading{position:relative;padding:10px;font-weight:bold}#webcam_container .keycontrol_overlay .keycontrol_overlay_column{position:relative;width:45%;padding:10px;float:left}#files .gcode_files{padding-right:7px}#files .gcode_files .entry{padding:5px;line-height:20px;border-bottom:1px solid #ddd;position:relative}#files .gcode_files .entry:hover{background-color:#f5f5f5}#files .gcode_files .entry .title{text-overflow:ellipsis;word-break:break-all}#files .gcode_files .entry .uploaded,#files .gcode_files .entry .size,#files .gcode_files .entry .additionalInfo{font-size:85%;color:#999}#files .gcode_files .entry .action-buttons{position:absolute;bottom:5px;right:5px}#files .gcode_files .entry .additionalInfo{padding-bottom:22px}#files .upload-buttons{margin-top:10px}#files .form-search{text-align:center;margin-bottom:5px !important}#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 .jog-panel .slider{margin-bottom:10px}#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}#control .slider-handle{width:14px;height:14px;margin-left:-7px;margin-top:-3px}#control .custom_section h1{cursor:pointer}#control .custom_section_horizontal>.custom_control{display:inline-block}#control .custom_section_vertical>.custom_control{display:block}#control .custom_control .slider{margin-left:10px;margin-right:10px;margin-bottom:2px}#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 .canvas_container{position:relative}#gcode #gcode_layer_slider{position:absolute;right:0;top:0;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{margin-bottom:30px}#term .terminal #terminal-output{min-height:340px;margin-bottom:5px}#term #terminal-sendpanel{text-align:right}#settings_dialog_menu,#wizard_dialog_menu{margin-left:0}#wizard_firstrun_acl .acl_decision{margin-top:1em}#settings_appearance_managelanguagesdialog_emptylist{overflow:hidden;width:100%;height:300px;text-align:center;display:table}#settings_appearance_managelanguagesdialog_emptylist div{display:table-cell;vertical-align:middle}.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}.ui-pnotify .alert a{color:#c09853}.ui-pnotify .alert-error a,.ui-pnotify .alert-danger a{color:#b94a48}.ui-pnotify .alert-success a{color:#468847}.ui-pnotify .alert-info a{color:#3a87ad}.pnotify_additional_info .pnotify_more{font-size:85%}.text-right{text-align:right}.text-center{text-align:center}.overflow_visible{overflow:visible !important}.border_box{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.hidden{display:none}textarea.block{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:100%}#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:0;left:0;margin-left:0;width:100%}#drop_overlay #drop_overlay_wrapper #drop_locally,#drop_overlay #drop_overlay_wrapper #drop_locally_background{position:absolute;top:0;left:50%;margin-left:-50%;width:50%;border-right:2px dashed #ccc}#drop_overlay #drop_overlay_wrapper #drop_sd,#drop_overlay #drop_overlay_wrapper #drop_sd_background{position:absolute;top:0;left:50%;margin-left:0;width:50%;border-left:2px dashed #ccc}#drop_overlay #drop_overlay_wrapper .dropzone{height:100%;z-index:10001;color:#fff;font-size:30px}#drop_overlay #drop_overlay_wrapper .dropzone i{font-size:50px}#drop_overlay #drop_overlay_wrapper .dropzone .text{display:block;text-align:center;line-height:40px;position:absolute;width:100%;bottom:5%;filter:alpha(opacity=100);-moz-opacity:1;-khtml-opacity:1;opacity:1}#drop_overlay #drop_overlay_wrapper .dropzone_background{width:50%;height:100%;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;display:inline-block !important}.center{float:none;margin-left:auto;margin-right:auto}.flipH{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1)}.flipV{-webkit-transform:scaleY(-1);-moz-transform:scaleY(-1);-ms-transform:scaleY(-1)}.flipH.flipV{-webkit-transform:scaleX(-1) scaleY(-1);-moz-transform:scaleX(-1) scaleY(-1);-ms-transform:scaleX(-1) scaleY(-1)}.rotate90{-webkit-transform:rotate(-90deg);transform:rotate(-90deg)}.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%}.modal.large{width:975px;margin-left:-487px}.full-sized-box{position:absolute;bottom:0;left:0;right:0;top:0;padding:15px}.full-sized-box .row-fluid{height:100%}@media (max-width:979px){.full-sized-box{position:static}}_::-webkit-full-page-media,_:future,:root .full-sized-box{position:static}.scrollable{height:100%;overflow:auto;-webkit-overflow-scrolling:touch}.input-append .add-on.add-on-limited,.input-prepend .add-on.add-on-limited{overflow-x:hidden;text-overflow:ellipsis;width:inherit}.control-group.error .input-prepend .fileinput-button,.control-group.error .input-append .fileinput-button{border-color:#b94a48}input[type=number]{text-align:right} \ 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}.nowrap{white-space:nowrap}.actioncol{text-align:center;white-space:nowrap}.actioncol a{text-decoration:none;color:#000}.actioncol a.disabled{color:#ccc;cursor:default}#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.transparent{background-color:rgba(235,235,235,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top,rgba(255,255,255,0.6),rgba(204,204,204,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear,0 0,0 100%,from(rgba(255,255,255,0.6)),to(rgba(204,204,204,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top,rgba(255,255,255,0.6),rgba(204,204,204,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top,rgba(255,255,255,0.6),rgba(204,204,204,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom,rgba(255,255,255,0.6),rgba(204,204,204,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#99ffffff',endColorstr='#99cccccc',GradientType=0)}#navbar .navbar-inner.transparent .brand,#navbar .navbar-inner.transparent .nav>li>a{text-shadow:0 1px 0 #fff;color:#333}#navbar .navbar-inner.transparent .brand .caret,#navbar .navbar-inner.transparent .nav>li>a .caret{border-bottom-color:#939393;border-top-color:#939393}#navbar .navbar-inner.transparent .brand:hover .caret,#navbar .navbar-inner.transparent .nav>li>a:hover .caret,#navbar .navbar-inner.transparent .brand:focus .caret,#navbar .navbar-inner.transparent .nav>li>a:focus .caret{border-bottom-color:#636363;border-top-color:#636363}#navbar .navbar-inner.transparent .brand span{background-image:url(../img/tentacle-20x20.png)}@media(-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.transparent .brand span{background-image:url(../img/tentacle-20x20@2x.png)}}#navbar .navbar-inner.transparent .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.transparent .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.transparent .nav li.dropdown.open.active>.dropdown-toggle{background-color:rgba(224,224,224,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top,rgba(204,204,204,0.6),rgba(255,255,255,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear,0 0,0 100%,from(rgba(204,204,204,0.6)),to(rgba(255,255,255,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top,rgba(204,204,204,0.6),rgba(255,255,255,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top,rgba(204,204,204,0.6),rgba(255,255,255,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom,rgba(204,204,204,0.6),rgba(255,255,255,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#99cccccc',endColorstr='#99ffffff',GradientType=0)}#navbar .navbar-inner.red{background-color:#bb645f;background-image:-moz-linear-gradient(top,#e28e8a,#802420);background-image:-webkit-gradient(linear,0 0,0 100%,from(#e28e8a),to(#802420));background-image:-webkit-linear-gradient(top,#e28e8a,#802420);background-image:-o-linear-gradient(top,#e28e8a,#802420);background-image:linear-gradient(to bottom,#e28e8a,#802420);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe28e8a',endColorstr='#ff802420',GradientType=0)}#navbar .navbar-inner.red .brand,#navbar .navbar-inner.red .nav>li>a{text-shadow:0 1px 0 #802420;color:#f2f2f2}#navbar .navbar-inner.red .brand .caret,#navbar .navbar-inner.red .nav>li>a .caret{border-bottom-color:#d89491;border-top-color:#d89491}#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:#e5c3c1;border-top-color:#e5c3c1}#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:#a74f4a;background-image:-moz-linear-gradient(top,#802420,#e28e8a);background-image:-webkit-gradient(linear,0 0,0 100%,from(#802420),to(#e28e8a));background-image:-webkit-linear-gradient(top,#802420,#e28e8a);background-image:-o-linear-gradient(top,#802420,#e28e8a);background-image:linear-gradient(to bottom,#802420,#e28e8a);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff802420',endColorstr='#ffe28e8a',GradientType=0)}#navbar .navbar-inner.red.transparent{background-color:rgba(187,100,95,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top,rgba(226,142,138,0.6),rgba(128,36,32,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear,0 0,0 100%,from(rgba(226,142,138,0.6)),to(rgba(128,36,32,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top,rgba(226,142,138,0.6),rgba(128,36,32,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top,rgba(226,142,138,0.6),rgba(128,36,32,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom,rgba(226,142,138,0.6),rgba(128,36,32,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#99e28e8a',endColorstr='#99802420',GradientType=0)}#navbar .navbar-inner.red.transparent .brand,#navbar .navbar-inner.red.transparent .nav>li>a{text-shadow:0 1px 0 #802420;color:#f2f2f2}#navbar .navbar-inner.red.transparent .brand .caret,#navbar .navbar-inner.red.transparent .nav>li>a .caret{border-bottom-color:#d89491;border-top-color:#d89491}#navbar .navbar-inner.red.transparent .brand:hover .caret,#navbar .navbar-inner.red.transparent .nav>li>a:hover .caret,#navbar .navbar-inner.red.transparent .brand:focus .caret,#navbar .navbar-inner.red.transparent .nav>li>a:focus .caret{border-bottom-color:#e5c3c1;border-top-color:#e5c3c1}#navbar .navbar-inner.red.transparent .brand span{background-image:url(../img/tentacle-20x20-light.png)}@media(-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.red.transparent .brand span{background-image:url(../img/tentacle-20x20-light@2x.png)}}#navbar .navbar-inner.red.transparent .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.red.transparent .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.red.transparent .nav li.dropdown.open.active>.dropdown-toggle{background-color:rgba(167,79,74,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top,rgba(128,36,32,0.6),rgba(226,142,138,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear,0 0,0 100%,from(rgba(128,36,32,0.6)),to(rgba(226,142,138,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top,rgba(128,36,32,0.6),rgba(226,142,138,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top,rgba(128,36,32,0.6),rgba(226,142,138,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom,rgba(128,36,32,0.6),rgba(226,142,138,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#99802420',endColorstr='#99e28e8a',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:#f2f2f2}#navbar .navbar-inner.orange .brand .caret,#navbar .navbar-inner.orange .nav>li>a .caret{border-bottom-color:#f2b58d;border-top-color:#f2b58d}#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:#f2d3c0;border-top-color:#f2d3c0}#navbar .navbar-inner.orange .brand span{background-image:url(../img/tentacle-20x20-light.png)}@media(-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.orange .brand span{background-image:url(../img/tentacle-20x20-light@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.orange.transparent{background-color:rgba(227,150,101,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top,rgba(249,195,160,0.6),rgba(194,83,12,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear,0 0,0 100%,from(rgba(249,195,160,0.6)),to(rgba(194,83,12,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top,rgba(249,195,160,0.6),rgba(194,83,12,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top,rgba(249,195,160,0.6),rgba(194,83,12,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom,rgba(249,195,160,0.6),rgba(194,83,12,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#99f9c3a0',endColorstr='#99c2530c',GradientType=0)}#navbar .navbar-inner.orange.transparent .brand,#navbar .navbar-inner.orange.transparent .nav>li>a{text-shadow:0 1px 0 #f6a570;color:#f2f2f2}#navbar .navbar-inner.orange.transparent .brand .caret,#navbar .navbar-inner.orange.transparent .nav>li>a .caret{border-bottom-color:#f2b58d;border-top-color:#f2b58d}#navbar .navbar-inner.orange.transparent .brand:hover .caret,#navbar .navbar-inner.orange.transparent .nav>li>a:hover .caret,#navbar .navbar-inner.orange.transparent .brand:focus .caret,#navbar .navbar-inner.orange.transparent .nav>li>a:focus .caret{border-bottom-color:#f2d3c0;border-top-color:#f2d3c0}#navbar .navbar-inner.orange.transparent .brand span{background-image:url(../img/tentacle-20x20-light.png)}@media(-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.orange.transparent .brand span{background-image:url(../img/tentacle-20x20-light@2x.png)}}#navbar .navbar-inner.orange.transparent .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.orange.transparent .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.orange.transparent .nav li.dropdown.open.active>.dropdown-toggle{background-color:rgba(216,128,71,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top,rgba(194,83,12,0.6),rgba(249,195,160,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear,0 0,0 100%,from(rgba(194,83,12,0.6)),to(rgba(249,195,160,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top,rgba(194,83,12,0.6),rgba(249,195,160,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top,rgba(194,83,12,0.6),rgba(249,195,160,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom,rgba(194,83,12,0.6),rgba(249,195,160,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#99c2530c',endColorstr='#99f9c3a0',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.yellow.transparent{background-color:rgba(227,215,101,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top,rgba(249,240,160,0.6),rgba(194,176,12,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear,0 0,0 100%,from(rgba(249,240,160,0.6)),to(rgba(194,176,12,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top,rgba(249,240,160,0.6),rgba(194,176,12,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top,rgba(249,240,160,0.6),rgba(194,176,12,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom,rgba(249,240,160,0.6),rgba(194,176,12,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#99f9f0a0',endColorstr='#99c2b00c',GradientType=0)}#navbar .navbar-inner.yellow.transparent .brand,#navbar .navbar-inner.yellow.transparent .nav>li>a{text-shadow:0 1px 0 #f6e970;color:#333}#navbar .navbar-inner.yellow.transparent .brand .caret,#navbar .navbar-inner.yellow.transparent .nav>li>a .caret{border-bottom-color:#93892e;border-top-color:#93892e}#navbar .navbar-inner.yellow.transparent .brand:hover .caret,#navbar .navbar-inner.yellow.transparent .nav>li>a:hover .caret,#navbar .navbar-inner.yellow.transparent .brand:focus .caret,#navbar .navbar-inner.yellow.transparent .nav>li>a:focus .caret{border-bottom-color:#635e30;border-top-color:#635e30}#navbar .navbar-inner.yellow.transparent .brand span{background-image:url(../img/tentacle-20x20.png)}@media(-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.yellow.transparent .brand span{background-image:url(../img/tentacle-20x20@2x.png)}}#navbar .navbar-inner.yellow.transparent .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.yellow.transparent .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.yellow.transparent .nav li.dropdown.open.active>.dropdown-toggle{background-color:rgba(216,202,71,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top,rgba(194,176,12,0.6),rgba(249,240,160,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear,0 0,0 100%,from(rgba(194,176,12,0.6)),to(rgba(249,240,160,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top,rgba(194,176,12,0.6),rgba(249,240,160,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top,rgba(194,176,12,0.6),rgba(249,240,160,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom,rgba(194,176,12,0.6),rgba(249,240,160,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#99c2b00c',endColorstr='#99f9f0a0',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.green.transparent{background-color:rgba(152,240,100,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top,rgba(200,255,167,0.6),rgba(80,218,0,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear,0 0,0 100%,from(rgba(200,255,167,0.6)),to(rgba(80,218,0,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top,rgba(200,255,167,0.6),rgba(80,218,0,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top,rgba(200,255,167,0.6),rgba(80,218,0,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom,rgba(200,255,167,0.6),rgba(80,218,0,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#99c8ffa7',endColorstr='#9950da00',GradientType=0)}#navbar .navbar-inner.green.transparent .brand,#navbar .navbar-inner.green.transparent .nav>li>a{text-shadow:0 1px 0 #a7ff74;color:#333}#navbar .navbar-inner.green.transparent .brand .caret,#navbar .navbar-inner.green.transparent .nav>li>a .caret{border-bottom-color:#55992e;border-top-color:#55992e}#navbar .navbar-inner.green.transparent .brand:hover .caret,#navbar .navbar-inner.green.transparent .nav>li>a:hover .caret,#navbar .navbar-inner.green.transparent .brand:focus .caret,#navbar .navbar-inner.green.transparent .nav>li>a:focus .caret{border-bottom-color:#446630;border-top-color:#446630}#navbar .navbar-inner.green.transparent .brand span{background-image:url(../img/tentacle-20x20.png)}@media(-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.green.transparent .brand span{background-image:url(../img/tentacle-20x20@2x.png)}}#navbar .navbar-inner.green.transparent .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.green.transparent .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.green.transparent .nav li.dropdown.open.active>.dropdown-toggle{background-color:rgba(128,233,67,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top,rgba(80,218,0,0.6),rgba(200,255,167,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear,0 0,0 100%,from(rgba(80,218,0,0.6)),to(rgba(200,255,167,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top,rgba(80,218,0,0.6),rgba(200,255,167,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top,rgba(80,218,0,0.6),rgba(200,255,167,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom,rgba(80,218,0,0.6),rgba(200,255,167,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#9950da00',endColorstr='#99c8ffa7',GradientType=0)}#navbar .navbar-inner.blue{background-color:#2e63cc;background-image:-moz-linear-gradient(top,#4d88ff,#002b80);background-image:-webkit-gradient(linear,0 0,0 100%,from(#4d88ff),to(#002b80));background-image:-webkit-linear-gradient(top,#4d88ff,#002b80);background-image:-o-linear-gradient(top,#4d88ff,#002b80);background-image:linear-gradient(to bottom,#4d88ff,#002b80);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff4d88ff',endColorstr='#ff002b80',GradientType=0)}#navbar .navbar-inner.blue .brand,#navbar .navbar-inner.blue .nav>li>a{text-shadow:0 1px 0 #002b80;color:#f2f2f2}#navbar .navbar-inner.blue .brand .caret,#navbar .navbar-inner.blue .nav>li>a .caret{border-bottom-color:#799bdf;border-top-color:#799bdf}#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:#b6c7e9;border-top-color:#b6c7e9}#navbar .navbar-inner.blue .brand span{background-image:url(../img/tentacle-20x20-light.png)}@media(-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.blue .brand span{background-image:url(../img/tentacle-20x20-light@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:#1f50b3;background-image:-moz-linear-gradient(top,#002b80,#4d88ff);background-image:-webkit-gradient(linear,0 0,0 100%,from(#002b80),to(#4d88ff));background-image:-webkit-linear-gradient(top,#002b80,#4d88ff);background-image:-o-linear-gradient(top,#002b80,#4d88ff);background-image:linear-gradient(to bottom,#002b80,#4d88ff);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff002b80',endColorstr='#ff4d88ff',GradientType=0)}#navbar .navbar-inner.blue.transparent{background-color:rgba(46,99,204,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top,rgba(77,136,255,0.6),rgba(0,43,128,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear,0 0,0 100%,from(rgba(77,136,255,0.6)),to(rgba(0,43,128,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top,rgba(77,136,255,0.6),rgba(0,43,128,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top,rgba(77,136,255,0.6),rgba(0,43,128,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom,rgba(77,136,255,0.6),rgba(0,43,128,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#994d88ff',endColorstr='#99002b80',GradientType=0)}#navbar .navbar-inner.blue.transparent .brand,#navbar .navbar-inner.blue.transparent .nav>li>a{text-shadow:0 1px 0 #002b80;color:#f2f2f2}#navbar .navbar-inner.blue.transparent .brand .caret,#navbar .navbar-inner.blue.transparent .nav>li>a .caret{border-bottom-color:#799bdf;border-top-color:#799bdf}#navbar .navbar-inner.blue.transparent .brand:hover .caret,#navbar .navbar-inner.blue.transparent .nav>li>a:hover .caret,#navbar .navbar-inner.blue.transparent .brand:focus .caret,#navbar .navbar-inner.blue.transparent .nav>li>a:focus .caret{border-bottom-color:#b6c7e9;border-top-color:#b6c7e9}#navbar .navbar-inner.blue.transparent .brand span{background-image:url(../img/tentacle-20x20-light.png)}@media(-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.blue.transparent .brand span{background-image:url(../img/tentacle-20x20-light@2x.png)}}#navbar .navbar-inner.blue.transparent .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.blue.transparent .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.blue.transparent .nav li.dropdown.open.active>.dropdown-toggle{background-color:rgba(31,80,179,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top,rgba(0,43,128,0.6),rgba(77,136,255,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear,0 0,0 100%,from(rgba(0,43,128,0.6)),to(rgba(77,136,255,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top,rgba(0,43,128,0.6),rgba(77,136,255,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top,rgba(0,43,128,0.6),rgba(77,136,255,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom,rgba(0,43,128,0.6),rgba(77,136,255,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#99002b80',endColorstr='#994d88ff',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.violet.transparent{background-color:rgba(152,100,240,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top,rgba(200,167,255,0.6),rgba(80,0,218,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear,0 0,0 100%,from(rgba(200,167,255,0.6)),to(rgba(80,0,218,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top,rgba(200,167,255,0.6),rgba(80,0,218,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top,rgba(200,167,255,0.6),rgba(80,0,218,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom,rgba(200,167,255,0.6),rgba(80,0,218,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#99c8a7ff',endColorstr='#995000da',GradientType=0)}#navbar .navbar-inner.violet.transparent .brand,#navbar .navbar-inner.violet.transparent .nav>li>a{text-shadow:0 1px 0 #5000da;color:#f2f2f2}#navbar .navbar-inner.violet.transparent .brand .caret,#navbar .navbar-inner.violet.transparent .nav>li>a .caret{border-bottom-color:#b58df9;border-top-color:#b58df9}#navbar .navbar-inner.violet.transparent .brand:hover .caret,#navbar .navbar-inner.violet.transparent .nav>li>a:hover .caret,#navbar .navbar-inner.violet.transparent .brand:focus .caret,#navbar .navbar-inner.violet.transparent .nav>li>a:focus .caret{border-bottom-color:#d3c0f5;border-top-color:#d3c0f5}#navbar .navbar-inner.violet.transparent .brand span{background-image:url(../img/tentacle-20x20-light.png)}@media(-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.violet.transparent .brand span{background-image:url(../img/tentacle-20x20-light@2x.png)}}#navbar .navbar-inner.violet.transparent .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.violet.transparent .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.violet.transparent .nav li.dropdown.open.active>.dropdown-toggle{background-color:rgba(128,67,233,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top,rgba(80,0,218,0.6),rgba(200,167,255,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear,0 0,0 100%,from(rgba(80,0,218,0.6)),to(rgba(200,167,255,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top,rgba(80,0,218,0.6),rgba(200,167,255,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top,rgba(80,0,218,0.6),rgba(200,167,255,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom,rgba(80,0,218,0.6),rgba(200,167,255,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#995000da',endColorstr='#99c8a7ff',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.black.transparent{background-color:rgba(79,79,79,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top,rgba(120,120,120,0.6),rgba(18,18,18,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear,0 0,0 100%,from(rgba(120,120,120,0.6)),to(rgba(18,18,18,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top,rgba(120,120,120,0.6),rgba(18,18,18,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top,rgba(120,120,120,0.6),rgba(18,18,18,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom,rgba(120,120,120,0.6),rgba(18,18,18,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#99787878',endColorstr='#99121212',GradientType=0)}#navbar .navbar-inner.black.transparent .brand,#navbar .navbar-inner.black.transparent .nav>li>a{text-shadow:0 1px 0 #121212;color:#f2f2f2}#navbar .navbar-inner.black.transparent .brand .caret,#navbar .navbar-inner.black.transparent .nav>li>a .caret{border-bottom-color:#959595;border-top-color:#959595}#navbar .navbar-inner.black.transparent .brand:hover .caret,#navbar .navbar-inner.black.transparent .nav>li>a:hover .caret,#navbar .navbar-inner.black.transparent .brand:focus .caret,#navbar .navbar-inner.black.transparent .nav>li>a:focus .caret{border-bottom-color:#c4c4c4;border-top-color:#c4c4c4}#navbar .navbar-inner.black.transparent .brand span{background-image:url(../img/tentacle-20x20-light.png)}@media(-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.black.transparent .brand span{background-image:url(../img/tentacle-20x20-light@2x.png)}}#navbar .navbar-inner.black.transparent .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.black.transparent .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.black.transparent .nav li.dropdown.open.active>.dropdown-toggle{background-color:rgba(59,59,59,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top,rgba(18,18,18,0.6),rgba(120,120,120,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear,0 0,0 100%,from(rgba(18,18,18,0.6)),to(rgba(120,120,120,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top,rgba(18,18,18,0.6),rgba(120,120,120,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top,rgba(18,18,18,0.6),rgba(120,120,120,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom,rgba(18,18,18,0.6),rgba(120,120,120,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#99121212',endColorstr='#99787878',GradientType=0)}#navbar .navbar-inner.white{background-color:#e9e9e9;background-image:-moz-linear-gradient(top,#fff,#c8c8c8);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#c8c8c8));background-image:-webkit-linear-gradient(top,#fff,#c8c8c8);background-image:-o-linear-gradient(top,#fff,#c8c8c8);background-image:linear-gradient(to bottom,#fff,#c8c8c8);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffc8c8c8',GradientType=0)}#navbar .navbar-inner.white .brand,#navbar .navbar-inner.white .nav>li>a{text-shadow:0 1px 0 #fff;color:#333}#navbar .navbar-inner.white .brand .caret,#navbar .navbar-inner.white .nav>li>a .caret{border-bottom-color:#919191;border-top-color:#919191}#navbar .navbar-inner.white .brand:hover .caret,#navbar .navbar-inner.white .nav>li>a:hover .caret,#navbar .navbar-inner.white .brand:focus .caret,#navbar .navbar-inner.white .nav>li>a:focus .caret{border-bottom-color:#626262;border-top-color:#626262}#navbar .navbar-inner.white .brand span{background-image:url(../img/tentacle-20x20.png)}@media(-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.white .brand span{background-image:url(../img/tentacle-20x20@2x.png)}}#navbar .navbar-inner.white .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.white .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.white .nav li.dropdown.open.active>.dropdown-toggle{background-color:#dedede;background-image:-moz-linear-gradient(top,#c8c8c8,#fff);background-image:-webkit-gradient(linear,0 0,0 100%,from(#c8c8c8),to(#fff));background-image:-webkit-linear-gradient(top,#c8c8c8,#fff);background-image:-o-linear-gradient(top,#c8c8c8,#fff);background-image:linear-gradient(to bottom,#c8c8c8,#fff);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffc8c8c8',endColorstr='#ffffffff',GradientType=0)}#navbar .navbar-inner.white.transparent{background-color:rgba(233,233,233,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top,rgba(255,255,255,0.6),rgba(200,200,200,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear,0 0,0 100%,from(rgba(255,255,255,0.6)),to(rgba(200,200,200,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top,rgba(255,255,255,0.6),rgba(200,200,200,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top,rgba(255,255,255,0.6),rgba(200,200,200,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom,rgba(255,255,255,0.6),rgba(200,200,200,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#99ffffff',endColorstr='#99c8c8c8',GradientType=0)}#navbar .navbar-inner.white.transparent .brand,#navbar .navbar-inner.white.transparent .nav>li>a{text-shadow:0 1px 0 #fff;color:#333}#navbar .navbar-inner.white.transparent .brand .caret,#navbar .navbar-inner.white.transparent .nav>li>a .caret{border-bottom-color:#919191;border-top-color:#919191}#navbar .navbar-inner.white.transparent .brand:hover .caret,#navbar .navbar-inner.white.transparent .nav>li>a:hover .caret,#navbar .navbar-inner.white.transparent .brand:focus .caret,#navbar .navbar-inner.white.transparent .nav>li>a:focus .caret{border-bottom-color:#626262;border-top-color:#626262}#navbar .navbar-inner.white.transparent .brand span{background-image:url(../img/tentacle-20x20.png)}@media(-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#navbar .navbar-inner.white.transparent .brand span{background-image:url(../img/tentacle-20x20@2x.png)}}#navbar .navbar-inner.white.transparent .nav li.dropdown.open>.dropdown-toggle,#navbar .navbar-inner.white.transparent .nav li.dropdown.active>.dropdown-toggle,#navbar .navbar-inner.white.transparent .nav li.dropdown.open.active>.dropdown-toggle{background-color:rgba(222,222,222,0.6);background-image:"../img/trans-background.png";background-image:-moz-linear-gradient(top,rgba(200,200,200,0.6),rgba(255,255,255,0.6)),url("../img/trans-background.png");background-image:-webkit-gradient(linear,0 0,0 100%,from(rgba(200,200,200,0.6)),to(rgba(255,255,255,0.6))),url("../img/trans-background.png");background-image:-webkit-linear-gradient(top,rgba(200,200,200,0.6),rgba(255,255,255,0.6)),url("../img/trans-background.png");background-image:-o-linear-gradient(top,rgba(200,200,200,0.6),rgba(255,255,255,0.6)),url("../img/trans-background.png");background-image:linear-gradient(to bottom,rgba(200,200,200,0.6),rgba(255,255,255,0.6)),url("../img/trans-background.png");background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#99c8c8c8',endColorstr='#99ffffff',GradientType=0)}#navbar .navbar-inner .brand span{background-size:20px 20px;background-position:left center;padding-left:24px;background-repeat:no-repeat}.octoprint-container{margin-top:20px}.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;text-align:left;white-space:nowrap}table th.gcode_files_action,table td.gcode_files_action{width:90px;text-align:center;white-space:nowrap}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{width:45px;text-align:center;white-space:nowrap}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{width:60px;text-align:center;white-space:nowrap}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{width:70px;text-align:center;white-space:nowrap}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}table th.settings_printerProfiles_profiles_name,table td.settings_printerProfiles_profiles_name{text-overflow:ellipsis;text-align:left}table th.settings_printerProfiles_profiles_model,table td.settings_printerProfiles_profiles_model{text-align:left;width:250px}table th.settings_printerProfiles_profiles_action,table td.settings_printerProfiles_profiles_action{width:80px;text-align:center;white-space:nowrap}table th.settings_printerProfiles_profiles_action a,table td.settings_printerProfiles_profiles_action a{text-decoration:none;color:#000}table th.settings_printerProfiles_profiles_action a.disabled,table td.settings_printerProfiles_profiles_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,#webcam_timelapse_postRoll,#webcam_timelapse_fps{text-align:right}ul.dropdown-menu li a{cursor:pointer}#connection_ports,#connection_baudrates,#connection_printers{width:100%}#offline_overlay,#reloadui_overlay{position:fixed;top:0;left:0;width:100%;height:100%;z-index:10001;display:none}#offline_overlay_background,#reloadui_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,#reloadui_overlay_wrapper{position:absolute;top:0;bottom:0;left:0;right:0;padding-top:60px}#offline_overlay_wrapper .container,#reloadui_overlay_wrapper .container{margin:auto}#webcam_container{width:100%;position:relative;outline:0}#webcam_container .keycontrol_overlay{position:absolute;left:10px;right:10px;bottom:10px;background:rgba(0,0,0,0.5);font-size:85%;color:white;padding:0}#webcam_container .keycontrol_overlay kbd{border:1px solid #eee;border-radius:3px;margin-left:2px;margin-right:2px;font-size:90%;padding:2px;min-width:1em}#webcam_container .keycontrol_overlay .keycontrol_overlay_heading{position:relative;padding:10px;font-weight:bold}#webcam_container .keycontrol_overlay .keycontrol_overlay_column{position:relative;width:45%;padding:10px;float:left}#files .gcode_files{padding-right:7px}#files .gcode_files .entry{padding:5px;line-height:20px;border-bottom:1px solid #ddd;position:relative}#files .gcode_files .entry:hover{background-color:#f5f5f5}#files .gcode_files .entry .title{text-overflow:ellipsis;word-break:break-all}#files .gcode_files .entry .uploaded,#files .gcode_files .entry .size,#files .gcode_files .entry .additionalInfo{font-size:85%;color:#999}#files .gcode_files .entry .action-buttons{position:absolute;bottom:5px;right:5px}#files .gcode_files .entry .additionalInfo{padding-bottom:22px}#files .upload-buttons{margin-top:10px}#files .form-search{text-align:center;margin-bottom:5px!important}#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 .jog-panel .slider{margin-bottom:10px}#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}#control .slider-handle{width:14px;height:14px;margin-left:-7px;margin-top:-3px}#control .custom_section h1{cursor:pointer}#control .custom_section_horizontal>.custom_control{display:inline-block}#control .custom_section_vertical>.custom_control{display:block}#control .custom_control .slider{margin-left:10px;margin-right:10px;margin-bottom:2px}#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 .canvas_container{position:relative}#gcode #gcode_layer_slider{position:absolute;right:0;top:0;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{margin-bottom:30px}#term .terminal #terminal-output{min-height:340px;margin-bottom:5px}#term #terminal-sendpanel{text-align:right}#settings_dialog_menu,#wizard_dialog_menu{margin-left:0}#wizard_firstrun_acl .acl_decision{margin-top:1em}#settings_appearance_managelanguagesdialog_emptylist{overflow:hidden;width:100%;height:300px;text-align:center;display:table}#settings_appearance_managelanguagesdialog_emptylist div{display:table-cell;vertical-align:middle}.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}.ui-pnotify .alert a{color:#c09853}.ui-pnotify .alert-error a,.ui-pnotify .alert-danger a{color:#b94a48}.ui-pnotify .alert-success a{color:#468847}.ui-pnotify .alert-info a{color:#3a87ad}.pnotify_additional_info .pnotify_more{font-size:85%}.text-right{text-align:right}.text-center{text-align:center}.overflow_visible{overflow:visible!important}.border_box{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.hidden{display:none}textarea.block{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:100%}#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:0;left:0;margin-left:0;width:100%}#drop_overlay #drop_overlay_wrapper #drop_locally,#drop_overlay #drop_overlay_wrapper #drop_locally_background{position:absolute;top:0;left:50%;margin-left:-50%;width:50%;border-right:2px dashed #ccc}#drop_overlay #drop_overlay_wrapper #drop_sd,#drop_overlay #drop_overlay_wrapper #drop_sd_background{position:absolute;top:0;left:50%;margin-left:0;width:50%;border-left:2px dashed #ccc}#drop_overlay #drop_overlay_wrapper .dropzone{height:100%;z-index:10001;color:#fff;font-size:30px}#drop_overlay #drop_overlay_wrapper .dropzone i{font-size:50px}#drop_overlay #drop_overlay_wrapper .dropzone .text{display:block;text-align:center;line-height:40px;position:absolute;width:100%;bottom:5%;filter:alpha(opacity=100);-moz-opacity:1.0;-khtml-opacity:1.0;opacity:1.0}#drop_overlay #drop_overlay_wrapper .dropzone_background{width:50%;height:100%;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;display:inline-block!important}.center{float:none;margin-left:auto;margin-right:auto}.flipH{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1)}.flipV{-webkit-transform:scaleY(-1);-moz-transform:scaleY(-1);-ms-transform:scaleY(-1)}.flipH.flipV{-webkit-transform:scaleX(-1) scaleY(-1);-moz-transform:scaleX(-1) scaleY(-1);-ms-transform:scaleX(-1) scaleY(-1)}.rotate90{-webkit-transform:rotate(-90deg);transform:rotate(-90deg)}.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%}.modal.large{width:975px;margin-left:-487px}.full-sized-box{position:absolute;bottom:0;left:0;right:0;top:0;padding:15px}.full-sized-box .row-fluid{height:100%}@media(max-width:979px){.full-sized-box{position:static}}_::-webkit-full-page-media,_:future,:root .full-sized-box{position:static}.scrollable{height:100%;overflow:auto;-webkit-overflow-scrolling:touch}.input-append .add-on.add-on-limited,.input-prepend .add-on.add-on-limited{overflow-x:hidden;text-overflow:ellipsis;width:inherit}.control-group.error .input-prepend .fileinput-button,.control-group.error .input-append .fileinput-button{border-color:#b94a48}input[type=number]{text-align:right} \ No newline at end of file From 251ec9d823b3f6b8fa350dbe03cd54844ffe1584 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Thu, 8 Oct 2015 09:26:34 +0200 Subject: [PATCH 16/20] Added changelog entry for #632 and adjusted Fan On --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5210bd66..134b456a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,10 @@ * Documentation improvements * Test buttons for webcam snapshot & stream URL, ffmpeg path and some other settings (see also [#183](https://github.com/foosel/OctoPrint/issues/183)). +* Temperature graph automatically adjusts its Y axis range if necessary to + accomodate the plotted data (see also [#632](https://github.com/foosel/OctoPrint/issues/632)). +* "Fan on" command now always sends `S255` parameter for better compatibility + across firmwares. ### Bug Fixes From bbbdb3dde6aa9811d5a2fe7e46539d44d2c24094 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Thu, 8 Oct 2015 10:22:09 +0200 Subject: [PATCH 17/20] Note that z-based timelapses don't work when printing from SD --- src/octoprint/templates/tabs/timelapse.jinja2 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/octoprint/templates/tabs/timelapse.jinja2 b/src/octoprint/templates/tabs/timelapse.jinja2 index 5fda40a9..acea78ad 100644 --- a/src/octoprint/templates/tabs/timelapse.jinja2 +++ b/src/octoprint/templates/tabs/timelapse.jinja2 @@ -7,10 +7,11 @@ {{ _('Warning') }} {{ _('Do not use with spiralized ("Joris") vases or similar continuous Z models.') }} + {{ _('Note') }} {% trans %}Does not work when printing from the printer's SD Card (no way to detect the change in Z reliably). Use "Timed" mode for those prints instead.{% endtrans %}
From d3b7a77da79a24f14602f2ea79d5df55e3d9026b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Thu, 8 Oct 2015 12:22:22 +0200 Subject: [PATCH 18/20] Fix: Core wizard API didn't yet understand json-only requests --- src/octoprint/plugins/corewizard/__init__.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/octoprint/plugins/corewizard/__init__.py b/src/octoprint/plugins/corewizard/__init__.py index 75f16e78..e9cc114b 100644 --- a/src/octoprint/plugins/corewizard/__init__.py +++ b/src/octoprint/plugins/corewizard/__init__.py @@ -84,13 +84,17 @@ class CoreWizardPlugin(octoprint.plugin.AssetPlugin, from flask import request from octoprint.server.api import valid_boolean_trues, NO_CONTENT - if "ac" in request.values and request.values["ac"] in valid_boolean_trues and \ - "user" in request.values.keys() and "pass1" in request.values.keys() and \ - "pass2" in request.values.keys() and request.values["pass1"] == request.values["pass2"]: + data = request.values + if hasattr(request, "json") and request.json: + data = request.json + + if "ac" in data and data["ac"] in valid_boolean_trues and \ + "user" in data.keys() and "pass1" in data.keys() and \ + "pass2" in data.keys() and data["pass1"] == data["pass2"]: # configure access control self._settings.global_set_boolean(["accessControl", "enabled"], True) - octoprint.server.userManager.addUser(request.values["user"], request.values["pass1"], True, ["user", "admin"], overwrite=True) - elif "ac" in request.values.keys() and not request.values["ac"] in valid_boolean_trues: + octoprint.server.userManager.addUser(data["user"], data["pass1"], True, ["user", "admin"], overwrite=True) + elif "ac" in data.keys() and not data["ac"] in valid_boolean_trues: # disable access control self._settings.global_set_boolean(["accessControl", "enabled"], False) From dd4bec33291884a4914738b3d4c5b94a2d463b1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Thu, 8 Oct 2015 13:49:57 +0200 Subject: [PATCH 19/20] Fix: Do not cache templates if wizards are active Otherwise wizards will be reshown until the cache is forcefully cleared. --- src/octoprint/server/views.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/octoprint/server/views.py b/src/octoprint/server/views.py index 6ec2337f..0dafccfc 100644 --- a/src/octoprint/server/views.py +++ b/src/octoprint/server/views.py @@ -33,10 +33,16 @@ _valid_div_re = re.compile("[a-zA-Z_-]+") @app.route("/") def index(): - force_refresh = util.flask.cache_check_headers() or "_refresh" in request.values - global _templates, _plugin_names, _plugin_vars + # helper to check if wizards are active + def wizard_active(templates): + return templates is not None and bool(templates["wizard"]["order"]) + + # we force a refresh if the client forces one or if we have wizards cached + force_refresh = util.flask.cache_check_headers() or "_refresh" in request.values or wizard_active(_templates) + + # if we need to refresh our template cache or it's not yet set, process it if force_refresh or _templates is None or _plugin_names is None or _plugin_vars is None: _templates, _plugin_names, _plugin_vars = _process_templates() @@ -58,9 +64,8 @@ def index(): break else: - wizard = bool(_templates["wizard"]["order"]) + wizard = wizard_active(_templates) enable_accesscontrol = userManager is not None - render_kwargs.update(dict( webcamStream=settings().get(["webcam", "stream"]), enableTemperatureGraph=settings().get(["feature", "temperatureGraph"]), @@ -75,7 +80,8 @@ def index(): # no plugin took an interest, we'll use the default UI def make_default_ui(): r = make_response(render_template("index.jinja2", **render_kwargs)) - if bool(render_kwargs["templates"]["wizard"]["order"]): + if wizard: + # if we have active wizard dialogs, set non caching headers r = util.flask.add_non_caching_response_headers(r) return r From 3767f83fe23b597650f7a3197068bb25f771ca44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Thu, 8 Oct 2015 15:23:19 +0200 Subject: [PATCH 20/20] Slight timing changes in wizard finishing process We now wait until the settings have been saved before we close the dialog. That should hopefully reduce chances of an asynchronous "Oh no, the settings were updated" message showing up just when finishing the dialog. --- src/octoprint/static/js/app/dataupdater.js | 1 - .../static/js/app/viewmodels/settings.js | 2 +- .../static/js/app/viewmodels/wizard.js | 28 +++++++++++++++---- .../templates/overlays/reloadui.jinja2 | 9 ++++-- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/octoprint/static/js/app/dataupdater.js b/src/octoprint/static/js/app/dataupdater.js index 443f657d..c607016d 100644 --- a/src/octoprint/static/js/app/dataupdater.js +++ b/src/octoprint/static/js/app/dataupdater.js @@ -7,7 +7,6 @@ function DataUpdater(allViewModels) { self._configHash = undefined; self.reloadOverlay = $("#reloadui_overlay"); - $("#reloadui_overlay_reload").click(function() { location.reload(true); }); self.connect = function() { OctoPrint.socket.connect({debug: !!SOCKJS_DEBUG}); diff --git a/src/octoprint/static/js/app/viewmodels/settings.js b/src/octoprint/static/js/app/viewmodels/settings.js index f3d0ed20..0e2a23d5 100644 --- a/src/octoprint/static/js/app/viewmodels/settings.js +++ b/src/octoprint/static/js/app/viewmodels/settings.js @@ -705,7 +705,7 @@ $(function() { data = getOnlyChangedData(self.getLocalData(), self.lastReceivedSettings); } - OctoPrint.settings.save(data) + return OctoPrint.settings.save(data) .done(function(data, status, xhr) { self.receiving(true); self.sending(false); diff --git a/src/octoprint/static/js/app/viewmodels/wizard.js b/src/octoprint/static/js/app/viewmodels/wizard.js index 54ca62e4..de9db3d0 100644 --- a/src/octoprint/static/js/app/viewmodels/wizard.js +++ b/src/octoprint/static/js/app/viewmodels/wizard.js @@ -6,6 +6,7 @@ $(function() { self.settingsViewModel = parameters[1]; self.wizardDialog = undefined; + self.reloadOverlay = undefined; self.allViewModels = undefined; @@ -40,6 +41,7 @@ $(function() { self.onStartup = function() { self.wizardDialog = $("#wizard_dialog"); + self.reloadOverlay = $("#reloadui_overlay"); }; self.onUserLoggedIn = function() { @@ -126,8 +128,7 @@ $(function() { .done(function() { self.closeDialog(); if (reload) { - log.info("Wizard requested reloading"); - location.reload(true); + self.reloadOverlay.show(); } }); } @@ -144,12 +145,27 @@ $(function() { }; self.finishWizard = function() { + var deferred = $.Deferred(); self.finishing = true; - self.settingsViewModel.saveData(); - return OctoPrint.wizard.finish(self.wizards) - .always(function() { - self.finishing = false; + + self.settingsViewModel.saveData() + .done(function() { + OctoPrint.wizard.finish(self.wizards) + .done(function() { + deferred.resolve(arguments); + }) + .fail(function() { + deferred.reject(arguments); + }) + .always(function() { + self.finishing = false; + }); + }) + .fail(function() { + deferred.reject(arguments); }); + + return deferred; }; self.onSettingsPreventRefresh = function() { diff --git a/src/octoprint/templates/overlays/reloadui.jinja2 b/src/octoprint/templates/overlays/reloadui.jinja2 index 8790002e..746c9129 100644 --- a/src/octoprint/templates/overlays/reloadui.jinja2 +++ b/src/octoprint/templates/overlays/reloadui.jinja2 @@ -4,9 +4,14 @@

{{ _('Please reload') }}

-

{{ _('There is a new version of the server active now, a reload of the user interface is needed. This will not interrupt any print jobs you might have ongoing. Please reload the web interface now by clicking the button below.') }}

+

{% trans %} + There is a new version of the server active now, a reload + of the user interface is needed. This will not interrupt + any print jobs you might have ongoing. Please reload the + web interface now by clicking the button below. + {% endtrans %}

- {{ _('Reload now') }} + {{ _('Reload now') }}