Merge commit '3bcef9d729885e1565505ced777c04c9c78543c0' into devel

This commit is contained in:
thelastWallE 2021-09-17 22:38:28 +02:00
commit 39b1b42ca1
28 changed files with 3968 additions and 1408 deletions

View File

@ -9,7 +9,9 @@ charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
max_line_length = 90
max_line_length = 180
indent_size = 2
[**.py]
indent_size = 4

4
.gitignore vendored
View File

@ -13,4 +13,6 @@ dist
.vscode/**
thunder-tests
vscode.env
vscode.env
.venv
OctoKlipper.egg-info

View File

@ -14,34 +14,38 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from __future__ import absolute_import, division, print_function, unicode_literals
import datetime
import logging
import octoprint.plugin
import octoprint.plugin.core
import glob
import os
import time
import sys
from octoprint.server import NO_CONTENT
from octoprint.util import is_hidden_path
from octoprint.util import get_formatted_size
from . import util, cfgUtils, logger
from octoprint.util.comm import parse_firmware_line
from octoprint.access.permissions import Permissions, ADMIN_GROUP, USER_GROUP
from octoprint.access.permissions import Permissions, ADMIN_GROUP
from .modules import KlipperLogAnalyzer
from octoprint.server.util.flask import restricted_access
import flask
from flask_babel import gettext
try:
import configparser
except ImportError:
import ConfigParser as configparser
if sys.version_info[0] < 3:
import StringIO
MAX_UPLOAD_SIZE = 5 * 1024 * 1024 # 5Mb
class KlipperPlugin(
octoprint.plugin.StartupPlugin,
octoprint.plugin.TemplatePlugin,
octoprint.plugin.SettingsPlugin,
octoprint.plugin.AssetPlugin,
octoprint.plugin.SimpleApiPlugin,
octoprint.plugin.EventHandlerPlugin):
octoprint.plugin.EventHandlerPlugin,
octoprint.plugin.BlueprintPlugin):
_parsing_response = False
_parsing_check_response = True
@ -74,8 +78,10 @@ class KlipperPlugin(
self._settings.global_set(
["serial", "additionalPorts"], additional_ports)
self._settings.save()
self.log_info(
"Added klipper serial port {} to list of additional ports.".format(klipper_port))
logger.log_info(
self,
"Added klipper serial port {} to list of additional ports.".format(klipper_port)
)
# -- Settings Plugin
@ -124,10 +130,11 @@ class KlipperPlugin(
),
configuration=dict(
debug_logging=False,
configpath="~/printer.cfg",
configpath="~/",
old_config="",
logpath="/tmp/klippy.log",
reload_command="RESTART",
restart_onsave=False,
shortStatus_navbar=True,
shortStatus_sidebar=True,
parse_check=False,
@ -135,79 +142,7 @@ class KlipperPlugin(
)
)
def on_settings_load(self):
data = octoprint.plugin.SettingsPlugin.on_settings_load(self)
configpath = os.path.expanduser(
self._settings.get(["configuration", "configpath"])
)
try:
f = open(configpath, "r")
data["config"] = f.read()
f.close()
except IOError:
self.log_error(
"Error: Klipper config file not found at: {}".format(
configpath)
)
else:
self.send_message("reload", "config", "", data["config"])
# send the configdata to frontend to update ace editor
return data
def on_settings_save(self, data):
self.log_debug(
"Save klipper configs"
)
if "config" in data:
if self.key_exist(data, "configuration", "parse_check"):
check_parse = data["configuration"]["parse_check"]
else:
check_parse = self._settings.get(["configuration", "parse_check"])
if sys.version_info[0] < 3:
data["config"] = data["config"].encode('utf-8')
# check for configpath if it was changed during changing of the configfile
if self.key_exist(data, "configuration", "configpath"):
configpath = os.path.expanduser(
data["configuration"]["configpath"]
)
else:
# if the configpath was not changed during changing the printer.cfg. Then the configpath would not be in data[]
configpath = os.path.expanduser(
self._settings.get(["configuration", "configpath"])
)
if self.file_exist(configpath) and (self._parsing_check_response or not check_parse):
try:
f = open(configpath, "w")
f.write(data["config"])
f.close()
self.log_debug("Writing Klipper config to {}".format(configpath))
except IOError:
self.log_error("Error: Couldn't write Klipper config file: {}".format(configpath))
else:
#load the reload command from changed data if it is not existing load the saved setting
if self.key_exist(data, "configuration", "reload_command"):
reload_command = os.path.expanduser(
data["configuration"]["reload_command"]
)
else:
reload_command = self._settings.get(["configuration", "reload_command"])
if reload_command != "manually":
# Restart klippy to reload config
self._printer.commands(reload_command)
self.log_info("Restarting Klipper.")
# we dont want to write the klipper conf to the octoprint settings
data.pop("config", None)
# save the rest of changed settings into config.yaml of octoprint
old_debug_logging = self._settings.get_boolean(["configuration", "debug_logging"])
octoprint.plugin.SettingsPlugin.on_settings_save(self, data)
@ -233,66 +168,88 @@ class KlipperPlugin(
)
def get_settings_version(self):
return 3
# Settings_Versionhistory:
# 3 = add shortstatus on navbar. migrate the navbar setting for this
# 4 = -change of configpath to only path without filename
# -add setting for restart checkbox on editor save
return 4
#migrate Settings
def on_settings_migrate(self, target, current):
settings = self._settings
if current is None:
settings = self._settings
self.migrate_old_settings(settings)
if settings.has(["serialport"]):
settings.set(["connection", "port"],
settings.get(["serialport"]))
settings.remove(["serialport"])
if current is not None and current < 3:
self.migrate_settings_configuration(
settings,
"shortStatus_navbar",
"navbar",
)
if settings.has(["replace_connection_panel"]):
settings.set(
["connection", "replace_connection_panel"],
settings.get(["replace_connection_panel"])
)
settings.remove(["replace_connection_panel"])
if current is not None and current < 4:
self.migrate_settings_configuration(
settings,
"old_config",
"temp_config",
)
if settings.has(["probeHeight"]):
settings.set(["probe", "height"],
settings.get(["probeHeight"]))
settings.remove(["probeHeight"])
cfg_path = settings.get(["configuration", "configpath"])
if cfg_path.find("printer.cfg") != -1:
new_cfg_path = cfg_path.replace("printer.cfg","")
logger.log_info(self, "migrate setting for 'configuration/configpath': " + cfg_path + " -> " + new_cfg_path)
settings.set(["configuration", "configpath"], new_cfg_path)
if settings.has(["probeLift"]):
settings.set(["probe", "lift"], settings.get(["probeLift"]))
settings.remove(["probeLift"])
if settings.get(["configuration", "reload_command"]) != "manually" :
logger.log_info(self, "migrate setting for 'configuration/restart_onsave': False -> True")
settings.set(["configuration", "restart_onsave"], True)
if settings.has(["probeSpeedXy"]):
settings.set(["probe", "speed_xy"],
settings.get(["probeSpeedXy"]))
settings.remove(["probeSpeedXy"])
if settings.has(["probeSpeedZ"]):
settings.set(["probe", "speed_z"],
settings.get(["probeSpeedZ"]))
settings.remove(["probeSpeedZ"])
def migrate_old_settings(self, settings):
'''
For Old settings
'''
self.migrate_settings(settings, "serialport", "connection", "port")
self.migrate_settings(settings, "replace_connection_panel", "connection", "replace_connection_panel")
self.migrate_settings(settings, "probeHeight", "probe", "height")
self.migrate_settings(settings, "probeLift", "probe", "lift")
self.migrate_settings(settings, "probeSpeedXy", "probe", "speed_xy")
self.migrate_settings(settings, "probeSpeedZ", "probe", "speed_z")
self.migrate_settings(settings, "configPath", "configuration", "configpath")
if settings.has(["probePoints"]):
points = settings.get(["probePoints"])
points_new = []
for p in points:
points_new.append(
dict(name="", x=int(p["x"]), y=int(p["y"]), z=0))
settings.set(["probe", "points"], points_new)
settings.remove(["probePoints"])
if settings.has(["probePoints"]):
points = settings.get(["probePoints"])
points_new = [dict(name="", x=int(p["x"]), y=int(p["y"]), z=0) for p in points]
settings.set(["probe", "points"], points_new)
settings.remove(["probePoints"])
if settings.has(["configPath"]):
self.log_info("migrate setting for: configPath")
settings.set(["config_path"], settings.get(["configPath"]))
settings.remove(["configPath"])
def migrate_settings(self, settings, old, new, new2="") -> None:
"""migrate setting to setting with additional group
Args:
settings (any): instance of self._settings
old (str): the old setting to migrate
new (str): group or only new setting if there is no new2
new2 (str, optional): the new setting to migrate to. Defaults to "".
""" ''''''
if settings.has([old]):
if new2 != "":
logger.log_info(self, "migrate setting for '" + old + "' -> '" + new + "/" + new2 + "'")
settings.set([new, new2], settings.get([old]))
else:
logger.log_info(self, "migrate setting for '" + old + "' -> '" + new + "'")
settings.set([new], settings.get([old]))
settings.remove([old])
def migrate_settings_configuration(self, settings, new, old):
if settings.has(["configuration", old]):
logger.log_info(self, "migrate setting for 'configuration/" + old + "' -> 'configuration/" + new + "'")
settings.set(["configuration", new], settings.get(["configuration", old]))
settings.remove(["configuration", old])
if target is 3 and current is 2:
settings = self._settings
if settings.has(["configuration", "navbar"]):
self.log_info("migrate setting for: configuration/navbar")
settings.set(["configuration", "shortStatus_navbar"], settings.get(["configuration", "navbar"]))
settings.remove(["configuration", "navbar"])
# -- Template Plugin
def get_template_configs(self):
return [
dict(type="navbar", custom_bindings=True),
@ -323,17 +280,29 @@ class KlipperPlugin(
custom_bindings=True
),
dict(type="sidebar",
custom_bindings=True,
icon="rocket",
replaces="connection" if self._settings.get_boolean(
["connection", "replace_connection_panel"]) else ""
),
custom_bindings=True,
icon="rocket",
replaces="connection" if self._settings.get_boolean(
["connection", "replace_connection_panel"]) else ""
),
dict(
type="generic",
name="Performance Graph",
template="klipper_graph_dialog.jinja2",
custom_bindings=True
),
dict(
type="generic",
name="Config Backups",
template="klipper_backups_dialog.jinja2",
custom_bindings=True
),
dict(
type="generic",
name="Config Editor",
template="klipper_editor.jinja2",
custom_bindings=True
),
dict(
type="generic",
name="Macro Dialog",
@ -342,6 +311,12 @@ class KlipperPlugin(
)
]
def get_template_vars(self):
return {
"max_upload_size": MAX_UPLOAD_SIZE,
"max_upload_size_str": get_formatted_size(MAX_UPLOAD_SIZE),
}
# -- Asset Plugin
def get_assets(self):
@ -352,27 +327,31 @@ class KlipperPlugin(
"js/klipper_pid_tuning.js",
"js/klipper_offset.js",
"js/klipper_param_macro.js",
"js/klipper_graph.js"
],
"js/klipper_graph.js",
"js/klipper_backup.js",
"js/klipper_editor.js"
],
clientjs=["clientjs/klipper.js"],
css=["css/klipper.css"]
)
# -- Event Handler Plugin
def on_event(self, event, payload):
if "UserLoggedIn" == event:
self.update_status("info", "Klipper: Standby")
if "Connecting" == event:
self.update_status("info", "Klipper: Connecting ...")
elif "Connected" == event:
self.update_status("info", "Klipper: Connected to host")
self.log_info(
if event == "UserLoggedIn":
util.update_status(self, "info", "Klipper: Standby")
if event == "Connecting":
util.update_status(self, "info", "Klipper: Connecting ...")
elif event == "Connected":
util.update_status(self, "info", "Klipper: Connected to host")
logger.log_info(
self,
"Connected to host via {} @{}bps".format(payload["port"], payload["baudrate"]))
elif "Disconnected" == event:
self.update_status("info", "Klipper: Disconnected from host")
elif "Error" == event:
self.update_status("error", "Klipper: Error")
self.log_error(payload["error"])
elif event == "Disconnected":
util.update_status(self, "info", "Klipper: Disconnected from host")
elif event == "Error":
util.update_status(self, "error", "Klipper: Error")
logger.log_error(self, payload["error"])
# -- GCODE Hook
@ -381,22 +360,22 @@ class KlipperPlugin(
if "FIRMWARE_VERSION" in line:
printerInfo = parse_firmware_line(line)
if "FIRMWARE_VERSION" in printerInfo:
self.log_info("Firmware version: {}".format(
logger.log_info(self, "Firmware version: {}".format(
printerInfo["FIRMWARE_VERSION"]))
elif "// probe" in line or "// Failed to verify BLTouch" in line:
msg = line.strip('/')
self.log_info(msg)
logger.log_info(self, msg)
self.write_parsing_response_buffer()
elif "//" in line:
# add lines with // to a buffer
self._message = self._message + line.strip('/')
if not self._parsing_response:
self.update_status("info", self._message)
util.update_status(self, "info", self._message)
self._parsing_response = True
elif "!!" in line:
msg = line.strip('!')
self.update_status("error", msg)
self.log_error(msg)
util.update_status(self, "error", msg)
logger.log_error(self, msg)
self.write_parsing_response_buffer()
else:
self.write_parsing_response_buffer()
@ -406,15 +385,13 @@ class KlipperPlugin(
# write buffer with // lines after a gcode response without //
if self._parsing_response:
self._parsing_response = False
self.log_info(self._message)
logger.log_info(self, self._message)
self._message = ""
def get_api_commands(self):
return dict(
listLogFiles=[],
getStats=["logFile"],
reloadConfig=[],
checkConfig=["config"]
getStats=["logFile"]
)
def on_api_command(self, command, data):
@ -423,57 +400,179 @@ class KlipperPlugin(
logpath = os.path.expanduser(
self._settings.get(["configuration", "logpath"])
)
if self.file_exist(logpath):
if util.file_exist(self, logpath):
for f in glob.glob(self._settings.get(["configuration", "logpath"]) + "*"):
filesize = os.path.getsize(f)
filemdate = time.strftime("%d.%m.%Y %H:%M",time.localtime(os.path.getctime(f)))
files.append(dict(
name=os.path.basename(
f) + " ({:.1f} KB)".format(filesize / 1000.0),
name=os.path.basename(f) + " (" + filemdate + ")",
file=f,
size=filesize
))
return flask.jsonify(data=files)
else:
return flask.jsonify(data=files)
return flask.jsonify(data=files)
elif command == "getStats":
if "logFile" in data:
log_analyzer = KlipperLogAnalyzer.KlipperLogAnalyzer(
data["logFile"])
return flask.jsonify(log_analyzer.analyze())
elif command == "reloadConfig":
data = octoprint.plugin.SettingsPlugin.on_settings_load(self)
configpath = os.path.expanduser(
self._settings.get(["configuration", "configpath"])
)
def is_blueprint_protected(self):
return False
def route_hook(self, server_routes, *args, **kwargs):
from octoprint.server.util.tornado import LargeResponseHandler, path_validation_factory
from octoprint.util import is_hidden_path
configpath = os.path.expanduser(
self._settings.get(["configuration", "configpath"])
)
bak_path = os.path.join(self.get_plugin_data_folder(), "configs", "")
return [
(r"/download/configs/(.*)", LargeResponseHandler, dict(path=configpath,
as_attachment=True,
path_validation=path_validation_factory(lambda path: not is_hidden_path(path),
status_code=404))),
(r"/download/backup/(.*)", LargeResponseHandler, dict(path=bak_path,
as_attachment=True,
path_validation=path_validation_factory(lambda path: not is_hidden_path(path),
status_code=404)))
]
# API for Backups
# Get Content of a Backupconfig
@octoprint.plugin.BlueprintPlugin.route("/backup/<filename>", methods=["GET"])
@restricted_access
@Permissions.PLUGIN_KLIPPER_CONFIG.require(403)
def get_backup(self, filename):
data_folder = self.get_plugin_data_folder()
full_path = os.path.realpath(os.path.join(data_folder, "configs", filename))
response = cfgUtils.get_cfg(self, full_path)
return flask.jsonify(response = response)
# Delete a Backupconfig
@octoprint.plugin.BlueprintPlugin.route("/backup/<filename>", methods=["DELETE"])
@restricted_access
@Permissions.PLUGIN_KLIPPER_CONFIG.require(403)
def delete_backup(self, filename):
data_folder = self.get_plugin_data_folder()
full_path = os.path.realpath(os.path.join(data_folder, "configs", filename))
if (
full_path.startswith(data_folder)
and os.path.exists(full_path)
and not is_hidden_path(full_path)
):
try:
f = open(configpath, "r")
data["config"] = f.read()
f.close()
except IOError:
self.log_error(
"Error: Klipper config file not found at: {}".format(
configpath)
)
else:
os.remove(full_path)
except Exception:
self._octoklipper_logger.exception("Could not delete {}".format(filename))
raise
return NO_CONTENT
# Get a list of all backuped configfiles
@octoprint.plugin.BlueprintPlugin.route("/backup/list", methods=["GET"])
@restricted_access
@Permissions.PLUGIN_KLIPPER_CONFIG.require(403)
def list_backups(self):
files = cfgUtils.list_cfg_files(self, "backup")
return flask.jsonify(files = files)
# restore a backuped configfile
@octoprint.plugin.BlueprintPlugin.route("/backup/restore/<filename>", methods=["GET"])
@restricted_access
@Permissions.PLUGIN_KLIPPER_CONFIG.require(403)
def restore_backup(self, filename):
configpath = os.path.expanduser(
self._settings.get(["configuration", "configpath"])
)
data_folder = self.get_plugin_data_folder()
backupfile = os.path.realpath(os.path.join(data_folder, "configs", filename))
return flask.jsonify(restored = cfgUtils.copy_cfg(self, backupfile, configpath))
# API for Configs
# Get Content of a Configfile
@octoprint.plugin.BlueprintPlugin.route("/config/<filename>", methods=["GET"])
@restricted_access
@Permissions.PLUGIN_KLIPPER_CONFIG.require(403)
def get_config(self, filename):
cfg_path = os.path.expanduser(
self._settings.get(["configuration", "configpath"])
)
full_path = os.path.realpath(os.path.join(cfg_path, filename))
response = cfgUtils.get_cfg(self, full_path)
return flask.jsonify(response = response)
# Delete a Configfile
@octoprint.plugin.BlueprintPlugin.route("/config/<filename>", methods=["DELETE"])
@restricted_access
@Permissions.PLUGIN_KLIPPER_CONFIG.require(403)
def delete_config(self, filename):
cfg_path = os.path.expanduser(
self._settings.get(["configuration", "configpath"])
)
full_path = os.path.realpath(os.path.join(cfg_path, filename))
if (
full_path.startswith(cfg_path)
and os.path.exists(full_path)
and not is_hidden_path(full_path)
):
try:
os.remove(full_path)
except Exception:
self._octoklipper_logger.exception("Could not delete {}".format(filename))
raise
return NO_CONTENT
# Get a list of all configfiles
@octoprint.plugin.BlueprintPlugin.route("/config/list", methods=["GET"])
@restricted_access
@Permissions.PLUGIN_KLIPPER_CONFIG.require(403)
def list_configs(self):
files = cfgUtils.list_cfg_files(self, "")
return flask.jsonify(files = files, max_upload_size = MAX_UPLOAD_SIZE)
# check syntax of a given data
@octoprint.plugin.BlueprintPlugin.route("/config/check", methods=["POST"])
@restricted_access
@Permissions.PLUGIN_KLIPPER_CONFIG.require(403)
def check_config(self):
data = flask.request.json
data_to_check = data.get("DataToCheck", [])
response = cfgUtils.check_cfg(self, data_to_check)
return flask.jsonify(is_syntax_ok = response)
# save a configfile
@octoprint.plugin.BlueprintPlugin.route("/config/save", methods=["POST"])
@restricted_access
@Permissions.PLUGIN_KLIPPER_CONFIG.require(403)
def save_config(self):
data = flask.request.json
filename = data.get("filename", [])
if filename == []:
flask.abort(
400,
description="Invalid request, the filename is not set",
)
Filecontent = data.get("DataToSave", [])
saved = cfgUtils.save_cfg(self, Filecontent, filename)
if saved == True:
util.send_message(self, "reload", "configlist", "", "")
return flask.jsonify(saved = saved)
# restart klipper
@octoprint.plugin.BlueprintPlugin.route("/restart", methods=["POST"])
@restricted_access
@Permissions.PLUGIN_KLIPPER_CONFIG.require(403)
def restart_klipper(self):
reload_command = self._settings.get(["configuration", "reload_command"])
if reload_command != "manually":
# Restart klippy to reload config
self._printer.commands(reload_command)
logger.log_info(self, "Restarting Klipper.")
return NO_CONTENT
# APIs end
self._settings.set(["config"], data["config"])
# self.send_message("reload", "config", "", data["config"])
# send the configdata to frontend to update ace editor
if sys.version_info[0] < 3:
data["config"] = data["config"].decode('utf-8')
return flask.jsonify(data=data["config"])
elif command == "checkConfig":
if "config" in data:
if not self.validate_configfile(data["config"]):
self.log_debug("validateConfig not ok")
self._settings.set(["configuration", "old_config"], data["config"])
return flask.jsonify(checkConfig="not OK")
else:
self.log_debug("validateConfig ok")
self._settings.set(["configuration", "old_config"], "")
return flask.jsonify(checkConfig="OK")
def get_update_information(self):
return dict(
@ -499,128 +598,25 @@ class KlipperPlugin(
)
)
#-- Helpers
def send_message(self, type, subtype, title, payload):
self._plugin_manager.send_plugin_message(
self._identifier,
dict(
time=datetime.datetime.now().strftime("%H:%M:%S"),
type=type,
subtype=subtype,
title=title,
payload=payload
)
)
def poll_status(self):
self._printer.commands("STATUS")
def update_status(self, type, status):
self.send_message("status", type, status, status)
def log_info(self, message):
self._octoklipper_logger.info(message)
self.send_message("log", "info", message, message)
def log_debug(self, message):
self._octoklipper_logger.debug(message)
self._logger.info(message)
# sends a message to frontend(in klipper.js -> self.onDataUpdaterPluginMessage) and write it to the console.
# _mtype, subtype=debug/info, title of message, message)
self.send_message("console", "debug", message, message)
def log_error(self, error):
self._octoklipper_logger.error(error)
self._logger.info(error)
self.send_message("log", "error", error, error)
def file_exist(self, filepath):
if not os.path.isfile(filepath):
self.send_message("PopUp", "warning", "OctoKlipper Settings",
"Klipper " + filepath + " does not exist!")
return False
else:
return True
def key_exist(self, dict, key1, key2):
try:
dict[key1][key2]
except KeyError:
return False
else:
return True
def validate_configfile(self, dataToBeValidated):
"""
--->SyntaxCheck for a given data<----
"""
try:
dataToValidated = configparser.RawConfigParser(strict=False)
#
if sys.version_info[0] < 3:
buf = StringIO.StringIO(dataToBeValidated)
dataToValidated.readfp(buf)
else:
dataToValidated.read_string(dataToBeValidated)
sections_search_list = ["bltouch",
"probe"]
value_search_list = [ "x_offset",
"y_offset",
"z_offset"]
try:
# cycle through sections and then values
for y in sections_search_list:
for x in value_search_list:
if dataToValidated.has_option(y, x):
a_float = dataToValidated.getfloat(y, x)
except ValueError as error:
self.log_error(
"Error: Invalid Value for <b>"+x+"</b> in Section: <b>"+y+"</b>\n" +
"{}".format(str(error))
)
self.send_message("PopUp", "warning", "OctoKlipper: Invalid Config\n",
"Config got not saved!\n" +
"You can reload your last changes\n" +
"on the 'Klipper Configuration' tab.\n\n" +
"Invalid Value for <b>"+x+"</b> in Section: <b>"+y+"</b>\n" + "{}".format(str(error)))
self._parsing_check_response = False
return False
except configparser.Error as error:
if sys.version_info[0] < 3:
error.message = error.message.replace("\\n","")
error.message = error.message.replace("file: u","Klipper Configuration", 1)
error.message = error.message.replace("'","", 2)
error.message = error.message.replace("u'","'", 1)
else:
error.message = error.message.replace("\\n","")
error.message = error.message.replace("file:","Klipper Configuration", 1)
error.message = error.message.replace("'","", 2)
self.log_error(
"Error: Invalid Klipper config file:\n" +
"{}".format(str(error))
)
self.send_message("PopUp", "warning", "OctoKlipper: Invalid Config data\n",
"Config got not saved!\n" +
"You can reload your last changes\n" +
"on the 'Klipper Configuration' tab.\n\n" + str(error))
self._parsing_check_response = False
return False
else:
self._parsing_check_response = True
return True
__plugin_name__ = "OctoKlipper"
__plugin_pythoncompat__ = ">=2.7,<4"
__plugin_settings_overlay__ = {
'system': {
'actions': [{
'action': 'octoklipper_restart',
'command': 'sudo service klipper restart',
'name': gettext('Restart Klipper'),
'confirm': '<h3><center><b>' + gettext("You are about to restart Klipper!") + '<br>' + gettext("This will stop ongoing prints!") + '</b></center></h3><br>Command = "sudo service klipper restart"'
}]
}
}
def __plugin_load__():
global __plugin_implementation__
global __plugin_hooks__
__plugin_implementation__ = KlipperPlugin()
__plugin_hooks__ = {
"octoprint.server.http.routes": __plugin_implementation__.route_hook,
"octoprint.access.permissions": __plugin_implementation__.get_additional_permissions,
"octoprint.comm.protocol.gcode.received": __plugin_implementation__.on_parse_gcode,
"octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information

View File

@ -0,0 +1,297 @@
from __future__ import absolute_import, division, print_function, unicode_literals
import glob
import os, time, sys
import flask
from . import util, logger
from flask_babel import gettext
from shutil import copy, copyfile
try:
import configparser
except ImportError:
import ConfigParser as configparser
if sys.version_info[0] < 3:
import StringIO
def list_cfg_files(self, path: str) -> list:
"""Generate list of config files.
Args:
path (str): Path to the config files.
Returns:
list: for every file a dict with keys for name, file, size, mdate, url.
"""
files = []
if path=="backup":
cfg_path = os.path.join(self.get_plugin_data_folder(), "configs", "*")
else:
cfg_path = os.path.expanduser(
self._settings.get(["configuration", "configpath"])
)
cfg_path = os.path.join(cfg_path, "*.cfg")
cfg_files = glob.glob(cfg_path)
logger.log_debug(self, "list_cfg_files Path: " + cfg_path)
for f in cfg_files:
filesize = os.path.getsize(f)
filemdate = time.localtime(os.path.getmtime(f))
if path != "backup":
url = flask.url_for("index") + "plugin/klipper/download/configs/" + os.path.basename(f)
else:
url = flask.url_for("index") + "plugin/klipper/download/backup/" + os.path.basename(f)
files.append(dict(
name= os.path.basename(f),
file= f,
size= " ({:.1f} KB)".format(filesize / 1000.0),
mdate= time.strftime("%d.%m.%Y %H:%M", filemdate),
url= url,
))
logger.log_debug(self, "list_cfg_files " + str(len(files)) + ": " + f)
return files
def get_cfg(self, file):
"""Get the content of a configuration file.
Args:
file (str): The name of the file to read
Returns:
dict:
config (str): The configuration of the file
text (str): The text of the error
"""
response = {"config":"",
"text": ""}
if not file:
cfg_path = os.path.expanduser(
self._settings.get(["configuration", "configpath"])
)
file = os.path.join(cfg_path, "printer.cfg")
if util.file_exist(self, file):
logger.log_debug(self, "get_cfg_files Path: " + file)
try:
with open(file, "r") as f:
response['config'] = f.read()
except IOError as Err:
logger.log_error(
self,
"Error: Klipper config file not found at: {}".format(file)
+ "\n IOError: {}".format(Err)
)
response['text'] = Err
return response
else:
if sys.version_info[0] < 3:
response['config'] = response.config.decode('utf-8')
return response
finally:
f.close()
else:
response['text'] = gettext("File not found!")
return response
def save_cfg(self, content, filename="printer.cfg"):
"""Save the configuration file to given file.
Args:
content (str): The content of the configuration.
filename (str): The filename of the configuration file. Default is "printer.cfg"
Returns:
bool: True if the configuration file was saved successfully. Otherwise False
"""
logger.log_debug(
self,
"Save klipper config"
)
if sys.version_info[0] < 3:
content = content.encode('utf-8')
configpath = os.path.expanduser(self._settings.get(["configuration", "configpath"]))
if filename[-4:] != ".cfg":
filename += ".cfg"
filepath = os.path.join(configpath, filename)
logger.log_debug(self, "save filepath: {}".format(filepath))
self._settings.set(["configuration", "temp_config"], content)
check_parse = self._settings.get(["configuration", "parse_check"])
logger.log_debug(self, "check_parse on filesave: {}".format(check_parse))
if check_parse and not check_cfg(self, content):
return False
try:
logger.log_debug(self, "Writing Klipper config to {}".format(filepath))
with open(filepath, "w") as f:
f.write(content)
except IOError:
logger.log_error(self, "Error: Couldn't open Klipper config file: {}".format(filepath))
return False
else:
logger.log_debug(self, "Writen Klipper config to {}".format(filepath))
return True
finally:
f.close()
copy_cfg_to_backup(self, filepath)
def check_cfg(self, data):
"""Checks the given data on parsing errors.
Args:
data (str): Content to be validated.
Returns:
bool: True if the data is valid. False if it is not.
"""
try:
dataToValidated = configparser.RawConfigParser(strict=False)
if sys.version_info[0] < 3:
import StringIO
buf = StringIO.StringIO(data)
dataToValidated.readfp(buf)
else:
dataToValidated.read_string(data)
except configparser.Error as error:
show_error_message(self, error)
logger.log_debug(self, 'check_cfg: NOK!')
return False
else:
if not is_float_ok(self, dataToValidated):
logger.log_debug(self, "check_cfg: NOK!")
return False
logger.log_debug(self, "check_cfg: OK")
return True
def show_error_message(self, error):
error.message = error.message.replace('\\n', '')
if sys.version_info[0] < 3:
error.message = error.message.replace('file: u', 'Klipper Configuration', 1)
error.message = error.message.replace("'", '', 2)
error.message = error.message.replace("u'", "'", 1)
else:
error.message = error.message.replace('file:', 'Klipper Configuration', 1)
error.message = error.message.replace("'", '', 2)
logger.log_error(
self,
('Error: Invalid Klipper config file:\n' + '{}'.format(str(error))),
)
util.send_message(
self, 'PopUp', 'warning', 'Invalid Config data\n', ('\n' + str(error))
)
def is_float_ok(self, dataToValidated):
sections_search_list = [
"bltouch",
"probe"
]
value_search_list = [
"x_offset",
"y_offset",
"z_offset"
]
try:
# cycle through sections and then values
for y in sections_search_list:
for x in value_search_list:
if dataToValidated.has_option(y, x):
a_float = dataToValidated.getfloat(y, x)
except ValueError as error:
logger.log_error(
self,
"Error: Invalid Value for <b>" + x + "</b> in Section: <b>" + y + "</b>\n"
+ "{}".format(str(error))
)
util.send_message(
self,
"PopUp",
"warning",
"Invalid Config data\n",
"\n"
+ "Invalid Value for <b>" + x + "</b> in Section: <b>" + y + "</b>\n"
+ "{}".format(str(error))
)
return False
else:
return True
def copy_cfg(self, file, dst):
"""Copy the config file to the destination.
Args:
file (str): Filepath of the config file to copy.
dst (str): Path to copy the config file to.
Returns:
bool: True if the copy succeeded, False otherwise.
"""
if os.path.isfile(file):
try:
copy(file, dst)
except IOError:
logger.log_error(
self,
"Error: Klipper config file not found at: {}".format(file)
)
return False
else:
logger.log_debug(
self,
"File copied: "
+ file
)
return True
return False
def copy_cfg_to_backup(self, src):
"""Copy the config file to backup directory of OctoKlipper.
Args:
src (str): Path to the config file to copy.
Returns:
bool: True if the config file was copied successfully. False otherwise.
"""
if not os.path.isfile(src):
return False
cfg_path = os.path.join(self.get_plugin_data_folder(), "configs", "")
filename = os.path.basename(src)
if not os.path.exists(cfg_path):
try:
os.mkdir(cfg_path)
except OSError:
logger.log_error(self, "Error: Creation of the backup directory {} failed".format(cfg_path))
return False
else:
logger.log_debug(self, "Directory {} created".format(cfg_path))
dst = os.path.join(cfg_path, filename)
logger.log_debug(self, "copy_cfg_to_backup:" + src + " to " + dst)
if src == dst:
return False
try:
copyfile(src, dst)
except IOError:
logger.log_error(
self,
"Error: Couldn't copy Klipper config file to {}".format(dst)
)
return False
else:
logger.log_debug(self, "CfgBackup " + dst + " writen")
return True

View File

@ -0,0 +1,17 @@
from . import util
def log_info(self, message):
self._octoklipper_logger.info(message)
util.send_message(self, "log", "info", message, message)
def log_debug(self, message):
self._octoklipper_logger.debug(message)
self._logger.info(message)
# sends a message to frontend(in klipper.js -> self.onDataUpdaterPluginMessage) and write it to the console.
# _mtype, subtype=debug/info, title of message, message)
util.send_message(self, "console", "debug", message, message)
def log_error(self, error):
self._octoklipper_logger.error(error)
self._logger.error(error)
util.send_message(self, "log", "error", error, error)

View File

@ -15,6 +15,7 @@
import flask
import optparse, datetime
from .. import logger
class KlipperLogAnalyzer():
MAXBANDWIDTH=25000.
@ -81,6 +82,7 @@ class KlipperLogAnalyzer():
out.append(keyparts)
f.close()
except IOError:
logger.log_error(self, "Couldn't open log file: {}".format(logname))
print("Couldn't open log file")
return out

View File

@ -0,0 +1,76 @@
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(["OctoPrintClient"], factory);
} else {
factory(global.OctoPrintClient);
}
})(this, function (OctoPrintClient) {
var OctoKlipperClient = function (base) {
this.base = base;
this.url = this.base.getBlueprintUrl("klipper");
};
OctoKlipperClient.prototype.restartKlipper = function (opts) {
return this.base.post(this.url + "restart", opts);
};
OctoKlipperClient.prototype.getCfg = function (config, opts) {
return this.base.get(this.url + "config/" + config, opts);
};
OctoKlipperClient.prototype.getCfgBak = function (backup, opts) {
return this.base.get(this.url + "backup/" + backup, opts);
};
OctoKlipperClient.prototype.listCfg = function (opts) {
return this.base.get(this.url + "config/list", opts);
};
OctoKlipperClient.prototype.listCfgBak = function (opts) {
return this.base.get(this.url + "backup/list", opts);
};
OctoKlipperClient.prototype.checkCfg = function (content, opts) {
content = content || [];
var data = {
DataToCheck: content,
};
return this.base.postJson(this.url + "config/check", data, opts);
};
OctoKlipperClient.prototype.saveCfg = function (content, filename, opts) {
content = content || [];
filename = filename || [];
var data = {
DataToSave: content,
filename: filename,
};
return this.base.postJson(this.url + "config/save", data, opts);
};
OctoKlipperClient.prototype.deleteCfg = function (config, opts) {
return this.base.delete(this.url + "config/" + config, opts);
};
OctoKlipperClient.prototype.deleteBackup = function (backup, opts) {
return this.base.delete(this.url + "backup/" + backup, opts);
};
OctoKlipperClient.prototype.restoreBackup = function (backup, opts) {
return this.base.get(this.url + "backup/restore/" + backup, opts);
};
OctoKlipperClient.prototype.restoreBackupFromUpload = function (file, data) {
data = data || {};
var filename = data.filename || undefined;
return this.base.upload(this.url + "restore", file, filename, data);
};
OctoPrintClient.registerPluginComponent("klipper", OctoKlipperClient);
return OctoKlipperClient;
});

View File

@ -1,238 +1,388 @@
.plugin-klipper-sidebar {
padding: 1px;
height: auto;
border: 1px solid #aaa;
width: 98%;
text-align: center;
word-break: break-all;
margin: auto;
padding: 1px;
height: auto;
border: 1px solid #aaa;
width: 98%;
text-align: center;
word-break: break-all;
margin: auto;
}
li#navbar_plugin_klipper {
cursor: pointer;
max-width:360px;
max-height:80px;
word-break: break-all;
cursor: pointer;
max-width: 360px;
max-height: 80px;
word-break: break-all;
}
.plugin-klipper-sidebar a {
padding: 2px 2px;
text-align: center;
text-decoration: none;
display: inline-block;
cursor: pointer;
padding: 2px 2px;
text-align: center;
text-decoration: none;
display: inline-block;
cursor: pointer;
}
.plugin-klipper-sidebar a:hover, .plugin-klipper-sidebar a:active {
cursor: pointer;
}
.plugin-klipper-sidebar a:hover,
.plugin-klipper-sidebar a:active {
cursor: pointer;
}
.plugin-klipper-log {
padding: 0px;
overflow-y: scroll;
height: 400px;
border: 1px solid #eee;
width: 100%;
word-break: break-all;
padding: 0px;
overflow-y: scroll;
height: 400px;
border: 1px solid #eee;
width: 100%;
word-break: break-all;
}
.plugin-klipper-log .log-item {
margin: 3px auto 0 auto;
border: 1px solid #ddd;
border-radius: 3px;
background-color: #efefef;
color: #333;
margin: 3px auto 0 auto;
border: 1px solid #ddd;
border-radius: 3px;
background-color: #efefef;
color: #333;
}
.plugin-klipper-log .error {
background-color: #eebabb;
background-color: #eebabb;
}
.plugin-klipper-log .log-item .ts {
display: inline-block;
width: 13%;
height: 100%;
vertical-align: top;
font-size: 0.8em;
padding: 0 0 0 5px;
display: inline-block;
width: 13%;
height: 100%;
vertical-align: top;
font-size: 0.8em;
padding: 0 0 0 5px;
}
.plugin-klipper-log .log-item .msg {
display: inline-block;
width: 84%;
height: 100%;
display: inline-block;
width: 84%;
height: 100%;
}
.clear-btn {
margin-top: 6px;
margin-bottom: 6px;
margin-top: 6px;
margin-bottom: 6px;
}
#level .controls {
padding: 1px;
padding: 1px;
}
ul#klipper-settings {
margin: 0;
margin: 0;
}
#klipper-settings a{
margin: 5px;
#klipper-settings a {
margin: 5px;
}
#tab_plugin_klipper_main .row-fluid {
display: flex;
flex: row wrap;
align-items: stretch;
display: flex;
flex-flow: row wrap;
align-items: stretch;
}
@media all and (max-width: 940px) {
#tab_plugin_klipper_main .row-fluid {
/* On small screens, we are no longer using row direction but column */
flex-direction: column;
}
}
#tab_plugin_klipper_main .row-fluid {
/* On small screens, we are no longer using row direction but column */
flex-direction: column;
}
}
#tab_plugin_klipper_main #left-side {
flex: 3 1;
padding-right: 10px;
flex: 3 1;
padding-right: 10px;
}
#tab_plugin_klipper_main .span8 label {
float: left;
float: left;
}
#tab_plugin_klipper_main #right-side {
flex: 1 1;
max-width: 200px;
min-width: 100px;
flex: 1 1;
max-width: 200px;
min-width: 100px;
}
.klipper-row-fluid {
display: flex;
flex-flow: row wrap;
align-items: stretch;
}
.klipper-column-fluid {
display: flex;
flex-flow: column nowrap;
align-items: stretch;
}
.klipper-fluid-item-1 {
flex: 1 auto;
}
.klipper-fluid-item-2 {
flex: 2 auto;
}
.klipper-fluid-item-3 {
flex: 3 auto;
}
.gap {
justify-content: space-evenly;
}
@media all and (max-width: 940px) {
.klipper-row-fluid {
/* On small screens, we are no longer using row direction but column */
flex-direction: column;
}
}
#settings_plugin_klipper {
height: 100%;
height: 100%;
height: -webkit-fill-available;
}
div#klipper_backups_dialog {
display: flex;
flex-flow: column;
min-height: 400px;
}
div#klipper_backups_dialog div.modal-body {
min-height: 350px;
display: flex;
flex-flow: column;
flex-grow: 1;
}
div#klipper_backups_dialog .editor-controls {
flex: 0 auto;
display: flex;
align-items: stretch;
flex-flow: row wrap;
}
div#klipper_backups_dialog div.modal-body .textarea {
overflow: auto;
}
div#klipper_backups_dialog div.modal-body textarea {
margin-bottom: 0px !important;
padding-left: 0px !important;
padding-right: 0px !important;
box-sizing: border-box;
resize: none;
width: 100%;
}
/* UIcustomizer fix */
body.UICResponsiveMode #settings_dialog_content {
height: calc(100% - 60px);
margin-right: -18px;
margin-top: 50px;
width: calc(100% - 15px);
}
div#settings_plugin_klipper form {
margin: 0px;
height: 100%;
margin: 0px;
height: 100%;
}
div#settings_plugin_klipper form .tab-content {
height: calc(100% - 40px);
overflow: auto;
div#settings_plugin_klipper div.tab-content {
height: calc(100% - 58px);
overflow: auto;
}
div#settings_plugin_klipper div.tab-content .border{
border-bottom: 1px solid;
}
div#settings_plugin_klipper div.tab-footer {
height: 20px;
width: 100%;
top: 10px;
position: relative;
border-top: 1px solid #eee;
padding-top: 3px;
}
div#settings_plugin_klipper div.tab-content div#conf.tab-pane {
height: 100%;
height: 100%;
min-height: 200px;
width: 100%;
}
div#settings_plugin_klipper div.tab-content div#conf.tab-pane div.control-group {
height: 100%;
margin: 0;
display: flex;
flex-direction: column;
height: 100%;
margin: 0;
display: flex;
flex-direction: column;
}
div#settings_plugin_klipper div.tab-content div#conf.tab-pane div.control-group div.editor-controls{
margin-bottom: 0px;
height: 26px;
.klipper-settings-tab {
height: 100%;
width: 100%;
}
div#settings_plugin_klipper div.tab-content div#conf.tab-pane div.control-group div.conf-editor {
height: 95%;
height: calc(100% - 28px);
width: 99%;
width: calc(100% - 4px);
padding-top: 2px;
flex: 1 1;
overflow: auto;
#settings_plugin_klipper .m-0 {
margin: 0;
}
div#settings_plugin_klipper div.tab-content div#conf.tab-pane div.control-group div.conf-editor div#plugin-klipper-config {
font-family: monospace;
overflow: auto;
height: 100%;
height: -webkit-fill-available;
#settings_plugin_klipper .scroll-y {
overflow-y: scroll;
}
div#settings_plugin_klipper.tab-pane.active form.form-horizontal div.tab-content div#conf.tab-pane.active button.btn.btn-small {
width: 30%;
display: inline-block;
margin: 0px 2px 2px 2px;
#settings_plugin_klipper table {
table-layout: unset !important;
}
#settings_plugin_klipper .table-fixed thead th {
position: sticky;
top: 0;
background-color: rgba(12, 5, 5, 0.85);
color: rgb(200, 200, 200);
}
#settings_plugin_klipper .pagination-mini {
margin: 5;
}
@media (max-width: 979px) {
/* div#klipper_editor.modal {
height: unset !important;
} */
div#klipper_editor .modal-footer .btn {
margin-bottom: 1px;
margin-left: 5px;
}
}
.octoprint-container.UICMainCont.container-fluid {
margin-top:0px !important;
}
div#klipper_editor .modal-header div button.btn + button.btn {
margin-right: 12px;
}
div#klipper_editor .modal-body {
overflow: auto;
}
.klipper-btn-group {
display: inline-block;
}
div#klipper_editor .modal-footer input[type="text"] {
margin-bottom: 0px !important;
}
div#klipper_editor .modal-footer .editor-controls {
flex: 0 auto;
display: flex;
align-items: center;
}
div#klipper_editor div.conf-editor {
width: 99%;
width: calc(100% - 4px);
margin-top: 5px;
overflow: auto;
}
div#klipper_editor div.conf-editor div#plugin-klipper-config {
font-family: monospace;
overflow: auto;
width: 100%;
margin: auto;
}
/*checkboxes*/
div#settings_plugin_klipper.tab-pane.active form.form-horizontal div.tab-content div.tab-pane.active input.inline-checkbox {
vertical-align: -0.2em;
vertical-align: -0.2em;
}
div#settings_plugin_klipper.tab-pane.active form.form-horizontal div.tab-content div.tab-pane.active label.inline {
display: inline;
div#settings_plugin_klipper.tab-pane.active form.form-horizontal div.tab-content div.tab-pane.active label.inline,
div#klipper_editor .inline {
display: inline;
}
div#settings_plugin_klipper.tab-pane.active form.form-horizontal div.tab-content div.tab-pane.active div.controls input.controls-checkbox {
margin-top: 8px;
margin-top: 8px;
}
/*macros*/
div#settings_plugin_klipper.tab-pane.active form.form-horizontal div.tab-content div#macros.tab-pane.active div div#item.control-group label.control-label {
width: 80px;
div#settings_plugin_klipper div#macros label.control-label {
width: 80px;
}
#macros #item.control-group {
margin-bottom: 2px;
border: 2px solid #ccc;
border-radius: 3px;
background-color: #eeeeee;
color: #333;
padding-bottom: 2px;
padding-top: 2px;
margin-bottom: 2px;
border: 2px solid #ccc;
border-radius: 3px;
background-color: #eeeeee;
color: #333;
padding-bottom: 2px;
padding-top: 2px;
}
#klipper_graph_dialog {
width: 1050px;
width: 90%;
}
#klipper_graph_dialog .full-sized-box{
width: 1000px;
margin: 0 auto;
#klipper_graph_dialog .full-sized-box {
width: 100%;
margin: 0 auto;
}
#klipper_graph_dialog form {
margin: 0;
margin: 0;
}
#klipper_graph_dialog select {
width: auto;
width: auto;
}
#klipper_graph_dialog .graph-footer {
bottom:0;
bottom: 0;
}
#klipper_graph_dialog input {
display: inline-block;
display: inline-block;
}
#klipper_graph_dialog .status-label {
display: block;
position: absolute;
margin: 5px 0 0 10px;
display: block;
position: absolute;
margin: 5px 0 0 10px;
}
#klipper_graph_dialog .fill-checkbox {
display: block;
position: absolute;
top: 0%;
left: 50%;
display: block;
position: absolute;
top: 0%;
left: 50%;
}
#klipper_graph_dialog .help-inline {
display: block;
position: absolute;
top: 0px;
display: block;
position: absolute;
top: 0px;
}
#klipper_graph_canvas {
margin-top: 15px;
margin-top: 15px;
}

View File

@ -14,251 +14,274 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
$(function () {
function KlipperViewModel(parameters) {
var self = this;
function KlipperViewModel(parameters) {
var self = this;
self.header = OctoPrint.getRequestHeaders({
"content-type": "application/json",
"cache-control": "no-cache"
});
self.header = OctoPrint.getRequestHeaders({
"content-type": "application/json",
"cache-control": "no-cache",
});
self.apiUrl = OctoPrint.getSimpleApiUrl("klipper");
self.apiUrl = OctoPrint.getSimpleApiUrl("klipper");
self.Url = OctoPrint.getBlueprintUrl("klipper");
self.settings = parameters[0];
self.loginState = parameters[1];
self.connectionState = parameters[2];
self.levelingViewModel = parameters[3];
self.paramMacroViewModel = parameters[4];
self.access = parameters[5];
self.settings = parameters[0];
self.loginState = parameters[1];
self.connectionState = parameters[2];
self.levelingViewModel = parameters[3];
self.paramMacroViewModel = parameters[4];
self.access = parameters[5];
self.shortStatus_navbar = ko.observable();
self.shortStatus_sidebar = ko.observable();
self.logMessages = ko.observableArray();
self.shortStatus_navbar = ko.observable();
self.shortStatus_sidebar = ko.observable();
self.logMessages = ko.observableArray();
self.showPopUp = function(popupType, popupTitle, message){
var title = popupType.toUpperCase() + ": " + popupTitle;
new PNotify({
title: title,
text: message,
type: popupType,
hide: false
});
};
self.showPopUp = function (popupType, popupTitle, message) {
var title = "OctoKlipper: <br />" + popupTitle + "<br />";
var hide = false;
if (popupType == "success") {
hide = true
}
new PNotify({
title: title,
text: message,
type: popupType,
hide: hide,
icon: true
});
};
self.onSettingsShown = function () {
self.reloadConfig();
}
self.showLevelingDialog = function () {
var dialog = $("#klipper_leveling_dialog");
dialog.modal({
show: "true",
backdrop: "static",
keyboard: false
});
self.levelingViewModel.initView();
};
self.showPidTuningDialog = function () {
var dialog = $("#klipper_pid_tuning_dialog");
dialog.modal({
show: "true",
backdrop: "static",
keyboard: false
});
};
self.showOffsetDialog = function () {
var dialog = $("#klipper_offset_dialog");
dialog.modal({
show: "true",
backdrop: "static"
});
};
self.showGraphDialog = function () {
var dialog = $("#klipper_graph_dialog");
dialog.modal({
show: "true",
minHeight: "500px",
maxHeight: "600px"
});
};
self.executeMacro = function (macro) {
var paramObjRegex = /{(.*?)}/g;
if (!self.hasRight("MACRO")) return;
if (macro.macro().match(paramObjRegex) == null) {
OctoPrint.control.sendGcode(
// Use .split to create an array of strings which is sent to
// OctoPrint.control.sendGcode instead of a single string.
macro.macro().split(/\r\n|\r|\n/)
);
} else {
self.paramMacroViewModel.process(macro);
var dialog = $("#klipper_macro_dialog");
dialog.modal({
show: "true",
backdrop: "static"
});
}
};
self.navbarClicked = function () {
$("#tab_plugin_klipper_main_link").find("a").click();
};
self.onGetStatus = function () {
OctoPrint.control.sendGcode("Status");
};
self.onRestartFirmware = function () {
OctoPrint.control.sendGcode("FIRMWARE_RESTART");
};
self.onRestartHost = function () {
OctoPrint.control.sendGcode("RESTART");
};
self.onAfterBinding = function () {
self.connectionState.selectedPort(
self.settings.settings.plugins.klipper.connection.port()
);
};
self.onDataUpdaterPluginMessage = function(plugin, data) {
if(plugin == "klipper") {
switch(data.type) {
case "PopUp":
self.showPopUp(data.subtype, data.title, data.payload);
break;
case "reload":
break;
case "console":
self.consoleMessage(data.subtype, data.payload);
break;
case "status":
if (data.payload.length > 36) {
var shortText = data.payload.substring(0, 31) + " [..]"
self.shortStatus_navbar(shortText);
} else {
self.shortStatus_navbar(data.payload);
}
self.shortStatus_sidebar(data.payload);
break;
default:
self.logMessage(data.time, data.subtype, data.payload);
self.consoleMessage(data.subtype, data.payload);
}
//if ("warningPopUp" == data.type){
// self.showPopUp(data.subtype, data.title, data.payload);
// return;
//} else if ("errorPopUp" == data.type){
// self.showPopUp(data.subtype, data.title, data.payload);
// return;
//} else if ("reload" == data.type){
// return;
//} else if ("console" == data.type) {
// self.consoleMessage(data.subtype, data.payload);
//} else if (data.type == "status") {
// self.shortStatus(data.payload);
//} else {
// self.logMessage(data.time, data.subtype, data.payload);
//}
}
};
self.logMessage = function (timestamp, type="info", message) {
if (!timestamp) {
var today = new Date();
var timestamp = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
}
self.logMessages.push({
time: timestamp,
type: type,
msg: message.replace(/\n/gi, "<br>")
});
};
self.consoleMessage = function (type, message) {
if (self.settings.settings.plugins.klipper.configuration.debug_logging() === true) {
if (type == "info"){
console.info("OctoKlipper : " + message);
} else if (type == "debug"){
console.debug("OctoKlipper : " + message);
} else {
console.error("OctoKlipper : " + message);
}
}
return
};
self.reloadConfig = function() {
var settings = {
"crossDomain": true,
"url": self.apiUrl,
"method": "POST",
"headers": self.header,
"processData": false,
"dataType": "json",
"data": JSON.stringify({command: "reloadConfig"})
}
$.ajax(settings).done(function (response) {
self.consoleMessage(
"debug",
"Reloaded config file from Backend");
});
}
self.onClearLog = function () {
self.logMessages.removeAll();
};
self.isActive = function () {
return self.connectionState.isOperational();
};
self.hasRight = function (right_role, type) {
var arg = eval("self.access.permissions.PLUGIN_KLIPPER_" + right_role);
if (type == "Ko") {
return self.loginState.hasPermissionKo(arg);
}
return self.loginState.hasPermission(arg);
};
// OctoKlipper settings link
self.openOctoKlipperSettings = function (profile_type) {
if (!self.hasRight("CONFIG")) return;
$("a#navbar_show_settings").click();
$("li#settings_plugin_klipper_link a").click();
if (profile_type) {
var query = "#klipper-settings a[data-profile-type='" + profile_type + "']";
$(query).click();
}
};
self.showEditorDialog = function () {
if (!self.hasRight("CONFIG")) return;
var editorDialog = $("#klipper_editor");
editorDialog.modal({
show: "true",
width: "90%",
backdrop: "static",
});
}
OCTOPRINT_VIEWMODELS.push({
construct: KlipperViewModel,
dependencies: [
"settingsViewModel",
"loginStateViewModel",
"connectionViewModel",
"klipperLevelingViewModel",
"klipperMacroDialogViewModel",
"accessViewModel"
],
elements: [
"#tab_plugin_klipper_main",
"#sidebar_plugin_klipper",
"#navbar_plugin_klipper"
]
});
self.showLevelingDialog = function () {
var dialog = $("#klipper_leveling_dialog");
dialog.modal({
show: "true",
backdrop: "static",
keyboard: false,
});
self.levelingViewModel.initView();
};
self.showPidTuningDialog = function () {
var dialog = $("#klipper_pid_tuning_dialog");
dialog.modal({
show: "true",
backdrop: "static",
keyboard: false,
});
};
self.showOffsetDialog = function () {
var dialog = $("#klipper_offset_dialog");
dialog.modal({
show: "true",
backdrop: "static",
});
};
self.showGraphDialog = function () {
var dialog = $("#klipper_graph_dialog");
dialog.modal({
show: "true",
minHeight: "500px",
maxHeight: "600px",
});
};
self.executeMacro = function (macro) {
var paramObjRegex = /{(.*?)}/g;
if (!self.hasRight("MACRO")) return;
if (macro.macro().match(paramObjRegex) == null) {
OctoPrint.control.sendGcode(
// Use .split to create an array of strings which is sent to
// OctoPrint.control.sendGcode instead of a single string.
macro.macro().split(/\r\n|\r|\n/)
);
} else {
self.paramMacroViewModel.process(macro);
var dialog = $("#klipper_macro_dialog");
dialog.modal({
show: "true",
backdrop: "static",
});
}
};
self.navbarClicked = function () {
$("#tab_plugin_klipper_main_link").find("a").click();
};
self.onGetStatus = function () {
OctoPrint.control.sendGcode("Status");
};
self.onRestartFirmware = function () {
OctoPrint.control.sendGcode("FIRMWARE_RESTART");
};
self.onRestartHost = function () {
OctoPrint.control.sendGcode("RESTART");
};
self.onAfterBinding = function () {
self.connectionState.selectedPort(
self.settings.settings.plugins.klipper.connection.port()
);
};
self.onDataUpdaterPluginMessage = function (plugin, data) {
if (plugin == "klipper") {
switch (data.type) {
case "PopUp":
self.showPopUp(data.subtype, data.title, data.payload);
break;
case "start":
break;
case "reload":
break;
case "console":
self.consoleMessage(data.subtype, data.payload);
break;
case "status":
if (data.payload.length > 36) {
var shortText = data.payload.substring(0, 31) + " [..]";
self.shortStatus_navbar(shortText);
} else {
self.shortStatus_navbar(data.payload);
}
self.shortStatus_sidebar(data.payload);
break;
default:
self.logMessage(data.time, data.subtype, data.payload);
self.consoleMessage(data.subtype, data.payload);
}
}
};
self.logMessage = function (timestamp, type = "info", message) {
if (!timestamp) {
var today = new Date();
var timestamp =
today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
}
self.logMessages.push({
time: timestamp,
type: type,
msg: message.replace(/\n/gi, "<br />"),
});
};
self.consoleMessage = function (type, message) {
if (
self.settings.settings.plugins.klipper.configuration.debug_logging() === true
) {
if (type == "info") {
console.info("OctoKlipper : " + message);
} else if (type == "debug") {
console.debug("OctoKlipper : " + message);
} else {
console.error("OctoKlipper : " + message);
}
}
return;
};
self.onClearLog = function () {
self.logMessages.removeAll();
};
self.isActive = function () {
return self.connectionState.isOperational();
};
self.hasRight = function (right_role) {
//if (self.loginState.isAdmin) return true;
if (right_role == "CONFIG") {
return self.loginState.hasPermission(
self.access.permissions.PLUGIN_KLIPPER_CONFIG
);
} else if (right_role == "MACRO") {
return self.loginState.hasPermission(
self.access.permissions.PLUGIN_KLIPPER_MACRO
);
}
};
self.hasRightKo = function (right_role) {
//if (self.loginState.isAdmin) return true;
if (right_role == "CONFIG") {
return self.loginState.hasPermissionKo(
self.access.permissions.PLUGIN_KLIPPER_CONFIG
);
} else if (right_role == "MACRO") {
return self.loginState.hasPermissionKo(
self.access.permissions.PLUGIN_KLIPPER_MACRO
);
}
};
self.requestRestart = function () {
if (!self.loginState.hasPermission(self.access.permissions.PLUGIN_KLIPPER_CONFIG)) return;
var request = function () {
OctoPrint.plugins.klipper.restartKlipper().done(function () {
self.consoleMessage("debug", "requestRestart");
});
};
var html = "<h4>" + gettext("All ongoing Prints will be stopped!") + "</h4>";
showConfirmationDialog({
title: gettext("Klipper restart?"),
html: html,
proceed: gettext("Proceed"),
onproceed: request,
});
};
// OctoKlipper settings link
self.openOctoKlipperSettings = function (profile_type) {
if (!self.hasRight("CONFIG")) return;
$("a#navbar_show_settings").click();
$("li#settings_plugin_klipper_link a").click();
if (profile_type) {
var query = "#klipper-settings a[data-profile-type='" + profile_type + "']";
$(query).click();
}
};
self.sleep = function (ms) {
return new Promise(resolve => setTimeout(resolve, ms));
};
}
OCTOPRINT_VIEWMODELS.push({
construct: KlipperViewModel,
dependencies: [
"settingsViewModel",
"loginStateViewModel",
"connectionViewModel",
"klipperLevelingViewModel",
"klipperMacroDialogViewModel",
"accessViewModel",
],
elements: [
"#tab_plugin_klipper_main",
"#sidebar_plugin_klipper",
"#navbar_plugin_klipper",
],
});
});

View File

@ -0,0 +1,297 @@
// <Octoprint Klipper Plugin>
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
$(function () {
function KlipperBackupViewModel(parameters) {
var self = this;
self.loginState = parameters[0];
self.klipperViewModel = parameters[1];
self.access = parameters[2];
self.header = OctoPrint.getRequestHeaders({
"content-type": "application/json",
"cache-control": "no-cache",
});
self.apiUrl = OctoPrint.getSimpleApiUrl("klipper");
self.Url = OctoPrint.getBlueprintUrl("klipper");
self.markedForFileRestore = ko.observableArray([]);
self.CfgContent = ko.observable();
//uploads
self.maxUploadSize = ko.observable(0);
self.backupUploadData = undefined;
self.backupUploadName = ko.observable();
self.isAboveUploadSize = function (data) {
return data.size > self.maxUploadSize();
};
self.onStartupComplete = function () {
$('#klipper_backups_dialog').css('display', 'none');
if (self.loginState.loggedIn()) {
self.listBakFiles();
}
};
// initialize list helper
self.backups = new ItemListHelper(
"klipperBakFiles",
{
name: function (a, b) {
// sorts ascending
if (a["name"].toLocaleLowerCase() < b["name"].toLocaleLowerCase()) return -1;
if (a["name"].toLocaleLowerCase() > b["name"].toLocaleLowerCase()) return 1;
return 0;
},
date: function (a, b) {
// sorts descending
if (a["date"] > b["date"]) return -1;
if (a["date"] < b["date"]) return 1;
return 0;
},
size: function (a, b) {
// sorts descending
if (a["bytes"] > b["bytes"]) return -1;
if (a["bytes"] < b["bytes"]) return 1;
return 0;
},
},
{},
"name",
[],
[],
5
);
self.listBakFiles = function () {
self.klipperViewModel.consoleMessage("debug", "listBakFiles");
OctoPrint.plugins.klipper.listCfgBak()
.done(function (response) {
self.backups.updateItems(response.files);
self.backups.resetPage();
});
};
self.showCfg = function (backup) {
if (!self.loginState.hasPermission(self.access.permissions.PLUGIN_KLIPPER_CONFIG)) return;
OctoPrint.plugins.klipper.getCfgBak(backup).done(function (response) {
$('#klipper_backups_dialog textarea').attr('rows', response.response.config.split(/\r\n|\r|\n/).length);
self.CfgContent(response.response.config);
});
};
self.removeCfg = function (backup) {
if (!self.loginState.hasPermission(self.access.permissions.PLUGIN_KLIPPER_CONFIG)) return;
var perform = function () {
OctoPrint.plugins.klipper
.deleteBackup(backup)
.done(function () {
self.listBakFiles();
})
.fail(function (response) {
var html = "<p>" + _.sprintf(gettext("Failed to remove config %(name)s.</p><p>Please consult octoprint.log for details.</p>"), { name: _.escape(backup) });
html += pnotifyAdditionalInfo('<pre style="overflow: auto">' + _.escape(response.responseText) + "</pre>");
new PNotify({
title: gettext("Could not remove config"),
text: html,
type: "error",
hide: false,
});
});
};
showConfirmationDialog(
_.sprintf(gettext('You are about to delete backuped config file "%(name)s".'), {
name: _.escape(backup),
}),
perform
);
};
self.restoreBak = function (backup) {
if (!self.loginState.hasPermission(self.access.permissions.PLUGIN_KLIPPER_CONFIG)) return;
var restore = function () {
OctoPrint.plugins.klipper.restoreBackup(backup).done(function (response) {
self.klipperViewModel.consoleMessage("debug", "restoreCfg: " + backup + " / " + response.restored);
});
};
var html = "<p>" + gettext("This will overwrite any file with the same name on the configpath.") + "</p>" + "<p>" + backup + "</p>";
showConfirmationDialog({
title: gettext("Are you sure you want to restore now?"),
html: html,
proceed: gettext("Proceed"),
onproceed: restore,
});
};
self.markFilesOnPage = function () {
self.markedForFileRestore(_.uniq(self.markedForFileRestore().concat(_.map(self.backups.paginatedItems(), "file"))));
};
self.markAllFiles = function () {
self.markedForFileRestore(_.map(self.backups.allItems, "file"));
};
self.clearMarkedFiles = function () {
self.markedForFileRestore.removeAll();
};
self.restoreMarkedFiles = function () {
var perform = function () {
self._bulkRestore(self.markedForFileRestore()).done(function () {
self.markedForFileRestore.removeAll();
});
};
showConfirmationDialog(
_.sprintf(gettext("You are about to restore %(count)d backuped config files."), {
count: self.markedForFileRestore().length,
}),
perform
);
};
self.removeMarkedFiles = function () {
var perform = function () {
self._bulkRemove(self.markedForFileRestore()).done(function () {
self.markedForFileRestore.removeAll();
});
};
showConfirmationDialog(
_.sprintf(gettext("You are about to delete %(count)d backuped config files."), {
count: self.markedForFileRestore().length,
}),
perform
);
};
self._bulkRestore = function (files) {
var title, message, handler;
title = gettext("Restoring klipper config files");
self.klipperViewModel.consoleMessage("debug", title);
message = _.sprintf(gettext("Restoring %(count)d backuped config files..."), {
count: files.length,
});
handler = function (filename) {
return OctoPrint.plugins.klipper
.restoreBackup(filename)
.done(function (response) {
deferred.notify(
_.sprintf(gettext("Restored %(filename)s..."), {
filename: _.escape(filename),
}),
true
);
self.klipperViewModel.consoleMessage("debug", "restoreCfg: " + filename + " / " + response);
self.markedForFileRestore.remove(function (item) {
return item.name == filename;
});
})
.fail(function () {
deferred.notify(_.sprintf(gettext("Restoring of %(filename)s failed, continuing..."), { filename: _.escape(filename) }), false);
});
};
var deferred = $.Deferred();
var promise = deferred.promise();
var options = {
title: title,
message: message,
max: files.length,
output: true,
};
showProgressModal(options, promise);
var requests = [];
_.each(files, function (filename) {
var request = handler(filename);
requests.push(request);
});
$.when.apply($, _.map(requests, wrapPromiseWithAlways)).done(function () {
deferred.resolve();
});
return promise;
};
self._bulkRemove = function (files) {
var title, message, handler;
title = gettext("Deleting backup files");
message = _.sprintf(gettext("Deleting %(count)d backup files..."), {
count: files.length,
});
handler = function (filename) {
return OctoPrint.plugins.klipper
.deleteBackup(filename)
.done(function () {
deferred.notify(_.sprintf(gettext("Deleted %(filename)s..."), { filename: _.escape(filename) }), true);
self.markedForFileRestore.remove(function (item) {
return item.name == filename;
});
})
.fail(function () {
deferred.notify(_.sprintf(gettext("Deleting of %(filename)s failed, continuing..."), { filename: _.escape(filename) }), false);
});
};
var deferred = $.Deferred();
var promise = deferred.promise();
var options = {
title: title,
message: message,
max: files.length,
output: true,
};
showProgressModal(options, promise);
var requests = [];
_.each(files, function (filename) {
var request = handler(filename);
requests.push(request);
});
$.when.apply($, _.map(requests, wrapPromiseWithAlways)).done(function () {
deferred.resolve();
self.listBakFiles();
});
return promise;
};
}
OCTOPRINT_VIEWMODELS.push({
construct: KlipperBackupViewModel,
dependencies: ["loginStateViewModel", "klipperViewModel", "accessViewModel"],
elements: ["#klipper_backups_dialog"],
});
});

View File

@ -0,0 +1,274 @@
// <Octoprint Klipper Plugin>
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
$(function () {
function KlipperEditorViewModel(parameters) {
var self = this;
var obKlipperConfig = null;
var editor = null;
var editor_dirty = false;
self.settings = parameters[0];
self.klipperViewModel = parameters[1];
self.CfgFilename = ko.observable("");
self.CfgContent = ko.observable("");
self.config = []
self.header = OctoPrint.getRequestHeaders({
"content-type": "application/json",
"cache-control": "no-cache",
});
$('#klipper_editor').on('shown.bs.modal', function () {
editor.focus();
self.setEditorDivSize();
$(window).on('resize', function(){
self.klipperViewModel.sleep(500).then(
function () {
self.setEditorDivSize();
}
);
});
});
self.closeEditor = function () {
if (editor_dirty===true) {
showConfirmationDialog({
message: gettext(
"Your file seems to have changed."
),
question: gettext("Do you really want to close it?"),
cancel: gettext("No"),
proceed: gettext("Yes"),
onproceed: function () {
var dialog = $("#klipper_editor");
dialog.modal('hide');
},
nofade: true
});
} else {
var dialog = $("#klipper_editor");
dialog.modal('hide');
}
}
self.addStyleAttribute = function ($element, styleAttribute) {
$element.attr('style', $element.attr('style') + '; ' + styleAttribute);
}
self.setEditorDivSize = function () {
var klipper_modal_body= $('#klipper_editor .modal-body');
var klipper_config= $('#plugin-klipper-config');
var height = $(window).height() - $('#klipper_editor .modal-header').outerHeight() - $('#klipper_editor .modal-footer').outerHeight() - 118;
self.addStyleAttribute(klipper_modal_body, 'height: ' + height + 'px !important;');
//self.addStyleAttribute(klipper_config, 'height: ' + height + 'px !important;');
klipper_config.css('height', height);
if (editor) {
editor.resize();
}
};
self.process = function (config) {
return new Promise(function (resolve) {
self.config = config;
self.CfgFilename(config.file);
self.CfgContent(config.content);
if (editor) {
editor.session.setValue(self.CfgContent());
editor_dirty=false;
editor.setFontSize(self.settings.settings.plugins.klipper.configuration.fontsize());
self.settings.settings.plugins.klipper.configuration.old_config(config.content);
editor.clearSelection();
self.klipperViewModel.sleep(500).then(
function() {
self.setEditorDivSize();
resolve("done");
}
);
}
});
}
self.checkSyntax = function () {
if (editor.session) {
self.klipperViewModel.consoleMessage("debug", "checkSyntax started");
OctoPrint.plugins.klipper.checkCfg(editor.session.getValue())
.done(function (response) {
var msg = ""
if (response.is_syntax_ok == true) {
self.klipperViewModel.showPopUp("success", gettext("SyntaxCheck"), gettext("SyntaxCheck OK"));
self.editorFocusDelay(1000);
} else {
msg = gettext('Syntax NOK')
showMessageDialog(
msg,
{
title: gettext("SyntaxCheck"),
onclose: function () { self.editorFocusDelay(1000); }
}
);
}
});
};
};
self.saveCfg = function () {
if (editor.session) {
self.klipperViewModel.consoleMessage("debug", "SaveCfg start");
OctoPrint.plugins.klipper.saveCfg(editor.session.getValue(), self.CfgFilename())
.done(function (response) {
var msg = ""
if (response.saved === true) {
self.klipperViewModel.showPopUp("success", gettext("Save Config"), gettext("File saved."));
editor_dirty = false;
if (self.settings.settings.plugins.klipper.configuration.restart_onsave()==true) {
self.klipperViewModel.requestRestart();
}
} else {
msg = gettext('File not saved!')
showMessageDialog(
msg,
{
title: gettext("Save Config"),
onclose: function () { self.editorFocusDelay(1000); }
}
)
}
});
}
};
self.minusFontsize = function () {
self.settings.settings.plugins.klipper.configuration.fontsize(
self.settings.settings.plugins.klipper.configuration.fontsize() - 1
);
if (self.settings.settings.plugins.klipper.configuration.fontsize() < 9) {
self.settings.settings.plugins.klipper.configuration.fontsize(9);
}
if (editor) {
editor.setFontSize(
self.settings.settings.plugins.klipper.configuration.fontsize()
);
editor.resize();
}
};
self.plusFontsize = function () {
self.settings.settings.plugins.klipper.configuration.fontsize(
self.settings.settings.plugins.klipper.configuration.fontsize() + 1
);
if (self.settings.settings.plugins.klipper.configuration.fontsize() > 20) {
self.settings.settings.plugins.klipper.configuration.fontsize(20);
}
if (editor) {
editor.setFontSize(
self.settings.settings.plugins.klipper.configuration.fontsize()
);
editor.resize();
}
};
self.loadLastSession = function () {
if (self.settings.settings.plugins.klipper.configuration.old_config() != "") {
self.klipperViewModel.consoleMessage(
"info",
"lastSession:" +
self.settings.settings.plugins.klipper.configuration.old_config()
);
if (editor.session) {
editor.session.setValue(
self.settings.settings.plugins.klipper.configuration.old_config()
);
editor.clearSelection();
}
}
};
self.reloadFromFile = function () {
OctoPrint.plugins.klipper.getCfg(self.CfgFilename())
.done(function (response) {
self.klipperViewModel.consoleMessage("debug", "reloadFromFile done");
if (response.response.text != "") {
var msg = response.response.text
showMessageDialog(
msg,
{
title: gettext("Reload File")
}
)
} else {
self.klipperViewModel.showPopUp("success", gettext("Reload Config"), gettext("File reloaded."));
if (editor) {
editor.session.setValue(response.response.config);
editor.clearSelection();
editor.focus();
}
}
})
.fail(function (response) {
var msg = response
showMessageDialog(
msg,
{
title: gettext("Reload File")
}
)
});
};
self.onStartup = function () {
ace.config.set("basePath", "plugin/klipper/static/js/lib/ace/");
editor = ace.edit("plugin-klipper-config");
editor.setTheme("ace/theme/monokai");
editor.session.setMode("ace/mode/klipper_config");
editor.clearSelection();
editor.setOptions({
hScrollBarAlwaysVisible: false,
vScrollBarAlwaysVisible: false,
autoScrollEditorIntoView: true,
showPrintMargin: false,
//maxLines: "Infinity"
});
editor.session.on('change', function (delta) {
self.CfgContent(editor.getValue());
editor_dirty = true;
editor.resize();
});
};
self.editorFocusDelay = function (delay) {
self.klipperViewModel.sleep(delay).then(
function () {
editor.focus();
}
);
};
}
OCTOPRINT_VIEWMODELS.push({
construct: KlipperEditorViewModel,
dependencies: ["settingsViewModel", "klipperViewModel"],
elements: ["#klipper_editor"],
});
});

View File

@ -13,220 +13,325 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
$(function() {
$('#klipper-settings a:first').tab('show');
function KlipperSettingsViewModel(parameters) {
var self = this;
var obKlipperConfig = null;
var editor = null;
$(function () {
$("#klipper-settings a:first").tab("show");
function KlipperSettingsViewModel(parameters) {
var self = this;
self.settings = parameters[0];
self.klipperViewModel = parameters[1];
self.settings = parameters[0];
self.klipperViewModel = parameters[1];
self.klipperEditorViewModel = parameters[2];
self.klipperBackupViewModel = parameters[3];
self.access = parameters[4];
self.header = OctoPrint.getRequestHeaders({
"content-type": "application/json",
"cache-control": "no-cache"
});
self.header = OctoPrint.getRequestHeaders({
"content-type": "application/json",
"cache-control": "no-cache",
});
self.apiUrl = OctoPrint.getSimpleApiUrl("klipper");
self.markedForFileRemove = ko.observableArray([]);
self.onSettingsBeforeSave = function () {
if (editor.session && self.settings.settings.plugins.klipper.configuration.parse_check() === true) {
self.klipperViewModel.consoleMessage("debug", "onSettingsBeforeSave:")
var settings = {
"crossDomain": true,
"url": self.apiUrl,
"method": "POST",
"headers": self.header,
"processData": false,
"dataType": "json",
"data": JSON.stringify({command: "checkConfig",
config: editor.session.getValue()})
}
$.ajax(settings).done(function (response) {
});
}
}
self.addMacro = function() {
self.settings.settings.plugins.klipper.macros.push({
name: 'Macro',
macro: '',
sidebar: true,
tab: true
});
}
self.removeMacro = function(macro) {
self.settings.settings.plugins.klipper.macros.remove(macro);
}
self.moveMacroUp = function(macro) {
self.moveItemUp(self.settings.settings.plugins.klipper.macros, macro)
}
self.moveMacroDown = function(macro) {
self.moveItemDown(self.settings.settings.plugins.klipper.macros, macro)
}
self.addProbePoint = function() {
self.settings.settings.plugins.klipper.probe.points.push(
{
name: 'point-#',
x:0, y:0, z:0
}
);
}
self.removeProbePoint = function(point) {
self.settings.settings.plugins.klipper.probe.points.remove(point);
}
self.moveProbePointUp = function(macro) {
self.moveItemUp(self.settings.settings.plugins.klipper.probe.points, macro)
}
self.moveProbePointDown = function(macro) {
self.moveItemDown(self.settings.settings.plugins.klipper.probe.points, macro)
}
self.moveItemDown = function(list, item) {
var i = list().indexOf(item);
if (i < list().length - 1) {
var rawList = list();
list.splice(i, 2, rawList[i + 1], rawList[i]);
}
}
self.moveItemUp = function(list, item) {
var i = list().indexOf(item);
if (i > 0) {
var rawList = list();
list.splice(i-1, 2, rawList[i], rawList[i-1]);
}
}
self.minusFontsize = function () {
self.settings.settings.plugins.klipper.configuration.fontsize(self.settings.settings.plugins.klipper.configuration.fontsize() - 1);
if (self.settings.settings.plugins.klipper.configuration.fontsize() < 9) {
self.settings.settings.plugins.klipper.configuration.fontsize(9);
}
if (editor) {
editor.setFontSize(self.settings.settings.plugins.klipper.configuration.fontsize());
editor.resize();
}
}
self.plusFontsize = function () {
self.settings.settings.plugins.klipper.configuration.fontsize(self.settings.settings.plugins.klipper.configuration.fontsize() + 1);
if (self.settings.settings.plugins.klipper.configuration.fontsize() > 20) {
self.settings.settings.plugins.klipper.configuration.fontsize(20);
}
if (editor) {
editor.setFontSize(self.settings.settings.plugins.klipper.configuration.fontsize());
editor.resize();
}
}
self.loadLastSession = function () {
if (self.settings.settings.plugins.klipper.configuration.old_config() != "") {
self.klipperViewModel.consoleMessage("info","lastSession:" + self.settings.settings.plugins.klipper.configuration.old_config())
if (editor.session) {
editor.session.setValue(self.settings.settings.plugins.klipper.configuration.old_config());
editor.clearSelection();
}
}
}
self.reloadFromFile = function () {
if (editor.session) {
var settings = {
"crossDomain": true,
"url": self.apiUrl,
"method": "POST",
"headers": self.header,
"processData": false,
"dataType": "json",
"data": JSON.stringify({command: "reloadConfig"})
}
$.ajax(settings).done(function (response) {
editor.session.setValue(response["data"]);
editor.clearSelection();
});
}
}
self.configBound = function (config) {
config.withSilence = function() {
this.notifySubscribers = function() {
if (!this.isSilent) {
ko.subscribable.fn.notifySubscribers.apply(this, arguments);
}
}
this.silentUpdate = function(newValue) {
this.isSilent = true;
this(newValue);
this.isSilent = false;
};
return this;
}
obKlipperConfig = config.withSilence();
if (editor) {
editor.setValue(obKlipperConfig());
editor.setFontSize(self.settings.settings.plugins.klipper.configuration.fontsize());
editor.resize();
editor.clearSelection();
}
return obKlipperConfig;
}
ace.config.set("basePath", "plugin/klipper/static/js/lib/ace/");
editor = ace.edit("plugin-klipper-config");
editor.setTheme("ace/theme/monokai");
editor.session.setMode("ace/mode/klipper_config");
editor.setOptions({
hScrollBarAlwaysVisible: false,
vScrollBarAlwaysVisible: false,
autoScrollEditorIntoView: true,
showPrintMargin: false,
maxLines: "Infinity"
})
editor.session.on('change', function(delta) {
if (obKlipperConfig) {
obKlipperConfig.silentUpdate(editor.getValue());
editor.resize();
}
});
// Uncomment this if not using maxLines: "Infinity"...
// setInterval(function(){ editor.resize(); }, 500);
self.onDataUpdaterPluginMessage = function(plugin, data) {
if(plugin == "klipper") {
if ("reload" == data.type){
if ("config" == data.subtype){
if (editor.session) {
editor.session.setValue(data.payload);
editor.clearSelection();
}
}
return
}
}
}
self.checkFontsize = function () {
if (self.settings.settings.plugins.klipper.configuration.fontsize() > 20) {
self.settings.settings.plugins.klipper.configuration.fontsize(20)
} else if (self.settings.settings.plugins.klipper.configuration.fontsize()< 9) {
self.settings.settings.plugins.klipper.configuration.fontsize(9)
}
}
OCTOPRINT_VIEWMODELS.push({
construct: KlipperSettingsViewModel,
dependencies: [
"settingsViewModel",
"klipperViewModel"
],
elements: ["#settings_plugin_klipper"]
});
// initialize list helper
self.configs = new ItemListHelper(
"klipperCfgFiles",
{
name: function (a, b) {
// sorts ascending
if (a["name"].toLocaleLowerCase() < b["name"].toLocaleLowerCase()) return -1;
if (a["name"].toLocaleLowerCase() > b["name"].toLocaleLowerCase()) return 1;
return 0;
},
date: function (a, b) {
// sorts descending
if (a["date"] > b["date"]) return -1;
if (a["date"] < b["date"]) return 1;
return 0;
},
size: function (a, b) {
// sorts descending
if (a["bytes"] > b["bytes"]) return -1;
if (a["bytes"] < b["bytes"]) return 1;
return 0;
},
},
{},
"name",
[],
[],
15
);
self.onStartupComplete = function () {
self.listCfgFiles();
self.loadBaseConfig();
};
self.listCfgFiles = function () {
self.klipperViewModel.consoleMessage("debug", "listCfgFiles started");
OctoPrint.plugins.klipper.listCfg().done(function (response) {
self.klipperViewModel.consoleMessage("debug", "listCfgFiles done");
self.configs.updateItems(response.files);
self.configs.resetPage();
});
};
self.loadBaseConfig = function () {
if (!self.klipperViewModel.hasRight("CONFIG")) return;
OctoPrint.plugins.klipper.getCfg("printer.cfg").done(function (response) {
var config = {
content: response.response.config,
file: "printer.cfg",
};
self.klipperEditorViewModel.process(config).then();
});
};
self.removeCfg = function (config) {
if (!self.klipperViewModel.hasRight("CONFIG")) return;
var perform = function () {
OctoPrint.plugins.klipper
.deleteCfg(config)
.done(function () {
self.listCfgFiles();
})
.fail(function (response) {
var html = "<p>" + _.sprintf(gettext("Failed to remove config %(name)s.</p><p>Please consult octoprint.log for details.</p>"), { name: _.escape(config) });
html += pnotifyAdditionalInfo('<pre style="overflow: auto">' + _.escape(response.responseText) + "</pre>");
new PNotify({
title: gettext("Could not remove config"),
text: html,
type: "error",
hide: false,
});
});
};
showConfirmationDialog(
_.sprintf(gettext('You are about to delete config file "%(name)s".'), {
name: _.escape(config),
}),
perform
);
};
self.markFilesOnPage = function () {
self.markedForFileRemove(_.uniq(self.markedForFileRemove().concat(_.map(self.configs.paginatedItems(), "file"))));
};
self.markAllFiles = function () {
self.markedForFileRemove(_.map(self.configs.allItems, "file"));
};
self.clearMarkedFiles = function () {
self.markedForFileRemove.removeAll();
};
self.removeMarkedFiles = function () {
var perform = function () {
self._bulkRemove(self.markedForFileRemove()).done(function () {
self.markedForFileRemove.removeAll();
});
};
showConfirmationDialog(
_.sprintf(gettext("You are about to delete %(count)d config files."), {
count: self.markedForFileRemove().length,
}),
perform
);
};
self._bulkRemove = function (files) {
var title, message, handler;
title = gettext("Deleting config files");
message = _.sprintf(gettext("Deleting %(count)d config files..."), {
count: files.length,
});
handler = function (filename) {
return OctoPrint.plugins.klipper
.deleteCfg(filename)
.done(function () {
deferred.notify(
_.sprintf(gettext("Deleted %(filename)s..."), {
filename: _.escape(filename),
}),
true
);
self.markedForFileRemove.remove(function (item) {
return item.name == filename;
});
})
.fail(function () {
deferred.notify(_.sprintf(gettext("Deleting of %(filename)s failed, continuing..."), { filename: _.escape(filename) }), false);
});
};
var deferred = $.Deferred();
var promise = deferred.promise();
var options = {
title: title,
message: message,
max: files.length,
output: true,
};
showProgressModal(options, promise);
var requests = [];
_.each(files, function (filename) {
var request = handler(filename);
requests.push(request);
});
$.when.apply($, _.map(requests, wrapPromiseWithAlways)).done(function () {
deferred.resolve();
self.listCfgFiles();
});
return promise;
};
self.showBackupsDialog = function () {
self.klipperViewModel.consoleMessage("debug", "showBackupsDialog");
self.klipperBackupViewModel.listBakFiles();
var dialog = $("#klipper_backups_dialog");
dialog.modal({
show: "true",
});
};
self.showEditor = function () {
if (!self.klipperViewModel.hasRight("CONFIG")) return;
var editorDialog = $("#klipper_editor");
editorDialog.modal({
show: "true",
width: "90%",
backdrop: "static",
});
}
self.newFile = function () {
if (!self.klipperViewModel.hasRight("CONFIG")) return;
var config = {
content: "",
file: "Change Filename",
};
self.klipperEditorViewModel.process(config).then(
function() { self.showEditor(); }
);
};
self.openConfig = function (file) {
if (!self.klipperViewModel.hasRight("CONFIG")) return;
OctoPrint.plugins.klipper.getCfg(file).done(function (response) {
var config = {
content: response.response.config,
file: file,
};
self.klipperEditorViewModel.process(config).then(
function() { self.showEditor(); }
);
});
};
self.addMacro = function () {
self.settings.settings.plugins.klipper.macros.push({
name: "Macro",
macro: "",
sidebar: true,
tab: true,
});
};
self.removeMacro = function (macro) {
self.settings.settings.plugins.klipper.macros.remove(macro);
};
self.moveMacroUp = function (macro) {
self.moveItemUp(self.settings.settings.plugins.klipper.macros, macro);
};
self.moveMacroDown = function (macro) {
self.moveItemDown(self.settings.settings.plugins.klipper.macros, macro);
};
self.addProbePoint = function () {
self.settings.settings.plugins.klipper.probe.points.push({
name: "point-#",
x: 0,
y: 0,
z: 0,
});
};
self.removeProbePoint = function (point) {
self.settings.settings.plugins.klipper.probe.points.remove(point);
};
self.moveProbePointUp = function (macro) {
self.moveItemUp(self.settings.settings.plugins.klipper.probe.points, macro);
};
self.moveProbePointDown = function (macro) {
self.moveItemDown(self.settings.settings.plugins.klipper.probe.points, macro);
};
self.moveItemDown = function (list, item) {
var i = list().indexOf(item);
if (i < list().length - 1) {
var rawList = list();
list.splice(i, 2, rawList[i + 1], rawList[i]);
}
};
self.moveItemUp = function (list, item) {
var i = list().indexOf(item);
if (i > 0) {
var rawList = list();
list.splice(i - 1, 2, rawList[i], rawList[i - 1]);
}
};
self.onDataUpdaterPluginMessage = function (plugin, data) {
if (plugin == "klipper" && data.type == "reload" && data.subtype == "configlist") {
self.klipperViewModel.consoleMessage("debug", "onDataUpdaterPluginMessage klipper reload configlist");
self.listCfgFiles();
} else if (plugin == "klipper" && data.type == "start" && data.subtype == "config") {
self.klipperViewModel.consoleMessage("debug", "onDataUpdaterPluginMessage klipper start config");
self.startConfig(data.title, data.payload);
}
};
self.startConfig = function (file, content) {
if (!self.klipperViewModel.hasRight("CONFIG")) return;
filename = file || "";
var config = {
content: content,
file: filename,
};
self.klipperEditorViewModel.process(config).then();
};
}
OCTOPRINT_VIEWMODELS.push({
construct: KlipperSettingsViewModel,
dependencies: ["settingsViewModel", "klipperViewModel", "klipperEditorViewModel", "klipperBackupViewModel", "accessViewModel"],
elements: ["#settings_plugin_klipper"],
});
});

View File

@ -160,7 +160,7 @@ ace.define("ace/mode/klipper_config_highlight_rules",[], function(require, expor
caseInsensitive: true
}, {
token: "support.type",
regex: /[\^~!]*(?:z:)?[a-zA-Z]{1,2}\d{1,2}(?:\.\d{1,2})?/,
regex: /[\^~!]*(?:z:)?[a-zA-Z]{1,4}(?:gpio)?\d{1,2}(?:\.\d{1,2})?/,
caseInsensitive: true
}],
"#config_line_start_gcode": [{
@ -192,8 +192,8 @@ ace.define("ace/mode/klipper_config_highlight_rules",[], function(require, expor
}]
}],
"#config_line": [{
token: ["variable.name", "variable.name"],
regex: /(?!gcode|sensor_type|rpi:)(\w+)(\s*[:]\s*)/,
token: ["variable.name", "variable.name", "variable.name", "variable.name"],
regex: /(?!gcode|sensor_type|rpi:)(\w+)(\s*[:]\s*\w+[:])|(?!gcode|sensor_type|rpi:)(\w+)(\s*[:]\s*)/,
push: [{
token: "text",
regex: /$/,

View File

@ -0,0 +1,110 @@
<div id="klipper_backups_dialog" class="modal hide fade large" tabindex="-1" role="dialog" aria-labelledby="klipper_backups_dialog_label"
aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h3 id="klipper_dialog_label">{{ _('Backups') }}</h3>
</div>
<div class="modal-body">
<div class="editor-controls">
<div class="klipper-btn-group klipper-fluid-item-1" data-bind="visible: $root.klipperViewModel.hasRightKo('CONFIG')">
<div class="btn-group">
<button class="btn btn-small dropdown-toggle" data-toggle="dropdown">
<i class="far fa-square"></i>
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li>
<a href="javascript:void(0)" data-bind="click: markFilesOnPage">{{ _('Select all on this page') }}</a>
</li>
<li>
<a href="javascript:void(0)" data-bind="click: markAllFiles">{{ _('Select all') }}</a>
</li>
<li class="divider">
</li>
<li>
<a href="javascript:void(0)" data-bind="click: clearMarkedFiles">{{ _('Clear selection') }}</a>
</li>
</ul>
</div>
<button class="btn btn-small" data-bind="click: listBakFiles" title="{{ _('Refresh file list') }}">
<i class="icon-refresh"></i> {{ _('Refresh Files') }}
</button>
<button class="btn btn-small"
data-bind="click: restoreMarkedFiles, enable: markedForFileRestore().length > 0">{{ _('Restore selected') }}</button>
<button class="btn btn-small"
data-bind="click: removeMarkedFiles, enable: markedForFileRestore().length > 0">{{ _('Delete selected') }}</button>
</div>
<div class="pull-right">
<div class="btn-group">
<button class="btn btn-small dropdown-toggle" data-toggle="dropdown">
<i class="fas fa-wrench"></i>
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right">
<li><a href="javascript:void(0)" data-bind="click: function() { backups.changeSorting('name'); }"><i class="fas fa-check"
data-bind="style: {visibility: backups.currentSorting() == 'name' ? 'visible' : 'hidden'}"></i> {{ _('Sort by name') }}
({{ _('ascending') }})</a>
</li>
<li><a href="javascript:void(0)" data-bind="click: function() { backups.changeSorting('date'); }"><i class="fas fa-check"
data-bind="style: {visibility: backups.currentSorting() == 'date' ? 'visible' : 'hidden'}"></i> {{ _('Sort by date') }}
({{ _('descending') }})</a>
</li>
<li><a href="javascript:void(0)" data-bind="click: function() { backups.changeSorting('size'); }"><i class="fas fa-check"
data-bind="style: {visibility: backups.currentSorting() == 'size' ? 'visible' : 'hidden'}"></i> {{ _('Sort by file size') }}
({{ _('descending') }})</a>
</li>
</ul>
</div>
</div>
</div>
<table class="table table-striped table-hover table-condensed table-hover" id="klipper_bak_files">
<thead>
<tr>
<th class="klipper_baks_checkbox"></th>
<th class="klipper_baks_name">{{ _('Name') }}</th>
<th class="klipper_baks_size">{{ _('Size') }}</th>
<th class="klipper_baks_action">{{ _('Action') }}</th>
</tr>
</thead>
<tbody data-bind="foreach: backups.paginatedItems">
<tr data-bind="attr: {title: name}">
<td class="klipper_baks_checkbox"><input type="checkbox"
data-bind="value: file, checked: $root.markedForFileRestore, invisible: !$root.klipperViewModel.hasRightKo('CONFIG')" />
</td>
<td class="klipper_baks_name" data-bind="text: name"></td>
<td class="klipper_baks_size" data-bind="text: size"></td>
<td class="klipper_baks_action">
<a href="javascript:void(0)" class="far fa-trash-alt" title="{{ _('Delete') }}"
data-bind="css: {disabled: !$root.klipperViewModel.hasRightKo('CONFIG')()}, click: function() { $parent.removeCfg($data.name); }"></a>
&nbsp;|&nbsp;
<a href="javascript:void(0)" class="fas fa-undo" title="{{ _('Restore') }}"
data-bind="css: {disabled: !$root.klipperViewModel.hasRightKo('CONFIG')()}, click: function() { $parent.restoreBak($data.name); }"></a>
&nbsp;|&nbsp;
<a href="javascript:void(0)" class="fas fa-download" title="{{ _('Download') }}"
data-bind="css: {disabled: !$root.klipperViewModel.hasRightKo('CONFIG')()}, attr: { href: ($root.klipperViewModel.hasRightKo('CONFIG')()) ? $data.url : 'javascript:void(0)'}"></a>
&nbsp;|&nbsp;
<a href="javascript:void(0)" class="fas fa-eye" title="{{ _('Preview') }}"
data-bind="css: {disabled: !$root.klipperViewModel.hasRightKo('CONFIG')()}, click: function() { $parent.showCfg($data.name); }"></a>
</td>
</tr>
</tbody>
</table>
<div class="pagination pagination-mini pagination-centered">
<ul>
<li data-bind="css: {disabled: backups.currentPage() === 0}"><a href="javascript:void(0)" data-bind="click: backups.prevPage">«</a></li>
</ul>
<ul data-bind="foreach: backups.pages">
<li data-bind="css: { active: $data.number === $root.backups.currentPage(), disabled: $data.number === -1 }"><a href="javascript:void(0)"
data-bind="text: $data.text, click: function() { $root.backups.changePage($data.number); }"></a></li>
</ul>
<ul>
<li data-bind="css: {disabled: backups.currentPage() === backups.lastPage()}"><a href="javascript:void(0)"
data-bind="click: backups.nextPage">»</a></li>
</ul>
</div>
<div class="textarea">
<textarea rows="21" readonly data-bind="value: CfgContent" id="klipper_bak_text"></textarea>
</div>
</div>
</div>

View File

@ -0,0 +1,46 @@
<div id="klipper_editor" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="klipper_editor_label" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-bind="click: closeEditor">&times;</button>
<h3 id="klipper_dialog_label">Editor
<div class="pull-right">
<button data-bind="click: minusFontsize" title="{{ _('Decrease Fontsize') }}" class="btn">
<i class="fas fa-search-minus"></i>
</button>
<button data-bind="click: plusFontsize" title="{{ _('Increase Fontsize') }}" class="btn">
<i class="fas fa-search-plus"></i>
</button>
</div>
</h3>
</div>
<div class="modal-body">
<script src="plugin/klipper/static/js/lib/ace/ace.min.js" type="text/javascript" charset="utf-8"></script>
<script src="plugin/klipper/static/js/lib/ace/theme-monokai.min.js" type="text/javascript" charset="utf-8"></script>
<script src="plugin/klipper/static/js/lib/ace/mode-klipper_config.js" type="text/javascript"></script>
<div class="full-sized-box">
<div class="conf-editor" id="conf_editor">
<input id="hdnLoadKlipperConfig" type="hidden" data-bind="value: CfgContent">
<div id="plugin-klipper-config"></div>
</div>
</div>
</div>
<div class="modal-footer">
<div class="editor-controls">
<span class="control-label">{{ _('Filename') }}:</span>
<input type="text" data-bind="value: CfgFilename">
<div class="klipper-btn-group klipper-fluid-item-2">
<button class="btn btn-small" data-bind="click: reloadFromFile" title="{{ _('Reload from file') }}">
<i class="fas fa-upload"></i> {{ _('Reload from file') }}
</button>
<button class="btn btn-small" data-bind="click: loadLastSession" title="{{ _('Reload last version') }}">
<i class="fas fa-redo"></i> {{ _('Reload last version') }}
</button>
<button class="btn btn-small" data-bind="click: checkSyntax" title="{{ _('Check Syntax') }}">
<i class="fas fa-spell-check"></i> {{ _('Check Syntax') }}
</button>
<button class="btn btn-small" data-bind="click: saveCfg" title="{{ _('Save Config') }}">
<i class="fas fa-save"></i> {{ _('Save Config') }}
</button>
</div>
</div>
</div>
</div>

View File

@ -1,7 +1,7 @@
<div id="klipper_graph_dialog" class="modal hide fade large" tabindex="-1" role="dialog" aria-labelledby="klipper_graph_dialog_label" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h3 id="klipper_pid_tuning_dialog_label">{{ _('Performance Graph') }}</h3>
<h3 id="klipper_dialog_label">{{ _('Performance Graph') }}</h3>
</div>
<div class="modal-body">
<div class="full-sized-box">

View File

@ -1,7 +1,7 @@
<div id="klipper_offset_dialog" class="modal hide fade small" tabindex="-1" role="dialog" aria-labelledby="klipper_offset_dialog_label" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h3 id="klipper_pid_tuning_dialog_label">{{ _('Coordinate Offset') }}</h3>
<h3 id="klipper_dialog_label">{{ _('Coordinate Offset') }}</h3>
</div>
<div class="modal-body">
<div class="row-fluid" style="margin-bottom: 15px">

View File

@ -1,7 +1,7 @@
<div id="klipper_macro_dialog" class="modal hide fade small" tabindex="-1" role="dialog" aria-labelledby="klipper_macro_dialog_label" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h3 id="klipper_pid_tuning_dialog_label">{{ _('Run - ') }}<span data-bind="text: macroName"></span></h3>
<h3 id="klipper_dialog_label">{{ _('Run - ') }}<span data-bind="text: macroName"></span></h3>
</div>
<div class="modal-body">
<div class="control-group" data-bind="foreach: parameters">

View File

@ -1,7 +1,7 @@
<div id="klipper_pid_tuning_dialog" class="modal hide fade small" tabindex="-1" role="dialog" aria-labelledby="klipper_pid_tuning_dialog_label" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h3 id="klipper_pid_tuning_dialog_label">{{ _('PID Tuning') }}</h3>
<h3 id="klipper_dialog_label">{{ _('PID Tuning') }}</h3>
</div>
<div class="modal-body">
<div class="control-group">

View File

@ -1,231 +1,332 @@
<form class="form-horizontal">
<ul class="nav nav-pills" id="klipper-settings">
<li><a href="#basic" data-toggle="tab" data-profile-type="klipper-basic">{{ _('Basic') }}</a></li>
<li><a href="#macros" data-toggle="tab" data-profile-type="klipper-macros">{{ _('Macros') }}</a></li>
<li><a href="#level" data-toggle="tab" data-profile-type="klipper-bed">{{ _('Bed Leveling') }}</a></li>
<li><a href="#conf" data-toggle="tab" data-profile-type="klipper-config">{{ _('Klipper Configuration') }}</a></li>
</ul>
<div class="tab-content">
<!-- Basics -->
<div class="tab-pane active" id="basic">
<div class="control-group">
<ul class="nav nav-pills" id="klipper-settings">
<li><a href="#basic" data-toggle="tab" data-profile-type="klipper-basic">{{ _('Basic') }}</a></li>
<li><a href="#macros" data-toggle="tab" data-profile-type="klipper-macros">{{ _('Macros') }}</a></li>
<li><a href="#level" data-toggle="tab" data-profile-type="klipper-bed">{{ _('Bed Leveling') }}</a></li>
<li><a href="#conf" data-toggle="tab" data-profile-type="klipper-config">{{ _('Klipper Configuration') }}</a></li>
</ul>
<div class="tab-content">
<!-- Basics -->
<div class="tab-pane active" id="basic">
<div class="control-group">
<label class="control-label">{{ _('Serial Port') }}</label>
<div class="controls">
<input type="text" class="input-block-level" data-bind="value: settings.settings.plugins.klipper.connection.port">
<input type="text" class="input-block-level" data-bind="value: settings.settings.plugins.klipper.connection.port" />
</div>
</div>
<div class="control-group">
</div>
<div class="control-group">
<label class="control-label">{{ _('Replace Connection Panel') }}</label>
<div class="controls">
<input class="controls-checkbox" title="{{ _('Replace Connection Panel') }}" type="checkbox" data-bind="checked: settings.settings.plugins.klipper.connection.replace_connection_panel">
<input class="controls-checkbox" title="{{ _('Replace Connection Panel') }}" type="checkbox"
data-bind="checked: settings.settings.plugins.klipper.connection.replace_connection_panel">
</div>
</div>
<div class="control-group">
</div>
<div class="control-group border">
<label class="control-label">{{ _('Show Short Messages') }}</label>
<div class="controls">
<label class="checkbox" title="{{ _('on NavBar') }}"><input type="checkbox" data-bind="checked: settings.settings.plugins.klipper.configuration.shortStatus_navbar"> {{ _('on NavBar') }}</label>
<label class="checkbox" title="{{ _('on SideBar') }}"><input type="checkbox" data-bind="checked: settings.settings.plugins.klipper.configuration.shortStatus_sidebar"> {{ _('on SideBar') }}</label>
<label class="checkbox" title="{{ _('on NavBar') }}"><input type="checkbox"
data-bind="checked: settings.settings.plugins.klipper.configuration.shortStatus_navbar" /> {{ _('on NavBar') }}</label>
<label class="checkbox" title="{{ _('on SideBar') }}"><input type="checkbox"
data-bind="checked: settings.settings.plugins.klipper.configuration.shortStatus_sidebar" /> {{ _('on SideBar') }}</label>
</div>
</div>
<div class="control-group">
</div>
<div class="control-group">
<label class="control-label">{{ _('Enable debug logging') }}</label>
<div class="controls">
<input class="controls-checkbox" title="{{ _('Enable debug logging') }}" type="checkbox" data-bind="checked: settings.settings.plugins.klipper.configuration.debug_logging">
<input class="controls-checkbox" title="{{ _('Enable debug logging') }}" type="checkbox"
data-bind="checked: settings.settings.plugins.klipper.configuration.debug_logging" />
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Klipper Config File') }}</label>
</div>
<div class="control-group border">
<label class="control-label">{{ _('Config Editor') }}</label>
<div class="controls">
<input type="text" class="input-block-level" data-bind="value: settings.settings.plugins.klipper.configuration.configpath">
<label class="checkbox" title="{{ _('Check parsing on save') }}"><input type="checkbox"
data-bind="checked: settings.settings.plugins.klipper.configuration.parse_check" /> {{ _('Check parsing on save') }}
</label>
<label title="{{ _('Fontsize') }}"><input type="text"
class="input-block-level span1" data-bind="value: settings.settings.plugins.klipper.configuration.fontsize,
event: { change: checkFontsize}" /> {{ _('Fontsize') }}
</label>
</div>
</div>
<div class="control-group">
</div>
<div class="control-group">
<label class="control-label">{{ _('Klipper Config Directory') }}</label>
<div class="controls">
<input type="text" class="input-block-level" data-bind="value: settings.settings.plugins.klipper.configuration.configpath" />
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Klipper Log File') }}</label>
<div class="controls">
<input type="text" class="input-block-level" data-bind="value: settings.settings.plugins.klipper.configuration.logpath">
<input type="text" class="input-block-level" data-bind="value: settings.settings.plugins.klipper.configuration.logpath" />
</div>
</div>
<div class="control-group">
</div>
<div class="control-group border">
<label class="control-label">{{ _('Configuration Reload Command') }}</label>
<div class="controls">
<select data-bind="value: settings.settings.plugins.klipper.configuration.reload_command">
<option value="RESTART">RESTART</option>
<option value="FIRMWARE_RESTART">FIRMWARE_RESTART</option>
<option value="manually">Manually</option>
</select>
<span class="help-block">
{{ _('The command that is executed when the Klipper configuration changed and needs to be reloaded.<br>
Set this to "Manually" if you don\'t want to immediately restart klipper.') }}
</span>
<select data-bind="value: settings.settings.plugins.klipper.configuration.reload_command">
<option value="RESTART">RESTART</option>
<option value="FIRMWARE_RESTART">FIRMWARE_RESTART</option>
</select>
<span class="help-block" style="margin-top:5px;">
{{ _('The command that is executed if you want to restart klipper.') }}
</span>
<label class="checkbox" title="{{ _('Reload klipper on editor save?') }}"><input type="checkbox"
data-bind="checked: settings.settings.plugins.klipper.configuration.restart_onsave" /> {{ _('Reload klipper on editor save?') }}
</label>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Config Backup') }}</label>
<div class="controls">
<button class="btn btn-small" data-bind='click: showBackupsDialog' title="{{ _('Show Backups') }}">
<i class="fas fa-upload"></i> {{ _('Show Backups') }}
</button>
</div>
</div>
</div>
</div>
<!-- Macros -->
<div class="tab-pane" id="macros">
<div class="control-group" style="margin-bottom: 0px;">
<div class="controls" style="margin-left: 82px;">
<!-- Macros -->
<div class="tab-pane" id="macros">
<div class="control-group" style="margin-bottom: 0px;">
<div class="controls" style="margin-left: 82px;">
<div class="row-fluid">
<div class="span8" style="text-align: right"><small>{{ _('Add macro button to:') }}</small></div>
<div class="span1" style="margin: auto;text-align: center"><small>{{ _('Klipper Tab') }}</small></div>
<div class="span2" style="margin: auto;"><small>{{ _('Sidebar') }}</small></div>
</div>
</div>
</div>
<div data-bind="foreach: settings.settings.plugins.klipper.macros">
<div class="control-group" id="item">
<label class="control-label">{{ _('Name') }}</label>
<div class="controls" style="margin-left: 82px;">
<div class="row-fluid">
<div class="span8">
<input type="text" class="input-block-level" data-bind="value: name"/>
</div>
<div class="span1" style="margin: auto; text-align: center;">
<input title="{{ _('Klipper Tab') }}" style="margin: auto;" type="checkbox" class="input-block-level" data-bind="checked: tab"/>
</div>
<div class="span1" style="margin: auto; text-align: center;">
<input title="{{ _('Sidebar') }}" style="margin: auto;" type="checkbox" class="input-block-level" data-bind="checked: sidebar"/>
</div>
<div class="span2" style="margin: auto; text-align: center;">
<a href='#' style="vertical-align: bottom;" data-bind='click: $parent.moveMacroUp' class="fa fa-chevron-up"></a>
<a href='#' style="vertical-align: bottom;" data-bind='click: $parent.moveMacroDown' class="fa fa-chevron-down"></a>
<a href='#' style="vertical-align: bottom;" data-bind='click: $parent.removeMacro' class="fa fa-trash-o"></a>
</div>
</div>
</div>
<label class="control-label">{{ _('Command') }}</label>
<div class="controls" style="margin-left: 82px;">
<div class="row-fluid">
<div class="span12" style="margin-top:2px;">
<textarea rows="2" class="block" data-bind="value: macro">
</textarea>
</div>
</div>
</div>
</div>
</div>
<div class="control-group">
<div class="controls">
<a href='#' data-bind='click: addMacro' title="{{ _('Add Macro') }}" class="fa fa-plus-circle"></a> {{ _('Add Macro') }}
</div>
</div>
<div class="control-group">
<span class="help-block">
{{ _('To show a dialog that asks for parameters you can write your macro like in the following example:') }}<br>
</span>
</div>
<div class="control-group">
<pre>
PID_CALIBRATE
HEATER={label:Heater, default:extruder, options:extruder|extruder1}
TARGET={label:Target Temperature, unit:°C, default:190}
WRITE_FILE={label:Write to File, default:0, options:0|1}
</pre>
</div>
</div>
<!-- Leveling -->
<div class="tab-pane" id="level">
<div class="control-group">
<span class="help-block">
{{ _('This feature assists in manually leveling you print bed by moving the head to the defined points in sequence.<br>
If you use a piece of paper for leveling, set "Probe Height" to the paper thickness eg. "0.1".') }}
</span>
</div>
<div class="control-group">
<label class="control-label">{{ _('Probe Height') }}</label>
<div class="controls">
<div class="input-append">
<input type="text" class="input-block-level span3" data-bind="value: settings.settings.plugins.klipper.probe.height">
<span class="add-on">mm</span>
</div>
<span class="help-inline">{{ _('Z-height to probe at') }}</span>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Probe Lift') }}</label>
<div class="controls">
<div class="input-append">
<input type="text" class="input-block-level span3" data-bind="value: settings.settings.plugins.klipper.probe.lift">
<span class="add-on">mm</span>
</div>
<span class="help-inline">{{ _('Lift Head by this amount before moving.') }}</span>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Probe Feedrate Z') }}</label>
<div class="controls">
<div class="input-append">
<input type="text" class="input-block-level span3" data-bind="value: settings.settings.plugins.klipper.probe.speed_z">
<span class="add-on">mm/min</span>
</div>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Feedrate X/Y') }}</label>
<div class="controls">
<div class="input-append">
<input type="text" class="input-block-level span3" data-bind="value: settings.settings.plugins.klipper.probe.speed_xy">
<span class="add-on">mm/min</span>
</div>
</div>
</div>
<div class="control-group">
<h5>{{ _('Probe Points') }}</h5>
<div class="controls">
<div class="row-fluid">
<div class="span3">name</div>
<div class="span3">x(mm)</div>
<div class="span3">y(mm)</div>
<div class="span3"> </div>
</div>
</div>
</div>
<div data-bind="foreach: settings.settings.plugins.klipper.probe.points" class="control-group">
<label class="control-label" data-bind="text: $index"></label>
<div class="controls">
<div class="row-fluid">
<div class="span3"><input type="text" class="input-block-level" data-bind="value: name"></div>
<div class="span3"><input type="text" class="input-block-level" data-bind="value: x"></div>
<div class="span3"><input type="text" class="input-block-level" data-bind="value: y"></div>
<div class="span3">
<a href='#' data-bind='click: $parent.moveProbePointUp' class="fa fa-chevron-up"></a>
<a href='#' data-bind='click: $parent.moveProbePointDown' class="fa fa-chevron-down"></a>
<a href='#' data-bind='click: $parent.removeProbePoint' class="fa fa-trash-o"></a>
<div data-bind="foreach: settings.settings.plugins.klipper.macros">
<div class="control-group" id="item">
<label class="control-label">{{ _('Name') }}</label>
<div class="controls" style="margin-left: 82px;">
<div class="row-fluid">
<div class="span8">
<input type="text" class="input-block-level" data-bind="value: name" />
</div>
<div class="span1" style="margin: auto; text-align: center;">
<input title="{{ _('Klipper Tab') }}" style="margin: auto;" type="checkbox" class="input-block-level" data-bind="checked: tab" />
</div>
<div class="span1" style="margin: auto; text-align: center;">
<input title="{{ _('Sidebar') }}" style="margin: auto;" type="checkbox" class="input-block-level" data-bind="checked: sidebar" />
</div>
<div class="span2" style="margin: auto; text-align: center;">
<a href='#' style="vertical-align: bottom;" data-bind='click: $parent.moveMacroUp' class="fa fa-chevron-up"></a>
<a href='#' style="vertical-align: bottom;" data-bind='click: $parent.moveMacroDown' class="fa fa-chevron-down"></a>
<a href='#' style="vertical-align: bottom;" data-bind='click: $parent.removeMacro' class="fa fa-trash-o"></a>
</div>
</div>
</div>
<label class="control-label">{{ _('Command') }}</label>
<div class="controls" style="margin-left: 82px;">
<div class="row-fluid">
<div class="span12" style="margin-top:2px;">
<textarea rows="2" class="block" data-bind="value: macro">
</textarea>
</div>
</div>
</div>
</div>
</div>
<div class="control-group">
<div class="controls">
<a href='#' data-bind='click: addMacro' title="{{ _('Add Macro') }}" class="fa fa-plus-circle"></a> {{ _('Add Macro') }}
</div>
</div>
<div class="control-group">
<span class="help-block">
{{ _('To show a dialog that asks for parameters you can write your macro like in the following example:') }}<br />
</span>
</div>
<div class="control-group">
<pre>
PID_CALIBRATE
HEATER={label:Heater, default:extruder, options:extruder|extruder1}
TARGET={label:Target Temperature, unit:°C, default:190}
WRITE_FILE={label:Write to File, default:0, options:0|1}
</pre>
</div>
</div>
<div class="control-group">
<div class="controls">
<a href='#' data-bind="click: addProbePoint" title="{{ _('Add Point') }}" class="fa fa-plus-circle"></a> {{ _('Add Point') }}
<!-- Leveling -->
<div class="tab-pane" id="level">
<div class="control-group">
<span class="help-block">
{{ _('This feature assists in manually leveling you print bed by moving the head to the defined points in
sequence.<br />
If you use a piece of paper for leveling, set "Probe Height" to the paper thickness eg. "0.1".') }}
</span>
</div>
<div class="control-group">
<label class="control-label">{{ _('Probe Height') }}</label>
<div class="controls">
<div class="input-append">
<input type="text" class="input-block-level span3" data-bind="value: settings.settings.plugins.klipper.probe.height" />
<span class="add-on">mm</span>
</div>
<span class="help-inline">{{ _('Z-height to probe at') }}</span>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Probe Lift') }}</label>
<div class="controls">
<div class="input-append">
<input type="text" class="input-block-level span3" data-bind="value: settings.settings.plugins.klipper.probe.lift" />
<span class="add-on">mm</span>
</div>
<span class="help-inline">{{ _('Lift Head by this amount before moving.') }}</span>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Probe Feedrate Z') }}</label>
<div class="controls">
<div class="input-append">
<input type="text" class="input-block-level span3" data-bind="value: settings.settings.plugins.klipper.probe.speed_z" />
<span class="add-on">mm/min</span>
</div>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Feedrate X/Y') }}</label>
<div class="controls">
<div class="input-append">
<input type="text" class="input-block-level span3" data-bind="value: settings.settings.plugins.klipper.probe.speed_xy" />
<span class="add-on">mm/min</span>
</div>
</div>
</div>
<div class="control-group">
<h5>{{ _('Probe Points') }}</h5>
<div class="controls">
<div class="row-fluid">
<div class="span3">name</div>
<div class="span3">x(mm)</div>
<div class="span3">y(mm)</div>
<div class="span3"> </div>
</div>
</div>
</div>
<div data-bind="foreach: settings.settings.plugins.klipper.probe.points" class="control-group">
<label class="control-label" data-bind="text: $index"></label>
<div class="controls">
<div class="row-fluid">
<div class="span3"><input type="text" class="input-block-level" data-bind="value: name" /></div>
<div class="span3"><input type="text" class="input-block-level" data-bind="value: x" /></div>
<div class="span3"><input type="text" class="input-block-level" data-bind="value: y" /></div>
<div class="span3">
<a href='#' data-bind='click: $parent.moveProbePointUp' class="fa fa-chevron-up"></a>
<a href='#' data-bind='click: $parent.moveProbePointDown' class="fa fa-chevron-down"></a>
<a href='#' data-bind='click: $parent.removeProbePoint' class="fa fa-trash-o"></a>
</div>
</div>
</div>
</div>
<div class="control-group">
<div class="controls">
<a href='#' data-bind="click: addProbePoint" title="{{ _('Add Point') }}" class="fa fa-plus-circle"></a> {{_ ('Add Point') }}
</div>
</div>
</div>
<!-- Klipper Conf -->
<div class="tab-pane" id="conf">
<div class="klipper-column-fluid klipper-settings-tab">
<h3 class="text-center m-0">{{ _('Config Files') }}</h3>
<div class="klipper-row-fluid">
<div class="klipper-fluid-item-2" data-bind="visible: $root.klipperViewModel.hasRightKo('CONFIG')">
<div class="btn-group">
<button class="btn btn-small dropdown-toggle" data-toggle="dropdown"><i class="far fa-square"></i>
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li><a href="javascript:void(0)" data-bind="click: markFilesOnPage">{{ _('Select all on this page') }}</a></li>
<li><a href="javascript:void(0)" data-bind="click: markAllFiles">{{ _('Select all') }}</a></li>
<li class="divider"></li>
<li><a href="javascript:void(0)" data-bind="click: clearMarkedFiles">{{ _('Clear selection') }}</a></li>
</ul>
</div>
<button class="btn btn-small" data-bind="click: newFile" title="{{ _('Add new File') }}">
<i class="far fa-file"></i> {{ _('New File') }}
</button>
<button class="btn btn-small" data-bind="click: showEditor" title="{{ _('Open last config') }}">
<i class="fas fa-file-code"></i> {{ _('Open last Editor') }}
</button>
<button class="btn btn-small" data-bind="click: listCfgFiles" title="{{ _('Refresh file list') }}">
<i class="icon-refresh"></i> {{ _('Refresh Files') }}
</button>
<button class="btn btn-small"
data-bind="click: removeMarkedFiles, enable: markedForFileRemove().length > 0">{{ _('Delete selected') }}</button>
</div>
<div class="pull-right">
<div class="btn-group">
<button class="btn btn-small dropdown-toggle" data-toggle="dropdown"><i class="fas fa-wrench"></i> <span class="caret"></span></button>
<ul class="dropdown-menu dropdown-menu-right">
<li><a href="javascript:void(0)" data-bind="click: function() { configs.changeSorting('name'); }"><i class="fas fa-check"
data-bind="style: {visibility: configs.currentSorting() == 'name' ? 'visible' : 'hidden'}"></i> {{ _('Sort by name') }}
({{ _('ascending') }})</a></li>
<li><a href="javascript:void(0)" data-bind="click: function() { configs.changeSorting('date'); }"><i class="fas fa-check"
data-bind="style: {visibility: configs.currentSorting() == 'date' ? 'visible' : 'hidden'}"></i> {{ _('Sort by date') }}
({{ _('descending') }})</a></li>
<li><a href="javascript:void(0)" data-bind="click: function() { configs.changeSorting('size'); }"><i class="fas fa-check"
data-bind="style: {visibility: configs.currentSorting() == 'size' ? 'visible' : 'hidden'}"></i> {{ _('Sort by file size') }}
({{ _('descending') }})</a></li>
</ul>
</div>
</div>
</div>
<div class="scroll-y">
<table class="table table-striped table-hover table-condensed table-hover table-fixed" id="klipper_cfg_files">
<thead>
<tr>
<th class="klipper_cfgs_checkbox span1"></th>
<th class="klipper_cfgs_name">{{ _('Name') }}</th>
<th class="klipper_cfgs_size">{{ _('Size') }}</th>
<th class="klipper_cfgs_action">{{ _('Action') }}</th>
</tr>
</thead>
<tbody data-bind="foreach: configs.paginatedItems">
<tr data-bind="attr: {title: name}">
<td class="klipper_cfgs_checkbox">
<input type="checkbox"
data-bind="value: name, checked: $root.markedForFileRemove, invisible: !$root.klipperViewModel.hasRightKo('CONFIG')">
</td>
<td class="klipper_cfgs_name" data-bind="text: name"></td>
<td class="klipper_cfgs_size" data-bind="text: size"></td>
<td class="klipper_cfgs_action">
<a href="javascript:void(0)" class="far fa-trash-alt" title="{{ _('Delete') }}"
data-bind="css: {disabled: !$root.klipperViewModel.hasRightKo('CONFIG')()}, click: function() { $parent.removeCfg($data.name);}"></a>
&nbsp;|&nbsp;
<a href="javascript:void(0)" class="fas fa-download" title="{{ _('Download') }}"
data-bind="css: {disabled: !$root.klipperViewModel.hasRightKo('CONFIG')()}, attr: { href: ($root.klipperViewModel.hasRightKo('CONFIG')()) ? $data.url : 'javascript:void(0)'}"></a>
&nbsp;|&nbsp;
<a href="javascript:void(0)" class="fas fa-pencil-alt" title="{{ _('Edit') }}"
data-bind="css: {disabled: !$root.klipperViewModel.hasRightKo('CONFIG')()}, click: function() { $parent.openConfig($data.name);}"></a>
</td>
</tr>
</tbody>
</table>
</div>
<div class="pagination pagination-mini pagination-centered">
<ul>
<li data-bind="css: {disabled: configs.currentPage() === 0}"><a href="javascript:void(0)" data-bind="click: configs.prevPage">«</a></li>
</ul>
<ul data-bind="foreach: configs.pages">
<li data-bind="css: {active: $data.number === $root.configs.currentPage(), disabled: $data.number === -1 }">
<a href="javascript:void(0)" data-bind="text: $data.text, click: function() { $root.configs.changePage($data.number); }"></a>
</li>
</ul>
<ul>
<li data-bind="css: {disabled: configs.currentPage() === configs.lastPage()}">
<a href="javascript:void(0)"
data-bind="click: configs.nextPage">»
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Klipper Conf -->
<div class="tab-pane" id="conf">
<div class="control-group">
<script src="plugin/klipper/static/js/lib/ace/ace.min.js" type="text/javascript" charset="utf-8"></script>
<script src="plugin/klipper/static/js/lib/ace/theme-monokai.min.js" type="text/javascript" charset="utf-8"></script>
<script src="plugin/klipper/static/js/lib/ace/mode-klipper_config.js" type="text/javascript"></script>
<div class="editor-controls">
<button class="btn btn-small" data-bind="click: loadLastSession"
title="{{ _('Reload last changes') }}">
<i class="fas fa-redo"></i> {{ _('Reload last changes') }}
</button>
<button class="btn btn-small" data-bind='click: reloadFromFile'
title="{{ _('Reload from file') }}">
<i class="fas fa-upload"></i> {{ _('Reload from file') }}
</button>
<label class="inline"><input class="inline-checkbox" type="checkbox" data-bind="checked: settings.settings.plugins.klipper.configuration.parse_check"> {{ _('Check parsing on save') }}</label>
&nbsp;&nbsp;
<a href='#' data-bind="click: minusFontsize" title="{{ _('Decrease Fontsize') }}" class="fas fa-search-minus"></a>
<a href='#' data-bind="click: plusFontsize" title="{{ _('Increase Fontsize') }}" class="fas fa-search-plus"></a>
</div>
<div class="conf-editor">
<input id="hdnLoadKlipperConfig" type="hidden" data-bind="value: configBound(settings.settings.plugins.klipper.config)" />
<div id="plugin-klipper-config"></div>
</div>
</div>
<div class="tab-footer">
<a href="https://www.paypal.com/donate/?business=7P63W664NF8LA&item_name=OctoKlipper" class="btn btn-mini" target="_blank">
<i class="fab fa-paypal"> {{ _('Donate') }}</i>
</a>
</div>
</div>
</form>

View File

@ -3,7 +3,7 @@
<label for="connection_printers" data-bind="css: {disabled: !connectionState.isErrorOrClosed()}, enable: connectionState.isErrorOrClosed() && loginState.isUser()">{{ _('Printer Profile') }}</label>
<select id="connection_printers" data-bind="options: connectionState.printerOptions, optionsText: 'name', optionsValue: 'id', value: connectionState.selectedPrinter, css: {disabled: !connectionState.isErrorOrClosed()}, enable: connectionState.isErrorOrClosed() && loginState.isUser()"></select>
<button class="btn btn-block" data-bind="click: connectionState.connect, text: connectionState.buttonText(), enable: loginState.isUser()">{{ _('Connect') }}</button>
<button class="btn btn-block" data-bind="visible: hasRight('CONFIG', 'Ko'), click: function() {openOctoKlipperSettings('klipper-config');}">{{ _('Open Klipper config') }}</button>
<button class="btn btn-block" title="{{ _('Open Editor') }}" data-bind="visible: $root.loginState.hasPermissionKo($root.access.permissions.PLUGIN_KLIPPER_CONFIG), click: showEditorDialog">{{ _('Open Editor') }}</button>
</div>
</div>
<!-- ko if: settings.settings.plugins.klipper.configuration.shortStatus_sidebar -->
@ -11,7 +11,7 @@
<a title="{{ _('Go to OctoKlipper Tab') }}" data-bind="text: shortStatus_sidebar, click: navbarClicked"></a>
</div>
<!-- /ko -->
<div class="control-group" data-bind="visible: hasRight('MACRO', 'Ko')">
<div class="control-group" data-bind="visible: $root.loginState.hasPermissionKo($root.access.permissions.PLUGIN_KLIPPER_MACRO)">
<div class="controls">
<label class="control-label small"><i class="icon-list-alt"></i> {{ _('Macros') }}</label>
<div data-bind="foreach: settings.settings.plugins.klipper.macros">

View File

@ -8,11 +8,7 @@
</div>
</div>
&nbsp;
<button
class="btn btn-mini pull-right clear-btn"
data-bind="click: onClearLog"
title="{{ _('Clear Log') }}"
>
<button class="btn btn-mini pull-right clear-btn" data-bind="click: onClearLog" title="{{ _('Clear Log') }}">
<i class="fa fa-trash"></i> {{ _('Clear Log') }}
</button>
</div>
@ -21,89 +17,63 @@
<div class="control-group">
<div class="controls">
<label class="control-label"></label>
<button
class="btn btn-block btn-small"
data-bind="click: onGetStatus, enable: isActive()"
title="{{ _('Query Klipper for its current status') }}"
>
<button class="btn btn-block btn-small" data-bind="click: onGetStatus, enable: isActive()"
title="{{ _('Query Klipper for its current status') }}">
<i class="fa icon-black fa-info-circle"></i> {{ _("Get Status") }}
</button>
<button
class="btn btn-block btn-small"
data-bind="visible: hasRight('CONFIG', 'Ko'), click: function() {openOctoKlipperSettings('klipper-config');}"
title="{{ _('Open the Klipper configuration file') }}"
>
<button class="btn btn-block btn-small"
data-bind="visible: $root.loginState.hasPermissionKo($root.access.permissions.PLUGIN_KLIPPER_CONFIG), click: openOctoKlipperSettings('klipper-basic')"
title="{{ _('Open the OctoKlipper Settings') }}">
{{ _("Open OctoKlipper Settings") }}
</button>
<button class="btn btn-block btn-small"
data-bind="visible: $root.loginState.hasPermissionKo($root.access.permissions.PLUGIN_KLIPPER_CONFIG), click: showEditorDialog"
title="{{ _('Show the Editor') }}">
<i class="fa icon-black fa-file-code-o"></i>
{{ _("Open Klipper config") }}
{{ _("Show Editor") }}
</button>
</div>
</div>
<div class="control-group">
<div class="controls">
<label class="control-label small"
><i class="icon-refresh"></i> {{ _("Restart") }}</label
>
<button
class="btn btn-block btn-small"
data-bind="click: onRestartHost, enable: isActive()"
title="{{ _('This will cause the host software to reload its config and perform an internal reset') }}"
>
<label class="control-label small"><i class="icon-refresh"></i> {{ _("Restart") }}</label>
<button class="btn btn-block btn-small" data-bind="click: onRestartHost, enable: isActive()"
title="{{ _('This will cause the host software to reload its config and perform an internal reset') }}">
{{ _("Host") }}
</button>
<button
class="btn btn-block btn-small"
data-bind="click: onRestartFirmware, enable: isActive()"
title="{{ _('Similar to a host restart, but also clears any error state from the micro-controller') }}"
>
<button class="btn btn-block btn-small" data-bind="click: onRestartFirmware, enable: isActive()"
title="{{ _('Similar to a host restart, but also clears any error state from the micro-controller') }}">
{{ _("Firmware") }}
</button>
</div>
</div>
<div class="control-group">
<div class="controls">
<label class="control-label"
><i class="icon-wrench"></i> {{ _("Tools") }}</label
>
<button
class="btn btn-block btn-small"
data-bind="click: showLevelingDialog, enable: isActive()"
title="{{ _('Assists in manually leveling your printbed by moving the head to a configurable set of positions in sequence.') }}"
>
<label class="control-label"><i class="icon-wrench"></i> {{ _("Tools") }}</label>
<button class="btn btn-block btn-small" data-bind="click: showLevelingDialog, enable: isActive()"
title="{{ _('Assists in manually leveling your printbed by moving the head to a configurable set of positions in sequence.') }}">
{{ _("Assisted Bed Leveling") }}
</button>
<button
class="btn btn-block btn-small"
data-bind="click: showPidTuningDialog, enable: isActive()"
title="{{ _('Determines optimal PID parameters by heat cycling the hotend/bed.') }}"
>
<button class="btn btn-block btn-small" data-bind="click: showPidTuningDialog, enable: isActive()"
title="{{ _('Determines optimal PID parameters by heat cycling the hotend/bed.') }}">
{{ _("PID Tuning") }}
</button>
<button
class="btn btn-block btn-small"
data-bind="click: showOffsetDialog, enable: isActive()"
title="{{ _('Sets a offset for subsequent GCODE coordinates.') }}"
>
<button class="btn btn-block btn-small" data-bind="click: showOffsetDialog, enable: isActive()"
title="{{ _('Sets a offset for subsequent GCODE coordinates.') }}">
{{ _("Coordinate Offset") }}
</button>
<button
class="btn btn-block btn-small"
data-bind="click: showGraphDialog"
title="{{ _('Assists in debugging performance issues by analyzing the Klipper log files.') }}"
>
<button class="btn btn-block btn-small" data-bind="click: showGraphDialog"
title="{{ _('Assists in debugging performance issues by analyzing the Klipper log files.') }}">
{{ _("Analyze Klipper Log") }}
</button>
</div>
</div>
<div class="controls" data-bind="visible: hasRight('MACRO', 'Ko')">
<label class="control-label"
><i class="icon-list-alt"></i> {{ _("Macros") }}</label
>
<div class="controls" data-bind="visible: $root.loginState.hasPermissionKo($root.access.permissions.PLUGIN_KLIPPER_MACRO)">
<label class="control-label"><i class="icon-list-alt"></i> {{ _("Macros") }}</label>
<div data-bind="foreach: settings.settings.plugins.klipper.macros">
<!-- ko if: tab -->
<button
class="btn btn-block btn-small"
data-bind="text: name, click: $parent.executeMacro, enable: $parent.isActive()"
></button>
<button class="btn btn-block btn-small" data-bind="text: name, click: $parent.executeMacro, enable: $parent.isActive()">
</button>
<!-- /ko -->
</div>
</div>

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: OctoKlipper 0.3.8.2\n"
"Report-Msgid-Bugs-To: i18n@octoprint.org\n"
"POT-Creation-Date: 2021-05-13 18:15+0200\n"
"POT-Creation-Date: 2021-09-17 08:18+0200\n"
"PO-Revision-Date: 2021-05-13 17:32+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: de\n"
@ -17,16 +17,331 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.9.0\n"
"Generated-By: Babel 2.9.1\n"
#: octoprint_klipper/__init__.py:87
#: octoprint_klipper/__init__.py:93
msgid "Allows to config klipper"
msgstr "Erlaubt Klipper zu konfigurieren"
#: octoprint_klipper/__init__.py:95
#: octoprint_klipper/__init__.py:101
msgid "Allows to use klipper macros"
msgstr "Erlaubt Makros zu benutzen"
#: octoprint_klipper/__init__.py:608
msgid "Restart Klipper"
msgstr "Neustart Klipper"
#: octoprint_klipper/__init__.py:609
msgid "You are about to restart Klipper!"
msgstr "Sie sind dabei Klipper neuzustarten!"
#: octoprint_klipper/__init__.py:609
msgid "This will stop ongoing prints!"
msgstr "Dies wird angehende Aufträge abbrechen!"
#: octoprint_klipper/cfgUtils.py:95
msgid "File not found!"
msgstr "Datei nicht gefunden!"
#: octoprint_klipper/static/js/klipper.js:259
msgid "All ongoing Prints will be stopped!"
msgstr "Alle laufende Drucke werden gestoppt!"
#: octoprint_klipper/static/js/klipper.js:262
msgid "Klipper restart?"
msgstr "Klipper neustarten?"
#: octoprint_klipper/static/js/klipper.js:264
#: octoprint_klipper/static/js/klipper_backup.js:159
msgid "Proceed"
msgstr "Weiter"
#: octoprint_klipper/static/js/klipper_backup.js:126
#: octoprint_klipper/static/js/klipper_settings.js:124
#, python-format
msgid ""
"Failed to remove config %(name)s.</p><p>Please consult octoprint.log for "
"details.</p>"
msgstr ""
"Konnte config %(name)s nicht löschen. </p><p> Bitte im octoprint.log "
"nachsehen für weitere Details.</p>"
#: octoprint_klipper/static/js/klipper_backup.js:129
#: octoprint_klipper/static/js/klipper_settings.js:127
msgid "Could not remove config"
msgstr "Konnte Konfiguration nicht löschen"
#: octoprint_klipper/static/js/klipper_backup.js:138
#, python-format
msgid "You are about to delete backuped config file \"%(name)s\"."
msgstr "Sie sind dabei die gesicherte Konfigurationsdatei \"%(name)s\" zu löschen."
#: octoprint_klipper/static/js/klipper_backup.js:154
msgid "This will overwrite any file with the same name on the configpath."
msgstr ""
"Dies wird jede Datei mit dem gleichen Namen im Konfigurationsordner "
"überschreiben."
#: octoprint_klipper/static/js/klipper_backup.js:157
msgid "Are you sure you want to restore now?"
msgstr "Sind sie sicher jetzt wiederherzustellen?"
#: octoprint_klipper/static/js/klipper_backup.js:184
#, python-format
msgid "You are about to restore %(count)d backuped config files."
msgstr ""
"Sie sind dabei %(count)d gesicherte Konfigurationsdateien "
"wiederherzustellen."
#: octoprint_klipper/static/js/klipper_backup.js:199
#, python-format
msgid "You are about to delete %(count)d backuped config files."
msgstr "Sie sind dabei %(count)d gesicherte Konfigurationsdateien zu löschen."
#: octoprint_klipper/static/js/klipper_backup.js:209
msgid "Restoring klipper config files"
msgstr "Stelle Klipper Konfigdatei wieder her"
#: octoprint_klipper/static/js/klipper_backup.js:211
#, python-format
msgid "Restoring %(count)d backuped config files..."
msgstr "Stelle %(count)d gesicherte Konfigurationsdateien wieder her."
#: octoprint_klipper/static/js/klipper_backup.js:220
#, python-format
msgid "Restored %(filename)s..."
msgstr "%(filename)s wiederhergestellt..."
#: octoprint_klipper/static/js/klipper_backup.js:231
#, python-format
msgid "Restoring of %(filename)s failed, continuing..."
msgstr "Wiederherstellung von Datei %(filename)s gescheitert, setze fort..."
#: octoprint_klipper/static/js/klipper_backup.js:264
msgid "Deleting backup files"
msgstr "Lösche gesicherte Dateien"
#: octoprint_klipper/static/js/klipper_backup.js:265
#, python-format
msgid "Deleting %(count)d backup files..."
msgstr "Lösche %(count)d gesicherte Konfigurationsdateien..."
#: octoprint_klipper/static/js/klipper_backup.js:273
#: octoprint_klipper/static/js/klipper_settings.js:183
#, python-format
msgid "Deleted %(filename)s..."
msgstr "%(filename)s gelöscht..."
#: octoprint_klipper/static/js/klipper_backup.js:279
#: octoprint_klipper/static/js/klipper_settings.js:193
#, python-format
msgid "Deleting of %(filename)s failed, continuing..."
msgstr "Löschung von Datei %(filename)s gescheitert, setze fort... "
#: octoprint_klipper/static/js/klipper_editor.js:61
msgid "Your file seems to have changed."
msgstr "Datei scheint geändert worden zu sein."
#: octoprint_klipper/static/js/klipper_editor.js:64
msgid "Do you really want to close it?"
msgstr "Wollen sie wirklich schliessen?"
#: octoprint_klipper/static/js/klipper_editor.js:65
msgid "No"
msgstr "Nein"
#: octoprint_klipper/static/js/klipper_editor.js:66
msgid "Yes"
msgstr "Ja"
#: octoprint_klipper/static/js/klipper_editor.js:127
#: octoprint_klipper/static/js/klipper_editor.js:134
msgid "SyntaxCheck"
msgstr "Syntaxprüfung"
#: octoprint_klipper/static/js/klipper_editor.js:127
#, fuzzy
msgid "SyntaxCheck OK"
msgstr "Syntaxprüfung OK"
#: octoprint_klipper/static/js/klipper_editor.js:130
msgid "Syntax NOK"
msgstr "Syntax ist nicht ok!"
#: octoprint_klipper/static/js/klipper_editor.js:151
#: octoprint_klipper/static/js/klipper_editor.js:161
#: octoprint_klipper/templates/klipper_editor.jinja2:40
#: octoprint_klipper/templates/klipper_editor.jinja2:41
msgid "Save Config"
msgstr "Speichere Konfig"
#: octoprint_klipper/static/js/klipper_editor.js:151
msgid "File saved."
msgstr "Datei gespeichert."
#: octoprint_klipper/static/js/klipper_editor.js:157
#, fuzzy
msgid "File not saved!"
msgstr "Datei nicht gespeichert."
#: octoprint_klipper/static/js/klipper_editor.js:226
#: octoprint_klipper/static/js/klipper_editor.js:243
msgid "Reload File"
msgstr "Datei neuladen"
#: octoprint_klipper/static/js/klipper_editor.js:230
msgid "Reload Config"
msgstr "Datei neuladen"
#: octoprint_klipper/static/js/klipper_editor.js:230
msgid "File reloaded."
msgstr "Datei neugeladen."
#: octoprint_klipper/static/js/klipper_settings.js:136
#, python-format
msgid "You are about to delete config file \"%(name)s\"."
msgstr "Sie sind dabei Konfigurationsdatei \"%(name)s\" zu löschen."
#: octoprint_klipper/static/js/klipper_settings.js:163
#, python-format
msgid "You are about to delete %(count)d config files."
msgstr "Sie sind dabei %(count)d Konfigurationsdateien zu löschen."
#: octoprint_klipper/static/js/klipper_settings.js:173
msgid "Deleting config files"
msgstr "Lösche Konfigurationsdateien"
#: octoprint_klipper/static/js/klipper_settings.js:174
#, python-format
msgid "Deleting %(count)d config files..."
msgstr "Lösche %(count)d Konfigurationsdateien..."
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:5
msgid "Backups"
msgstr "Sicherungen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:17
#: octoprint_klipper/templates/klipper_settings.jinja2:240
msgid "Select all on this page"
msgstr "Wähle alle auf dieser Seite aus"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:20
#: octoprint_klipper/templates/klipper_settings.jinja2:241
msgid "Select all"
msgstr "Alles Auswählen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:25
#: octoprint_klipper/templates/klipper_settings.jinja2:243
msgid "Clear selection"
msgstr "Auswahl zurücksetzen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:29
#: octoprint_klipper/templates/klipper_settings.jinja2:252
msgid "Refresh file list"
msgstr "Aktualisiere Dateiliste"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:30
#: octoprint_klipper/templates/klipper_settings.jinja2:253
msgid "Refresh Files"
msgstr "Aktualisieren"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:34
msgid "Restore selected"
msgstr "Ausgewählte wiederherstellen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:36
#: octoprint_klipper/templates/klipper_settings.jinja2:256
msgid "Delete selected"
msgstr "Ausgewählte löschen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:46
#: octoprint_klipper/templates/klipper_settings.jinja2:263
msgid "Sort by name"
msgstr "Sortiere nach Name"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:47
#: octoprint_klipper/templates/klipper_settings.jinja2:264
msgid "ascending"
msgstr "aufwärts"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:50
#: octoprint_klipper/templates/klipper_settings.jinja2:266
msgid "Sort by date"
msgstr "Sortiere nach Datum"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:51
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:55
#: octoprint_klipper/templates/klipper_settings.jinja2:267
#: octoprint_klipper/templates/klipper_settings.jinja2:270
msgid "descending"
msgstr "abwärts"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:54
#: octoprint_klipper/templates/klipper_settings.jinja2:269
msgid "Sort by file size"
msgstr "Sortie nach Dateigröße"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:65
#: octoprint_klipper/templates/klipper_settings.jinja2:101
#: octoprint_klipper/templates/klipper_settings.jinja2:280
msgid "Name"
msgstr "Name"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:66
#: octoprint_klipper/templates/klipper_settings.jinja2:281
msgid "Size"
msgstr "Größe"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:67
#: octoprint_klipper/templates/klipper_settings.jinja2:282
msgid "Action"
msgstr "Aktion"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:78
#: octoprint_klipper/templates/klipper_settings.jinja2:294
msgid "Delete"
msgstr "Löschen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:81
msgid "Restore"
msgstr "Wiederherstellen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:84
#: octoprint_klipper/templates/klipper_settings.jinja2:297
msgid "Download"
msgstr "Runterladen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:87
msgid "Preview"
msgstr "Vorschau"
#: octoprint_klipper/templates/klipper_editor.jinja2:6
msgid "Decrease Fontsize"
msgstr "Verringere Schriftgröße"
#: octoprint_klipper/templates/klipper_editor.jinja2:9
msgid "Increase Fontsize"
msgstr "Vergrößere Schriftgröße"
#: octoprint_klipper/templates/klipper_editor.jinja2:28
msgid "Filename"
msgstr "Dateiname"
#: octoprint_klipper/templates/klipper_editor.jinja2:31
#: octoprint_klipper/templates/klipper_editor.jinja2:32
msgid "Reload from file"
msgstr "Datei neuladen"
#: octoprint_klipper/templates/klipper_editor.jinja2:34
#: octoprint_klipper/templates/klipper_editor.jinja2:35
msgid "Reload last version"
msgstr "Lade letzte Version"
#: octoprint_klipper/templates/klipper_editor.jinja2:37
#: octoprint_klipper/templates/klipper_editor.jinja2:38
msgid "Check Syntax"
msgstr "Syntax prüfen"
#: octoprint_klipper/templates/klipper_graph_dialog.jinja2:4
msgid "Performance Graph"
msgstr "Performancediagramm"
@ -56,7 +371,7 @@ msgid "Depending on the size of the log file this might take a while."
msgstr "Abhängig von der Größe der Logdatei kann es etwas dauern."
#: octoprint_klipper/templates/klipper_leveling_dialog.jinja2:4
#: octoprint_klipper/templates/klipper_tab_main.jinja2:72
#: octoprint_klipper/templates/klipper_tab_main.jinja2:55
msgid "Assisted Bed Leveling"
msgstr "Unterstützte Druckbett-Nivellierung"
@ -86,7 +401,7 @@ msgid "Go to OctoKlipper Tab"
msgstr "Gehe zum OctoKlipper Reiter"
#: octoprint_klipper/templates/klipper_offset_dialog.jinja2:4
#: octoprint_klipper/templates/klipper_tab_main.jinja2:86
#: octoprint_klipper/templates/klipper_tab_main.jinja2:63
msgid "Coordinate Offset"
msgstr "Koordinaten Versatz"
@ -111,7 +426,7 @@ msgid "OK"
msgstr "OK"
#: octoprint_klipper/templates/klipper_pid_tuning_dialog.jinja2:4
#: octoprint_klipper/templates/klipper_tab_main.jinja2:79
#: octoprint_klipper/templates/klipper_tab_main.jinja2:59
msgid "PID Tuning"
msgstr "PID Abstimmung"
@ -141,7 +456,7 @@ msgstr "Haupteinstellungen"
#: octoprint_klipper/templates/klipper_settings.jinja2:4
#: octoprint_klipper/templates/klipper_sidebar.jinja2:16
#: octoprint_klipper/templates/klipper_tab_main.jinja2:99
#: octoprint_klipper/templates/klipper_tab_main.jinja2:72
msgid "Macros"
msgstr "Makros"
@ -167,68 +482,87 @@ msgid "Show Short Messages"
msgstr "Zeige Kurzmeldungen"
#: octoprint_klipper/templates/klipper_settings.jinja2:27
#: octoprint_klipper/templates/klipper_settings.jinja2:28
msgid "on NavBar"
msgstr "auf Nav-leiste"
#: octoprint_klipper/templates/klipper_settings.jinja2:28
#: octoprint_klipper/templates/klipper_settings.jinja2:29
#: octoprint_klipper/templates/klipper_settings.jinja2:30
msgid "on SideBar"
msgstr "auf Seitenleiste"
#: octoprint_klipper/templates/klipper_settings.jinja2:32
#: octoprint_klipper/templates/klipper_settings.jinja2:34
#: octoprint_klipper/templates/klipper_settings.jinja2:36
msgid "Enable debug logging"
msgstr "Aktiviere Debugloging"
#: octoprint_klipper/templates/klipper_settings.jinja2:38
msgid "Klipper Config File"
msgstr "Klipper Konfigdatei"
#: octoprint_klipper/templates/klipper_settings.jinja2:41
msgid "Config Editor"
msgstr "Konfig Editor"
#: octoprint_klipper/templates/klipper_settings.jinja2:43
#: octoprint_klipper/templates/klipper_settings.jinja2:44
msgid "Check parsing on save"
msgstr "Prüfe Syntax beim speichern"
#: octoprint_klipper/templates/klipper_settings.jinja2:46
#: octoprint_klipper/templates/klipper_settings.jinja2:48
msgid "Fontsize"
msgstr "Fontgröße"
#: octoprint_klipper/templates/klipper_settings.jinja2:53
msgid "Klipper Config Directory"
msgstr "Klipper Konfig Pfad"
#: octoprint_klipper/templates/klipper_settings.jinja2:59
msgid "Klipper Log File"
msgstr "Klipper Logdatei"
#: octoprint_klipper/templates/klipper_settings.jinja2:50
#: octoprint_klipper/templates/klipper_settings.jinja2:65
msgid "Configuration Reload Command"
msgstr "Konfiguration Neustart Befehl"
#: octoprint_klipper/templates/klipper_settings.jinja2:58
msgid ""
"The command that is executed when the Klipper configuration changed and "
"needs to be reloaded.<br>\n"
" Set this to \"Manually\" if you don't want to immediately "
"restart klipper."
msgstr ""
"Der Befehl der ausgeführt wird nachdem die Klipper Konfiguration <br>\n"
"geändert wurde und neu geladen werden muss.<br>\n"
"Auf \"Manually\" setzen wenn man nicht gleich Klipper neustarten möchte."
#: octoprint_klipper/templates/klipper_settings.jinja2:72
msgid "The command that is executed if you want to restart klipper."
msgstr "Der Befehl, der ausgeführt wird, um Klipper neu zu starten."
#: octoprint_klipper/templates/klipper_settings.jinja2:69
#: octoprint_klipper/templates/klipper_settings.jinja2:74
#: octoprint_klipper/templates/klipper_settings.jinja2:75
msgid "Reload klipper on editor save?"
msgstr "Klipper neu starten nach speichern im Editor?"
#: octoprint_klipper/templates/klipper_settings.jinja2:80
msgid "Config Backup"
msgstr "Konfig Sicherungen"
#: octoprint_klipper/templates/klipper_settings.jinja2:82
#: octoprint_klipper/templates/klipper_settings.jinja2:83
msgid "Show Backups"
msgstr "Zeige Sicherungen"
#: octoprint_klipper/templates/klipper_settings.jinja2:93
msgid "Add macro button to:"
msgstr "Makroschaltfläche anzeigen auf:"
#: octoprint_klipper/templates/klipper_settings.jinja2:70
#: octoprint_klipper/templates/klipper_settings.jinja2:84
#: octoprint_klipper/templates/klipper_settings.jinja2:94
#: octoprint_klipper/templates/klipper_settings.jinja2:108
msgid "Klipper Tab"
msgstr "Klipper Reiter"
#: octoprint_klipper/templates/klipper_settings.jinja2:71
#: octoprint_klipper/templates/klipper_settings.jinja2:87
#: octoprint_klipper/templates/klipper_settings.jinja2:95
#: octoprint_klipper/templates/klipper_settings.jinja2:111
msgid "Sidebar"
msgstr "Seitenleiste"
#: octoprint_klipper/templates/klipper_settings.jinja2:77
msgid "Name"
msgstr "Name"
#: octoprint_klipper/templates/klipper_settings.jinja2:96
#: octoprint_klipper/templates/klipper_settings.jinja2:120
msgid "Command"
msgstr "Befehl"
#: octoprint_klipper/templates/klipper_settings.jinja2:109
#: octoprint_klipper/templates/klipper_settings.jinja2:133
msgid "Add Macro"
msgstr "Makro hinzufügen"
#: octoprint_klipper/templates/klipper_settings.jinja2:114
#: octoprint_klipper/templates/klipper_settings.jinja2:138
msgid ""
"To show a dialog that asks for parameters you can write your macro like "
"in the following example:"
@ -236,71 +570,79 @@ msgstr ""
"Um ein Dialog anzeigen zu lassen, welches nach Parametern fragt, <br>\n"
"kann man ein Makro wie im nächsten Beispiel schreiben:"
#: octoprint_klipper/templates/klipper_settings.jinja2:130
#: octoprint_klipper/templates/klipper_settings.jinja2:154
msgid ""
"This feature assists in manually leveling you print bed by moving the "
"head to the defined points in sequence.<br>\n"
" If you use a piece of paper for leveling, set \"Probe Height\""
" to the paper thickness eg. \"0.1\"."
"head to the defined points in\n"
" sequence.<br />\n"
" If you use a piece of paper for leveling, set \"Probe Height\" "
"to the paper thickness eg. \"0.1\"."
msgstr ""
"Diese Funktion hilft beim Einstellen des Druckbettes, <br>\n"
"da es die angegebenen Punkte automatisch nacheinander anfährt.<br>\n"
"Wenn man ein Papier zum messen nimmt, setzt man \"Messhöhe\" <br>\n"
"auf die Papierstärke zBsp.: \"0.1\"."
#: octoprint_klipper/templates/klipper_settings.jinja2:135
#: octoprint_klipper/templates/klipper_settings.jinja2:160
msgid "Probe Height"
msgstr "Messhöhe"
#: octoprint_klipper/templates/klipper_settings.jinja2:141
#: octoprint_klipper/templates/klipper_settings.jinja2:166
msgid "Z-height to probe at"
msgstr "Höhe bei der gemessen wird"
#: octoprint_klipper/templates/klipper_settings.jinja2:145
#: octoprint_klipper/templates/klipper_settings.jinja2:170
msgid "Probe Lift"
msgstr "Höhe für Seitenbewegung"
#: octoprint_klipper/templates/klipper_settings.jinja2:151
#: octoprint_klipper/templates/klipper_settings.jinja2:176
msgid "Lift Head by this amount before moving."
msgstr "Hebe Druckkopf auf diese Höhe vor einer Seitenbewegung"
#: octoprint_klipper/templates/klipper_settings.jinja2:155
#: octoprint_klipper/templates/klipper_settings.jinja2:180
msgid "Probe Feedrate Z"
msgstr "Geschwindigkeit Z"
#: octoprint_klipper/templates/klipper_settings.jinja2:164
#: octoprint_klipper/templates/klipper_settings.jinja2:189
msgid "Feedrate X/Y"
msgstr "Geschwindigkeit X/Y"
#: octoprint_klipper/templates/klipper_settings.jinja2:173
#: octoprint_klipper/templates/klipper_settings.jinja2:198
msgid "Probe Points"
msgstr "Messpunkte"
#: octoprint_klipper/templates/klipper_settings.jinja2:200
#: octoprint_klipper/templates/klipper_settings.jinja2:225
msgid "Add Point"
msgstr "Füge Messpunkt hinzu"
#: octoprint_klipper/templates/klipper_settings.jinja2:212
#: octoprint_klipper/templates/klipper_settings.jinja2:213
msgid "Reload last changes"
msgstr "Lade letzte Änderung"
#: octoprint_klipper/templates/klipper_settings.jinja2:232
msgid "Config Files"
msgstr "Konfig Dateien"
#: octoprint_klipper/templates/klipper_settings.jinja2:216
#: octoprint_klipper/templates/klipper_settings.jinja2:217
msgid "Reload from file"
msgstr "Datei neuladen"
#: octoprint_klipper/templates/klipper_settings.jinja2:246
msgid "Add new File"
msgstr "Erstelle neue Datei"
#: octoprint_klipper/templates/klipper_settings.jinja2:219
msgid "Check parsing on save"
msgstr "Prüfe Syntax beim speichern"
#: octoprint_klipper/templates/klipper_settings.jinja2:247
msgid "New File"
msgstr "Neue Datei"
#: octoprint_klipper/templates/klipper_settings.jinja2:221
msgid "Decrease Fontsize"
msgstr "Verringere Schriftgröße"
#: octoprint_klipper/templates/klipper_settings.jinja2:249
msgid "Open last config"
msgstr "Öffne letzte Konfig"
#: octoprint_klipper/templates/klipper_settings.jinja2:222
msgid "Increase Fontsize"
msgstr "Vergrößere Schriftgröße"
#: octoprint_klipper/templates/klipper_settings.jinja2:250
#, fuzzy
msgid "Open last Editor"
msgstr "Öffne Editor"
#: octoprint_klipper/templates/klipper_settings.jinja2:300
msgid "Edit"
msgstr "Bearbeiten"
#: octoprint_klipper/templates/klipper_settings.jinja2:329
msgid "Donate"
msgstr "Spenden"
#: octoprint_klipper/templates/klipper_sidebar.jinja2:3
msgid "Printer Profile"
@ -311,63 +653,71 @@ msgid "Connect"
msgstr "Verbinde"
#: octoprint_klipper/templates/klipper_sidebar.jinja2:6
#: octoprint_klipper/templates/klipper_tab_main.jinja2:37
msgid "Open Klipper config"
msgstr "Öffne Klipper Konfig"
msgid "Open Editor"
msgstr "Öffne Editor"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:3
msgid "Messages"
msgstr "Nachricht"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:14
#: octoprint_klipper/templates/klipper_tab_main.jinja2:16
#: octoprint_klipper/templates/klipper_tab_main.jinja2:11
#: octoprint_klipper/templates/klipper_tab_main.jinja2:12
msgid "Clear Log"
msgstr "Log löschen"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:27
#: octoprint_klipper/templates/klipper_tab_main.jinja2:21
msgid "Query Klipper for its current status"
msgstr "Aktuellen Status von Klipper anfordern"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:29
#: octoprint_klipper/templates/klipper_tab_main.jinja2:22
msgid "Get Status"
msgstr "Statusanfrage"
msgstr "Statusabfrage"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:34
#, fuzzy
msgid "Open the Klipper configuration file"
msgstr "Öffne Klipper Konfiguration"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:26
msgid "Open the OctoKlipper Settings"
msgstr "Öffne die OctoKlipper Einstellungen"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:44
#: octoprint_klipper/templates/klipper_tab_main.jinja2:27
msgid "Open OctoKlipper Settings"
msgstr "Öffne OctoKlipper Einstellungen"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:31
msgid "Show the Editor"
msgstr "Öffne Editor"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:33
msgid "Show Editor"
msgstr "Öffne Editor"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:39
msgid "Restart"
msgstr "Neustart"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:49
#: octoprint_klipper/templates/klipper_tab_main.jinja2:41
msgid ""
"This will cause the host software to reload its config and perform an "
"internal reset"
msgstr ""
"Das wird die Konfigurationsdatei neuladen und die Hostsoftware neustarten"
msgstr "Das wird die Konfigurationsdatei neuladen und die Hostsoftware neu starten"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:51
#: octoprint_klipper/templates/klipper_tab_main.jinja2:42
msgid "Host"
msgstr "Host"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:56
#: octoprint_klipper/templates/klipper_tab_main.jinja2:45
msgid ""
"Similar to a host restart, but also clears any error state from the "
"micro-controller"
msgstr ""
"Wie der Host Neustart, aber zusätzlich wird die Firmware neugestartet"
msgstr "Wie der Host Neustart, aber zusätzlich wird die Firmware neu gestartet"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:58
#: octoprint_klipper/templates/klipper_tab_main.jinja2:46
msgid "Firmware"
msgstr "Firmware"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:65
#: octoprint_klipper/templates/klipper_tab_main.jinja2:52
msgid "Tools"
msgstr "Tools"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:70
#: octoprint_klipper/templates/klipper_tab_main.jinja2:54
msgid ""
"Assists in manually leveling your printbed by moving the head to a "
"configurable set of positions in sequence."
@ -375,22 +725,24 @@ msgstr ""
"Unterstützt beim manuellem Bettnivellieren, <br>\n"
"da der Druckkopf nacheinander einstellbare Punkte anfährt."
#: octoprint_klipper/templates/klipper_tab_main.jinja2:77
#: octoprint_klipper/templates/klipper_tab_main.jinja2:58
msgid "Determines optimal PID parameters by heat cycling the hotend/bed."
msgstr "Findet optimale PID Parameter beim zyklischen Heizen des Druckkopfes/-bettes."
msgstr ""
"Findet optimale PID Parameter beim zyklischen Heizen des "
"Druckkopfes/-bettes."
#: octoprint_klipper/templates/klipper_tab_main.jinja2:84
#: octoprint_klipper/templates/klipper_tab_main.jinja2:62
msgid "Sets a offset for subsequent GCODE coordinates."
msgstr "Setze einen Versatz für alle weiteren GCODE Koordinaten"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:91
#: octoprint_klipper/templates/klipper_tab_main.jinja2:66
msgid ""
"Assists in debugging performance issues by analyzing the Klipper log "
"files."
msgstr ""
"Unterstützt, mit dem analysieren des Klipperlogs, beim Suchen von Performanceproblemen."
"Unterstützt, mit dem analysieren des Klipperlogs, beim Suchen von "
"Performanceproblemen."
#: octoprint_klipper/templates/klipper_tab_main.jinja2:93
#: octoprint_klipper/templates/klipper_tab_main.jinja2:67
msgid "Analyze Klipper Log"
msgstr "Analysiere Klipperlog"

41
octoprint_klipper/util.py Normal file
View File

@ -0,0 +1,41 @@
def poll_status(self):
self._printer.commands("STATUS")
def update_status(self, type, status):
send_message(self, "status", type, status, status)
def file_exist(self, filepath):
'''
Returns if a file exists and shows PopUp if not
'''
from os import path
if not path.isfile(filepath):
send_message(self, "PopUp", "warning", "OctoKlipper Settings",
"File: <br />" + filepath + "<br /> does not exist!")
return False
else:
return True
def key_exist(dict, key1, key2):
try:
dict[key1][key2]
except KeyError:
return False
else:
return True
def send_message(self, type, subtype, title, payload):
"""
Send Message over API to FrontEnd
"""
import datetime
self._plugin_manager.send_plugin_message(
self._identifier,
dict(
time=datetime.datetime.now().strftime("%H:%M:%S"),
type=type,
subtype=subtype,
title=title,
payload=payload
)
)

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: OctoKlipper 0.3.8.2\n"
"Report-Msgid-Bugs-To: i18n@octoprint.org\n"
"POT-Creation-Date: 2021-05-13 18:15+0200\n"
"POT-Creation-Date: 2021-09-17 08:18+0200\n"
"PO-Revision-Date: 2021-05-13 17:32+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: de\n"
@ -17,16 +17,331 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.9.0\n"
"Generated-By: Babel 2.9.1\n"
#: octoprint_klipper/__init__.py:87
#: octoprint_klipper/__init__.py:93
msgid "Allows to config klipper"
msgstr "Erlaubt Klipper zu konfigurieren"
#: octoprint_klipper/__init__.py:95
#: octoprint_klipper/__init__.py:101
msgid "Allows to use klipper macros"
msgstr "Erlaubt Makros zu benutzen"
#: octoprint_klipper/__init__.py:608
msgid "Restart Klipper"
msgstr "Neustart Klipper"
#: octoprint_klipper/__init__.py:609
msgid "You are about to restart Klipper!"
msgstr "Sie sind dabei Klipper neuzustarten!"
#: octoprint_klipper/__init__.py:609
msgid "This will stop ongoing prints!"
msgstr "Dies wird angehende Aufträge abbrechen!"
#: octoprint_klipper/cfgUtils.py:95
msgid "File not found!"
msgstr "Datei nicht gefunden!"
#: octoprint_klipper/static/js/klipper.js:259
msgid "All ongoing Prints will be stopped!"
msgstr "Alle laufende Drucke werden gestoppt!"
#: octoprint_klipper/static/js/klipper.js:262
msgid "Klipper restart?"
msgstr "Klipper neustarten?"
#: octoprint_klipper/static/js/klipper.js:264
#: octoprint_klipper/static/js/klipper_backup.js:159
msgid "Proceed"
msgstr "Weiter"
#: octoprint_klipper/static/js/klipper_backup.js:126
#: octoprint_klipper/static/js/klipper_settings.js:124
#, python-format
msgid ""
"Failed to remove config %(name)s.</p><p>Please consult octoprint.log for "
"details.</p>"
msgstr ""
"Konnte config %(name)s nicht löschen. </p><p> Bitte im octoprint.log "
"nachsehen für weitere Details.</p>"
#: octoprint_klipper/static/js/klipper_backup.js:129
#: octoprint_klipper/static/js/klipper_settings.js:127
msgid "Could not remove config"
msgstr "Konnte Konfiguration nicht löschen"
#: octoprint_klipper/static/js/klipper_backup.js:138
#, python-format
msgid "You are about to delete backuped config file \"%(name)s\"."
msgstr "Sie sind dabei die gesicherte Konfigurationsdatei \"%(name)s\" zu löschen."
#: octoprint_klipper/static/js/klipper_backup.js:154
msgid "This will overwrite any file with the same name on the configpath."
msgstr ""
"Dies wird jede Datei mit dem gleichen Namen im Konfigurationsordner "
"überschreiben."
#: octoprint_klipper/static/js/klipper_backup.js:157
msgid "Are you sure you want to restore now?"
msgstr "Sind sie sicher jetzt wiederherzustellen?"
#: octoprint_klipper/static/js/klipper_backup.js:184
#, python-format
msgid "You are about to restore %(count)d backuped config files."
msgstr ""
"Sie sind dabei %(count)d gesicherte Konfigurationsdateien "
"wiederherzustellen."
#: octoprint_klipper/static/js/klipper_backup.js:199
#, python-format
msgid "You are about to delete %(count)d backuped config files."
msgstr "Sie sind dabei %(count)d gesicherte Konfigurationsdateien zu löschen."
#: octoprint_klipper/static/js/klipper_backup.js:209
msgid "Restoring klipper config files"
msgstr "Stelle Klipper Konfigdatei wieder her"
#: octoprint_klipper/static/js/klipper_backup.js:211
#, python-format
msgid "Restoring %(count)d backuped config files..."
msgstr "Stelle %(count)d gesicherte Konfigurationsdateien wieder her."
#: octoprint_klipper/static/js/klipper_backup.js:220
#, python-format
msgid "Restored %(filename)s..."
msgstr "%(filename)s wiederhergestellt..."
#: octoprint_klipper/static/js/klipper_backup.js:231
#, python-format
msgid "Restoring of %(filename)s failed, continuing..."
msgstr "Wiederherstellung von Datei %(filename)s gescheitert, setze fort..."
#: octoprint_klipper/static/js/klipper_backup.js:264
msgid "Deleting backup files"
msgstr "Lösche gesicherte Dateien"
#: octoprint_klipper/static/js/klipper_backup.js:265
#, python-format
msgid "Deleting %(count)d backup files..."
msgstr "Lösche %(count)d gesicherte Konfigurationsdateien..."
#: octoprint_klipper/static/js/klipper_backup.js:273
#: octoprint_klipper/static/js/klipper_settings.js:183
#, python-format
msgid "Deleted %(filename)s..."
msgstr "%(filename)s gelöscht..."
#: octoprint_klipper/static/js/klipper_backup.js:279
#: octoprint_klipper/static/js/klipper_settings.js:193
#, python-format
msgid "Deleting of %(filename)s failed, continuing..."
msgstr "Löschung von Datei %(filename)s gescheitert, setze fort... "
#: octoprint_klipper/static/js/klipper_editor.js:61
msgid "Your file seems to have changed."
msgstr "Datei scheint geändert worden zu sein."
#: octoprint_klipper/static/js/klipper_editor.js:64
msgid "Do you really want to close it?"
msgstr "Wollen sie wirklich schliessen?"
#: octoprint_klipper/static/js/klipper_editor.js:65
msgid "No"
msgstr "Nein"
#: octoprint_klipper/static/js/klipper_editor.js:66
msgid "Yes"
msgstr "Ja"
#: octoprint_klipper/static/js/klipper_editor.js:127
#: octoprint_klipper/static/js/klipper_editor.js:134
msgid "SyntaxCheck"
msgstr "Syntaxprüfung"
#: octoprint_klipper/static/js/klipper_editor.js:127
#, fuzzy
msgid "SyntaxCheck OK"
msgstr "Syntaxprüfung OK"
#: octoprint_klipper/static/js/klipper_editor.js:130
msgid "Syntax NOK"
msgstr "Syntax ist nicht ok!"
#: octoprint_klipper/static/js/klipper_editor.js:151
#: octoprint_klipper/static/js/klipper_editor.js:161
#: octoprint_klipper/templates/klipper_editor.jinja2:40
#: octoprint_klipper/templates/klipper_editor.jinja2:41
msgid "Save Config"
msgstr "Speichere Konfig"
#: octoprint_klipper/static/js/klipper_editor.js:151
msgid "File saved."
msgstr "Datei gespeichert."
#: octoprint_klipper/static/js/klipper_editor.js:157
#, fuzzy
msgid "File not saved!"
msgstr "Datei nicht gespeichert."
#: octoprint_klipper/static/js/klipper_editor.js:226
#: octoprint_klipper/static/js/klipper_editor.js:243
msgid "Reload File"
msgstr "Datei neuladen"
#: octoprint_klipper/static/js/klipper_editor.js:230
msgid "Reload Config"
msgstr "Datei neuladen"
#: octoprint_klipper/static/js/klipper_editor.js:230
msgid "File reloaded."
msgstr "Datei neugeladen."
#: octoprint_klipper/static/js/klipper_settings.js:136
#, python-format
msgid "You are about to delete config file \"%(name)s\"."
msgstr "Sie sind dabei Konfigurationsdatei \"%(name)s\" zu löschen."
#: octoprint_klipper/static/js/klipper_settings.js:163
#, python-format
msgid "You are about to delete %(count)d config files."
msgstr "Sie sind dabei %(count)d Konfigurationsdateien zu löschen."
#: octoprint_klipper/static/js/klipper_settings.js:173
msgid "Deleting config files"
msgstr "Lösche Konfigurationsdateien"
#: octoprint_klipper/static/js/klipper_settings.js:174
#, python-format
msgid "Deleting %(count)d config files..."
msgstr "Lösche %(count)d Konfigurationsdateien..."
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:5
msgid "Backups"
msgstr "Sicherungen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:17
#: octoprint_klipper/templates/klipper_settings.jinja2:240
msgid "Select all on this page"
msgstr "Wähle alle auf dieser Seite aus"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:20
#: octoprint_klipper/templates/klipper_settings.jinja2:241
msgid "Select all"
msgstr "Alles Auswählen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:25
#: octoprint_klipper/templates/klipper_settings.jinja2:243
msgid "Clear selection"
msgstr "Auswahl zurücksetzen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:29
#: octoprint_klipper/templates/klipper_settings.jinja2:252
msgid "Refresh file list"
msgstr "Aktualisiere Dateiliste"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:30
#: octoprint_klipper/templates/klipper_settings.jinja2:253
msgid "Refresh Files"
msgstr "Aktualisieren"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:34
msgid "Restore selected"
msgstr "Ausgewählte wiederherstellen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:36
#: octoprint_klipper/templates/klipper_settings.jinja2:256
msgid "Delete selected"
msgstr "Ausgewählte löschen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:46
#: octoprint_klipper/templates/klipper_settings.jinja2:263
msgid "Sort by name"
msgstr "Sortiere nach Name"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:47
#: octoprint_klipper/templates/klipper_settings.jinja2:264
msgid "ascending"
msgstr "aufwärts"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:50
#: octoprint_klipper/templates/klipper_settings.jinja2:266
msgid "Sort by date"
msgstr "Sortiere nach Datum"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:51
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:55
#: octoprint_klipper/templates/klipper_settings.jinja2:267
#: octoprint_klipper/templates/klipper_settings.jinja2:270
msgid "descending"
msgstr "abwärts"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:54
#: octoprint_klipper/templates/klipper_settings.jinja2:269
msgid "Sort by file size"
msgstr "Sortie nach Dateigröße"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:65
#: octoprint_klipper/templates/klipper_settings.jinja2:101
#: octoprint_klipper/templates/klipper_settings.jinja2:280
msgid "Name"
msgstr "Name"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:66
#: octoprint_klipper/templates/klipper_settings.jinja2:281
msgid "Size"
msgstr "Größe"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:67
#: octoprint_klipper/templates/klipper_settings.jinja2:282
msgid "Action"
msgstr "Aktion"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:78
#: octoprint_klipper/templates/klipper_settings.jinja2:294
msgid "Delete"
msgstr "Löschen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:81
msgid "Restore"
msgstr "Wiederherstellen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:84
#: octoprint_klipper/templates/klipper_settings.jinja2:297
msgid "Download"
msgstr "Runterladen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:87
msgid "Preview"
msgstr "Vorschau"
#: octoprint_klipper/templates/klipper_editor.jinja2:6
msgid "Decrease Fontsize"
msgstr "Verringere Schriftgröße"
#: octoprint_klipper/templates/klipper_editor.jinja2:9
msgid "Increase Fontsize"
msgstr "Vergrößere Schriftgröße"
#: octoprint_klipper/templates/klipper_editor.jinja2:28
msgid "Filename"
msgstr "Dateiname"
#: octoprint_klipper/templates/klipper_editor.jinja2:31
#: octoprint_klipper/templates/klipper_editor.jinja2:32
msgid "Reload from file"
msgstr "Datei neuladen"
#: octoprint_klipper/templates/klipper_editor.jinja2:34
#: octoprint_klipper/templates/klipper_editor.jinja2:35
msgid "Reload last version"
msgstr "Lade letzte Version"
#: octoprint_klipper/templates/klipper_editor.jinja2:37
#: octoprint_klipper/templates/klipper_editor.jinja2:38
msgid "Check Syntax"
msgstr "Syntax prüfen"
#: octoprint_klipper/templates/klipper_graph_dialog.jinja2:4
msgid "Performance Graph"
msgstr "Performancediagramm"
@ -56,7 +371,7 @@ msgid "Depending on the size of the log file this might take a while."
msgstr "Abhängig von der Größe der Logdatei kann es etwas dauern."
#: octoprint_klipper/templates/klipper_leveling_dialog.jinja2:4
#: octoprint_klipper/templates/klipper_tab_main.jinja2:72
#: octoprint_klipper/templates/klipper_tab_main.jinja2:55
msgid "Assisted Bed Leveling"
msgstr "Unterstützte Druckbett-Nivellierung"
@ -86,7 +401,7 @@ msgid "Go to OctoKlipper Tab"
msgstr "Gehe zum OctoKlipper Reiter"
#: octoprint_klipper/templates/klipper_offset_dialog.jinja2:4
#: octoprint_klipper/templates/klipper_tab_main.jinja2:86
#: octoprint_klipper/templates/klipper_tab_main.jinja2:63
msgid "Coordinate Offset"
msgstr "Koordinaten Versatz"
@ -111,7 +426,7 @@ msgid "OK"
msgstr "OK"
#: octoprint_klipper/templates/klipper_pid_tuning_dialog.jinja2:4
#: octoprint_klipper/templates/klipper_tab_main.jinja2:79
#: octoprint_klipper/templates/klipper_tab_main.jinja2:59
msgid "PID Tuning"
msgstr "PID Abstimmung"
@ -141,7 +456,7 @@ msgstr "Haupteinstellungen"
#: octoprint_klipper/templates/klipper_settings.jinja2:4
#: octoprint_klipper/templates/klipper_sidebar.jinja2:16
#: octoprint_klipper/templates/klipper_tab_main.jinja2:99
#: octoprint_klipper/templates/klipper_tab_main.jinja2:72
msgid "Macros"
msgstr "Makros"
@ -167,68 +482,87 @@ msgid "Show Short Messages"
msgstr "Zeige Kurzmeldungen"
#: octoprint_klipper/templates/klipper_settings.jinja2:27
#: octoprint_klipper/templates/klipper_settings.jinja2:28
msgid "on NavBar"
msgstr "auf Nav-leiste"
#: octoprint_klipper/templates/klipper_settings.jinja2:28
#: octoprint_klipper/templates/klipper_settings.jinja2:29
#: octoprint_klipper/templates/klipper_settings.jinja2:30
msgid "on SideBar"
msgstr "auf Seitenleiste"
#: octoprint_klipper/templates/klipper_settings.jinja2:32
#: octoprint_klipper/templates/klipper_settings.jinja2:34
#: octoprint_klipper/templates/klipper_settings.jinja2:36
msgid "Enable debug logging"
msgstr "Aktiviere Debugloging"
#: octoprint_klipper/templates/klipper_settings.jinja2:38
msgid "Klipper Config File"
msgstr "Klipper Konfigdatei"
#: octoprint_klipper/templates/klipper_settings.jinja2:41
msgid "Config Editor"
msgstr "Konfig Editor"
#: octoprint_klipper/templates/klipper_settings.jinja2:43
#: octoprint_klipper/templates/klipper_settings.jinja2:44
msgid "Check parsing on save"
msgstr "Prüfe Syntax beim speichern"
#: octoprint_klipper/templates/klipper_settings.jinja2:46
#: octoprint_klipper/templates/klipper_settings.jinja2:48
msgid "Fontsize"
msgstr "Fontgröße"
#: octoprint_klipper/templates/klipper_settings.jinja2:53
msgid "Klipper Config Directory"
msgstr "Klipper Konfig Pfad"
#: octoprint_klipper/templates/klipper_settings.jinja2:59
msgid "Klipper Log File"
msgstr "Klipper Logdatei"
#: octoprint_klipper/templates/klipper_settings.jinja2:50
#: octoprint_klipper/templates/klipper_settings.jinja2:65
msgid "Configuration Reload Command"
msgstr "Konfiguration Neustart Befehl"
#: octoprint_klipper/templates/klipper_settings.jinja2:58
msgid ""
"The command that is executed when the Klipper configuration changed and "
"needs to be reloaded.<br>\n"
" Set this to \"Manually\" if you don't want to immediately "
"restart klipper."
msgstr ""
"Der Befehl der ausgeführt wird nachdem die Klipper Konfiguration <br>\n"
"geändert wurde und neu geladen werden muss.<br>\n"
"Auf \"Manually\" setzen wenn man nicht gleich Klipper neustarten möchte."
#: octoprint_klipper/templates/klipper_settings.jinja2:72
msgid "The command that is executed if you want to restart klipper."
msgstr "Der Befehl, der ausgeführt wird, um Klipper neu zu starten."
#: octoprint_klipper/templates/klipper_settings.jinja2:69
#: octoprint_klipper/templates/klipper_settings.jinja2:74
#: octoprint_klipper/templates/klipper_settings.jinja2:75
msgid "Reload klipper on editor save?"
msgstr "Klipper neu starten nach speichern im Editor?"
#: octoprint_klipper/templates/klipper_settings.jinja2:80
msgid "Config Backup"
msgstr "Konfig Sicherungen"
#: octoprint_klipper/templates/klipper_settings.jinja2:82
#: octoprint_klipper/templates/klipper_settings.jinja2:83
msgid "Show Backups"
msgstr "Zeige Sicherungen"
#: octoprint_klipper/templates/klipper_settings.jinja2:93
msgid "Add macro button to:"
msgstr "Makroschaltfläche anzeigen auf:"
#: octoprint_klipper/templates/klipper_settings.jinja2:70
#: octoprint_klipper/templates/klipper_settings.jinja2:84
#: octoprint_klipper/templates/klipper_settings.jinja2:94
#: octoprint_klipper/templates/klipper_settings.jinja2:108
msgid "Klipper Tab"
msgstr "Klipper Reiter"
#: octoprint_klipper/templates/klipper_settings.jinja2:71
#: octoprint_klipper/templates/klipper_settings.jinja2:87
#: octoprint_klipper/templates/klipper_settings.jinja2:95
#: octoprint_klipper/templates/klipper_settings.jinja2:111
msgid "Sidebar"
msgstr "Seitenleiste"
#: octoprint_klipper/templates/klipper_settings.jinja2:77
msgid "Name"
msgstr "Name"
#: octoprint_klipper/templates/klipper_settings.jinja2:96
#: octoprint_klipper/templates/klipper_settings.jinja2:120
msgid "Command"
msgstr "Befehl"
#: octoprint_klipper/templates/klipper_settings.jinja2:109
#: octoprint_klipper/templates/klipper_settings.jinja2:133
msgid "Add Macro"
msgstr "Makro hinzufügen"
#: octoprint_klipper/templates/klipper_settings.jinja2:114
#: octoprint_klipper/templates/klipper_settings.jinja2:138
msgid ""
"To show a dialog that asks for parameters you can write your macro like "
"in the following example:"
@ -236,71 +570,79 @@ msgstr ""
"Um ein Dialog anzeigen zu lassen, welches nach Parametern fragt, <br>\n"
"kann man ein Makro wie im nächsten Beispiel schreiben:"
#: octoprint_klipper/templates/klipper_settings.jinja2:130
#: octoprint_klipper/templates/klipper_settings.jinja2:154
msgid ""
"This feature assists in manually leveling you print bed by moving the "
"head to the defined points in sequence.<br>\n"
" If you use a piece of paper for leveling, set \"Probe Height\""
" to the paper thickness eg. \"0.1\"."
"head to the defined points in\n"
" sequence.<br />\n"
" If you use a piece of paper for leveling, set \"Probe Height\" "
"to the paper thickness eg. \"0.1\"."
msgstr ""
"Diese Funktion hilft beim Einstellen des Druckbettes, <br>\n"
"da es die angegebenen Punkte automatisch nacheinander anfährt.<br>\n"
"Wenn man ein Papier zum messen nimmt, setzt man \"Messhöhe\" <br>\n"
"auf die Papierstärke zBsp.: \"0.1\"."
#: octoprint_klipper/templates/klipper_settings.jinja2:135
#: octoprint_klipper/templates/klipper_settings.jinja2:160
msgid "Probe Height"
msgstr "Messhöhe"
#: octoprint_klipper/templates/klipper_settings.jinja2:141
#: octoprint_klipper/templates/klipper_settings.jinja2:166
msgid "Z-height to probe at"
msgstr "Höhe bei der gemessen wird"
#: octoprint_klipper/templates/klipper_settings.jinja2:145
#: octoprint_klipper/templates/klipper_settings.jinja2:170
msgid "Probe Lift"
msgstr "Höhe für Seitenbewegung"
#: octoprint_klipper/templates/klipper_settings.jinja2:151
#: octoprint_klipper/templates/klipper_settings.jinja2:176
msgid "Lift Head by this amount before moving."
msgstr "Hebe Druckkopf auf diese Höhe vor einer Seitenbewegung"
#: octoprint_klipper/templates/klipper_settings.jinja2:155
#: octoprint_klipper/templates/klipper_settings.jinja2:180
msgid "Probe Feedrate Z"
msgstr "Geschwindigkeit Z"
#: octoprint_klipper/templates/klipper_settings.jinja2:164
#: octoprint_klipper/templates/klipper_settings.jinja2:189
msgid "Feedrate X/Y"
msgstr "Geschwindigkeit X/Y"
#: octoprint_klipper/templates/klipper_settings.jinja2:173
#: octoprint_klipper/templates/klipper_settings.jinja2:198
msgid "Probe Points"
msgstr "Messpunkte"
#: octoprint_klipper/templates/klipper_settings.jinja2:200
#: octoprint_klipper/templates/klipper_settings.jinja2:225
msgid "Add Point"
msgstr "Füge Messpunkt hinzu"
#: octoprint_klipper/templates/klipper_settings.jinja2:212
#: octoprint_klipper/templates/klipper_settings.jinja2:213
msgid "Reload last changes"
msgstr "Lade letzte Änderung"
#: octoprint_klipper/templates/klipper_settings.jinja2:232
msgid "Config Files"
msgstr "Konfig Dateien"
#: octoprint_klipper/templates/klipper_settings.jinja2:216
#: octoprint_klipper/templates/klipper_settings.jinja2:217
msgid "Reload from file"
msgstr "Datei neuladen"
#: octoprint_klipper/templates/klipper_settings.jinja2:246
msgid "Add new File"
msgstr "Erstelle neue Datei"
#: octoprint_klipper/templates/klipper_settings.jinja2:219
msgid "Check parsing on save"
msgstr "Prüfe Syntax beim speichern"
#: octoprint_klipper/templates/klipper_settings.jinja2:247
msgid "New File"
msgstr "Neue Datei"
#: octoprint_klipper/templates/klipper_settings.jinja2:221
msgid "Decrease Fontsize"
msgstr "Verringere Schriftgröße"
#: octoprint_klipper/templates/klipper_settings.jinja2:249
msgid "Open last config"
msgstr "Öffne letzte Konfig"
#: octoprint_klipper/templates/klipper_settings.jinja2:222
msgid "Increase Fontsize"
msgstr "Vergrößere Schriftgröße"
#: octoprint_klipper/templates/klipper_settings.jinja2:250
#, fuzzy
msgid "Open last Editor"
msgstr "Öffne Editor"
#: octoprint_klipper/templates/klipper_settings.jinja2:300
msgid "Edit"
msgstr "Bearbeiten"
#: octoprint_klipper/templates/klipper_settings.jinja2:329
msgid "Donate"
msgstr "Spenden"
#: octoprint_klipper/templates/klipper_sidebar.jinja2:3
msgid "Printer Profile"
@ -311,63 +653,71 @@ msgid "Connect"
msgstr "Verbinde"
#: octoprint_klipper/templates/klipper_sidebar.jinja2:6
#: octoprint_klipper/templates/klipper_tab_main.jinja2:37
msgid "Open Klipper config"
msgstr "Öffne Klipper Konfig"
msgid "Open Editor"
msgstr "Öffne Editor"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:3
msgid "Messages"
msgstr "Nachricht"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:14
#: octoprint_klipper/templates/klipper_tab_main.jinja2:16
#: octoprint_klipper/templates/klipper_tab_main.jinja2:11
#: octoprint_klipper/templates/klipper_tab_main.jinja2:12
msgid "Clear Log"
msgstr "Log löschen"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:27
#: octoprint_klipper/templates/klipper_tab_main.jinja2:21
msgid "Query Klipper for its current status"
msgstr "Aktuellen Status von Klipper anfordern"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:29
#: octoprint_klipper/templates/klipper_tab_main.jinja2:22
msgid "Get Status"
msgstr "Statusanfrage"
msgstr "Statusabfrage"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:34
#, fuzzy
msgid "Open the Klipper configuration file"
msgstr "Öffne Klipper Konfiguration"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:26
msgid "Open the OctoKlipper Settings"
msgstr "Öffne die OctoKlipper Einstellungen"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:44
#: octoprint_klipper/templates/klipper_tab_main.jinja2:27
msgid "Open OctoKlipper Settings"
msgstr "Öffne OctoKlipper Einstellungen"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:31
msgid "Show the Editor"
msgstr "Öffne Editor"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:33
msgid "Show Editor"
msgstr "Öffne Editor"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:39
msgid "Restart"
msgstr "Neustart"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:49
#: octoprint_klipper/templates/klipper_tab_main.jinja2:41
msgid ""
"This will cause the host software to reload its config and perform an "
"internal reset"
msgstr ""
"Das wird die Konfigurationsdatei neuladen und die Hostsoftware neustarten"
msgstr "Das wird die Konfigurationsdatei neuladen und die Hostsoftware neu starten"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:51
#: octoprint_klipper/templates/klipper_tab_main.jinja2:42
msgid "Host"
msgstr "Host"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:56
#: octoprint_klipper/templates/klipper_tab_main.jinja2:45
msgid ""
"Similar to a host restart, but also clears any error state from the "
"micro-controller"
msgstr ""
"Wie der Host Neustart, aber zusätzlich wird die Firmware neugestartet"
msgstr "Wie der Host Neustart, aber zusätzlich wird die Firmware neu gestartet"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:58
#: octoprint_klipper/templates/klipper_tab_main.jinja2:46
msgid "Firmware"
msgstr "Firmware"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:65
#: octoprint_klipper/templates/klipper_tab_main.jinja2:52
msgid "Tools"
msgstr "Tools"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:70
#: octoprint_klipper/templates/klipper_tab_main.jinja2:54
msgid ""
"Assists in manually leveling your printbed by moving the head to a "
"configurable set of positions in sequence."
@ -375,22 +725,24 @@ msgstr ""
"Unterstützt beim manuellem Bettnivellieren, <br>\n"
"da der Druckkopf nacheinander einstellbare Punkte anfährt."
#: octoprint_klipper/templates/klipper_tab_main.jinja2:77
#: octoprint_klipper/templates/klipper_tab_main.jinja2:58
msgid "Determines optimal PID parameters by heat cycling the hotend/bed."
msgstr "Findet optimale PID Parameter beim zyklischen Heizen des Druckkopfes/-bettes."
msgstr ""
"Findet optimale PID Parameter beim zyklischen Heizen des "
"Druckkopfes/-bettes."
#: octoprint_klipper/templates/klipper_tab_main.jinja2:84
#: octoprint_klipper/templates/klipper_tab_main.jinja2:62
msgid "Sets a offset for subsequent GCODE coordinates."
msgstr "Setze einen Versatz für alle weiteren GCODE Koordinaten"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:91
#: octoprint_klipper/templates/klipper_tab_main.jinja2:66
msgid ""
"Assists in debugging performance issues by analyzing the Klipper log "
"files."
msgstr ""
"Unterstützt, mit dem analysieren des Klipperlogs, beim Suchen von Performanceproblemen."
"Unterstützt, mit dem analysieren des Klipperlogs, beim Suchen von "
"Performanceproblemen."
#: octoprint_klipper/templates/klipper_tab_main.jinja2:93
#: octoprint_klipper/templates/klipper_tab_main.jinja2:67
msgid "Analyze Klipper Log"
msgstr "Analysiere Klipperlog"

View File

@ -7,25 +7,332 @@
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: OctoKlipper 0.3.8.2\n"
"Project-Id-Version: OctoKlipper 0.3.8.3\n"
"Report-Msgid-Bugs-To: i18n@octoprint.org\n"
"POT-Creation-Date: 2021-05-13 18:15+0200\n"
"POT-Creation-Date: 2021-09-17 08:18+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.9.0\n"
"Generated-By: Babel 2.9.1\n"
#: octoprint_klipper/__init__.py:87
#: octoprint_klipper/__init__.py:93
msgid "Allows to config klipper"
msgstr ""
#: octoprint_klipper/__init__.py:95
#: octoprint_klipper/__init__.py:101
msgid "Allows to use klipper macros"
msgstr ""
#: octoprint_klipper/__init__.py:608
msgid "Restart Klipper"
msgstr ""
#: octoprint_klipper/__init__.py:609
msgid "You are about to restart Klipper!"
msgstr ""
#: octoprint_klipper/__init__.py:609
msgid "This will stop ongoing prints!"
msgstr ""
#: octoprint_klipper/cfgUtils.py:95
msgid "File not found!"
msgstr ""
#: octoprint_klipper/static/js/klipper.js:259
msgid "All ongoing Prints will be stopped!"
msgstr ""
#: octoprint_klipper/static/js/klipper.js:262
msgid "Klipper restart?"
msgstr ""
#: octoprint_klipper/static/js/klipper.js:264
#: octoprint_klipper/static/js/klipper_backup.js:159
msgid "Proceed"
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:126
#: octoprint_klipper/static/js/klipper_settings.js:124
#, python-format
msgid ""
"Failed to remove config %(name)s.</p><p>Please consult octoprint.log for "
"details.</p>"
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:129
#: octoprint_klipper/static/js/klipper_settings.js:127
msgid "Could not remove config"
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:138
#, python-format
msgid "You are about to delete backuped config file \"%(name)s\"."
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:154
msgid "This will overwrite any file with the same name on the configpath."
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:157
msgid "Are you sure you want to restore now?"
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:184
#, python-format
msgid "You are about to restore %(count)d backuped config files."
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:199
#, python-format
msgid "You are about to delete %(count)d backuped config files."
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:209
msgid "Restoring klipper config files"
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:211
#, python-format
msgid "Restoring %(count)d backuped config files..."
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:220
#, python-format
msgid "Restored %(filename)s..."
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:231
#, python-format
msgid "Restoring of %(filename)s failed, continuing..."
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:264
msgid "Deleting backup files"
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:265
#, python-format
msgid "Deleting %(count)d backup files..."
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:273
#: octoprint_klipper/static/js/klipper_settings.js:183
#, python-format
msgid "Deleted %(filename)s..."
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:279
#: octoprint_klipper/static/js/klipper_settings.js:193
#, python-format
msgid "Deleting of %(filename)s failed, continuing..."
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:61
msgid "Your file seems to have changed."
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:64
msgid "Do you really want to close it?"
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:65
msgid "No"
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:66
msgid "Yes"
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:127
#: octoprint_klipper/static/js/klipper_editor.js:134
msgid "SyntaxCheck"
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:127
msgid "SyntaxCheck OK"
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:130
msgid "Syntax NOK"
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:151
#: octoprint_klipper/static/js/klipper_editor.js:161
#: octoprint_klipper/templates/klipper_editor.jinja2:40
#: octoprint_klipper/templates/klipper_editor.jinja2:41
msgid "Save Config"
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:151
msgid "File saved."
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:157
msgid "File not saved!"
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:226
#: octoprint_klipper/static/js/klipper_editor.js:243
msgid "Reload File"
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:230
msgid "Reload Config"
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:230
msgid "File reloaded."
msgstr ""
#: octoprint_klipper/static/js/klipper_settings.js:136
#, python-format
msgid "You are about to delete config file \"%(name)s\"."
msgstr ""
#: octoprint_klipper/static/js/klipper_settings.js:163
#, python-format
msgid "You are about to delete %(count)d config files."
msgstr ""
#: octoprint_klipper/static/js/klipper_settings.js:173
msgid "Deleting config files"
msgstr ""
#: octoprint_klipper/static/js/klipper_settings.js:174
#, python-format
msgid "Deleting %(count)d config files..."
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:5
msgid "Backups"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:17
#: octoprint_klipper/templates/klipper_settings.jinja2:240
msgid "Select all on this page"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:20
#: octoprint_klipper/templates/klipper_settings.jinja2:241
msgid "Select all"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:25
#: octoprint_klipper/templates/klipper_settings.jinja2:243
msgid "Clear selection"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:29
#: octoprint_klipper/templates/klipper_settings.jinja2:252
msgid "Refresh file list"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:30
#: octoprint_klipper/templates/klipper_settings.jinja2:253
msgid "Refresh Files"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:34
msgid "Restore selected"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:36
#: octoprint_klipper/templates/klipper_settings.jinja2:256
msgid "Delete selected"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:46
#: octoprint_klipper/templates/klipper_settings.jinja2:263
msgid "Sort by name"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:47
#: octoprint_klipper/templates/klipper_settings.jinja2:264
msgid "ascending"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:50
#: octoprint_klipper/templates/klipper_settings.jinja2:266
msgid "Sort by date"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:51
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:55
#: octoprint_klipper/templates/klipper_settings.jinja2:267
#: octoprint_klipper/templates/klipper_settings.jinja2:270
msgid "descending"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:54
#: octoprint_klipper/templates/klipper_settings.jinja2:269
msgid "Sort by file size"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:65
#: octoprint_klipper/templates/klipper_settings.jinja2:101
#: octoprint_klipper/templates/klipper_settings.jinja2:280
msgid "Name"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:66
#: octoprint_klipper/templates/klipper_settings.jinja2:281
msgid "Size"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:67
#: octoprint_klipper/templates/klipper_settings.jinja2:282
msgid "Action"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:78
#: octoprint_klipper/templates/klipper_settings.jinja2:294
msgid "Delete"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:81
msgid "Restore"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:84
#: octoprint_klipper/templates/klipper_settings.jinja2:297
msgid "Download"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:87
msgid "Preview"
msgstr ""
#: octoprint_klipper/templates/klipper_editor.jinja2:6
msgid "Decrease Fontsize"
msgstr ""
#: octoprint_klipper/templates/klipper_editor.jinja2:9
msgid "Increase Fontsize"
msgstr ""
#: octoprint_klipper/templates/klipper_editor.jinja2:28
msgid "Filename"
msgstr ""
#: octoprint_klipper/templates/klipper_editor.jinja2:31
#: octoprint_klipper/templates/klipper_editor.jinja2:32
msgid "Reload from file"
msgstr ""
#: octoprint_klipper/templates/klipper_editor.jinja2:34
#: octoprint_klipper/templates/klipper_editor.jinja2:35
msgid "Reload last version"
msgstr ""
#: octoprint_klipper/templates/klipper_editor.jinja2:37
#: octoprint_klipper/templates/klipper_editor.jinja2:38
msgid "Check Syntax"
msgstr ""
#: octoprint_klipper/templates/klipper_graph_dialog.jinja2:4
msgid "Performance Graph"
msgstr ""
@ -55,7 +362,7 @@ msgid "Depending on the size of the log file this might take a while."
msgstr ""
#: octoprint_klipper/templates/klipper_leveling_dialog.jinja2:4
#: octoprint_klipper/templates/klipper_tab_main.jinja2:72
#: octoprint_klipper/templates/klipper_tab_main.jinja2:55
msgid "Assisted Bed Leveling"
msgstr ""
@ -85,7 +392,7 @@ msgid "Go to OctoKlipper Tab"
msgstr ""
#: octoprint_klipper/templates/klipper_offset_dialog.jinja2:4
#: octoprint_klipper/templates/klipper_tab_main.jinja2:86
#: octoprint_klipper/templates/klipper_tab_main.jinja2:63
msgid "Coordinate Offset"
msgstr ""
@ -110,7 +417,7 @@ msgid "OK"
msgstr ""
#: octoprint_klipper/templates/klipper_pid_tuning_dialog.jinja2:4
#: octoprint_klipper/templates/klipper_tab_main.jinja2:79
#: octoprint_klipper/templates/klipper_tab_main.jinja2:59
msgid "PID Tuning"
msgstr ""
@ -140,7 +447,7 @@ msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:4
#: octoprint_klipper/templates/klipper_sidebar.jinja2:16
#: octoprint_klipper/templates/klipper_tab_main.jinja2:99
#: octoprint_klipper/templates/klipper_tab_main.jinja2:72
msgid "Macros"
msgstr ""
@ -166,130 +473,159 @@ msgid "Show Short Messages"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:27
#: octoprint_klipper/templates/klipper_settings.jinja2:28
msgid "on NavBar"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:28
#: octoprint_klipper/templates/klipper_settings.jinja2:29
#: octoprint_klipper/templates/klipper_settings.jinja2:30
msgid "on SideBar"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:32
#: octoprint_klipper/templates/klipper_settings.jinja2:34
#: octoprint_klipper/templates/klipper_settings.jinja2:36
msgid "Enable debug logging"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:38
msgid "Klipper Config File"
#: octoprint_klipper/templates/klipper_settings.jinja2:41
msgid "Config Editor"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:43
#: octoprint_klipper/templates/klipper_settings.jinja2:44
msgid "Check parsing on save"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:46
#: octoprint_klipper/templates/klipper_settings.jinja2:48
msgid "Fontsize"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:53
msgid "Klipper Config Directory"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:59
msgid "Klipper Log File"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:50
#: octoprint_klipper/templates/klipper_settings.jinja2:65
msgid "Configuration Reload Command"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:58
msgid ""
"The command that is executed when the Klipper configuration changed and "
"needs to be reloaded.<br>\n"
" Set this to \"Manually\" if you don't want to immediately "
"restart klipper."
#: octoprint_klipper/templates/klipper_settings.jinja2:72
msgid "The command that is executed if you want to restart klipper."
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:69
#: octoprint_klipper/templates/klipper_settings.jinja2:74
#: octoprint_klipper/templates/klipper_settings.jinja2:75
msgid "Reload klipper on editor save?"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:80
msgid "Config Backup"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:82
#: octoprint_klipper/templates/klipper_settings.jinja2:83
msgid "Show Backups"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:93
msgid "Add macro button to:"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:70
#: octoprint_klipper/templates/klipper_settings.jinja2:84
#: octoprint_klipper/templates/klipper_settings.jinja2:94
#: octoprint_klipper/templates/klipper_settings.jinja2:108
msgid "Klipper Tab"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:71
#: octoprint_klipper/templates/klipper_settings.jinja2:87
#: octoprint_klipper/templates/klipper_settings.jinja2:95
#: octoprint_klipper/templates/klipper_settings.jinja2:111
msgid "Sidebar"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:77
msgid "Name"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:96
#: octoprint_klipper/templates/klipper_settings.jinja2:120
msgid "Command"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:109
#: octoprint_klipper/templates/klipper_settings.jinja2:133
msgid "Add Macro"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:114
#: octoprint_klipper/templates/klipper_settings.jinja2:138
msgid ""
"To show a dialog that asks for parameters you can write your macro like "
"in the following example:"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:130
#: octoprint_klipper/templates/klipper_settings.jinja2:154
msgid ""
"This feature assists in manually leveling you print bed by moving the "
"head to the defined points in sequence.<br>\n"
" If you use a piece of paper for leveling, set \"Probe Height\""
" to the paper thickness eg. \"0.1\"."
"head to the defined points in\n"
" sequence.<br />\n"
" If you use a piece of paper for leveling, set \"Probe Height\" "
"to the paper thickness eg. \"0.1\"."
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:135
#: octoprint_klipper/templates/klipper_settings.jinja2:160
msgid "Probe Height"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:141
#: octoprint_klipper/templates/klipper_settings.jinja2:166
msgid "Z-height to probe at"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:145
#: octoprint_klipper/templates/klipper_settings.jinja2:170
msgid "Probe Lift"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:151
#: octoprint_klipper/templates/klipper_settings.jinja2:176
msgid "Lift Head by this amount before moving."
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:155
#: octoprint_klipper/templates/klipper_settings.jinja2:180
msgid "Probe Feedrate Z"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:164
#: octoprint_klipper/templates/klipper_settings.jinja2:189
msgid "Feedrate X/Y"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:173
#: octoprint_klipper/templates/klipper_settings.jinja2:198
msgid "Probe Points"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:200
#: octoprint_klipper/templates/klipper_settings.jinja2:225
msgid "Add Point"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:212
#: octoprint_klipper/templates/klipper_settings.jinja2:213
msgid "Reload last changes"
#: octoprint_klipper/templates/klipper_settings.jinja2:232
msgid "Config Files"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:216
#: octoprint_klipper/templates/klipper_settings.jinja2:217
msgid "Reload from file"
#: octoprint_klipper/templates/klipper_settings.jinja2:246
msgid "Add new File"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:219
msgid "Check parsing on save"
#: octoprint_klipper/templates/klipper_settings.jinja2:247
msgid "New File"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:221
msgid "Decrease Fontsize"
#: octoprint_klipper/templates/klipper_settings.jinja2:249
msgid "Open last config"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:222
msgid "Increase Fontsize"
#: octoprint_klipper/templates/klipper_settings.jinja2:250
msgid "Open last Editor"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:300
msgid "Edit"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:329
msgid "Donate"
msgstr ""
#: octoprint_klipper/templates/klipper_sidebar.jinja2:3
@ -301,80 +637,91 @@ msgid "Connect"
msgstr ""
#: octoprint_klipper/templates/klipper_sidebar.jinja2:6
#: octoprint_klipper/templates/klipper_tab_main.jinja2:37
msgid "Open Klipper config"
msgid "Open Editor"
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:3
msgid "Messages"
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:14
#: octoprint_klipper/templates/klipper_tab_main.jinja2:16
#: octoprint_klipper/templates/klipper_tab_main.jinja2:11
#: octoprint_klipper/templates/klipper_tab_main.jinja2:12
msgid "Clear Log"
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:27
#: octoprint_klipper/templates/klipper_tab_main.jinja2:21
msgid "Query Klipper for its current status"
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:29
#: octoprint_klipper/templates/klipper_tab_main.jinja2:22
msgid "Get Status"
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:34
msgid "Open the Klipper configuration file"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:26
msgid "Open the OctoKlipper Settings"
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:44
#: octoprint_klipper/templates/klipper_tab_main.jinja2:27
msgid "Open OctoKlipper Settings"
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:31
msgid "Show the Editor"
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:33
msgid "Show Editor"
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:39
msgid "Restart"
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:49
#: octoprint_klipper/templates/klipper_tab_main.jinja2:41
msgid ""
"This will cause the host software to reload its config and perform an "
"internal reset"
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:51
#: octoprint_klipper/templates/klipper_tab_main.jinja2:42
msgid "Host"
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:56
#: octoprint_klipper/templates/klipper_tab_main.jinja2:45
msgid ""
"Similar to a host restart, but also clears any error state from the "
"micro-controller"
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:58
#: octoprint_klipper/templates/klipper_tab_main.jinja2:46
msgid "Firmware"
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:65
#: octoprint_klipper/templates/klipper_tab_main.jinja2:52
msgid "Tools"
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:70
#: octoprint_klipper/templates/klipper_tab_main.jinja2:54
msgid ""
"Assists in manually leveling your printbed by moving the head to a "
"configurable set of positions in sequence."
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:77
#: octoprint_klipper/templates/klipper_tab_main.jinja2:58
msgid "Determines optimal PID parameters by heat cycling the hotend/bed."
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:84
#: octoprint_klipper/templates/klipper_tab_main.jinja2:62
msgid "Sets a offset for subsequent GCODE coordinates."
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:91
#: octoprint_klipper/templates/klipper_tab_main.jinja2:66
msgid ""
"Assists in debugging performance issues by analyzing the Klipper log "
"files."
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:93
#: octoprint_klipper/templates/klipper_tab_main.jinja2:67
msgid "Analyze Klipper Log"
msgstr ""