Merge branch 'feat/multipleCfg'
This commit is contained in:
parent
d70198b14b
commit
b2575c55db
|
@ -9,7 +9,9 @@ charset = utf-8
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
indent_style = space
|
indent_style = space
|
||||||
max_line_length = 90
|
max_line_length = 180
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
|
||||||
[**.py]
|
[**.py]
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
|
|
@ -14,16 +14,22 @@
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
import datetime
|
|
||||||
import logging
|
import logging
|
||||||
import octoprint.plugin
|
import octoprint.plugin
|
||||||
import octoprint.plugin.core
|
import octoprint.plugin.core
|
||||||
import glob
|
import glob
|
||||||
import os
|
import os
|
||||||
|
import time
|
||||||
import sys
|
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.util.comm import parse_firmware_line
|
||||||
from octoprint.access.permissions import Permissions, ADMIN_GROUP
|
from octoprint.access.permissions import Permissions, ADMIN_GROUP
|
||||||
from .modules import KlipperLogAnalyzer
|
from .modules import KlipperLogAnalyzer
|
||||||
|
from octoprint.server.util.flask import restricted_access
|
||||||
import flask
|
import flask
|
||||||
from flask_babel import gettext
|
from flask_babel import gettext
|
||||||
|
|
||||||
|
@ -35,13 +41,16 @@ except ImportError:
|
||||||
if sys.version_info[0] < 3:
|
if sys.version_info[0] < 3:
|
||||||
import StringIO
|
import StringIO
|
||||||
|
|
||||||
|
MAX_UPLOAD_SIZE = 5 * 1024 * 1024 # 5Mb
|
||||||
|
|
||||||
class KlipperPlugin(
|
class KlipperPlugin(
|
||||||
octoprint.plugin.StartupPlugin,
|
octoprint.plugin.StartupPlugin,
|
||||||
octoprint.plugin.TemplatePlugin,
|
octoprint.plugin.TemplatePlugin,
|
||||||
octoprint.plugin.SettingsPlugin,
|
octoprint.plugin.SettingsPlugin,
|
||||||
octoprint.plugin.AssetPlugin,
|
octoprint.plugin.AssetPlugin,
|
||||||
octoprint.plugin.SimpleApiPlugin,
|
octoprint.plugin.SimpleApiPlugin,
|
||||||
octoprint.plugin.EventHandlerPlugin):
|
octoprint.plugin.EventHandlerPlugin,
|
||||||
|
octoprint.plugin.BlueprintPlugin):
|
||||||
|
|
||||||
_parsing_response = False
|
_parsing_response = False
|
||||||
_parsing_check_response = True
|
_parsing_check_response = True
|
||||||
|
@ -74,8 +83,10 @@ class KlipperPlugin(
|
||||||
self._settings.global_set(
|
self._settings.global_set(
|
||||||
["serial", "additionalPorts"], additional_ports)
|
["serial", "additionalPorts"], additional_ports)
|
||||||
self._settings.save()
|
self._settings.save()
|
||||||
self.log_info(
|
logger.log_info(
|
||||||
"Added klipper serial port {} to list of additional ports.".format(klipper_port))
|
self,
|
||||||
|
"Added klipper serial port {} to list of additional ports.".format(klipper_port)
|
||||||
|
)
|
||||||
|
|
||||||
# -- Settings Plugin
|
# -- Settings Plugin
|
||||||
|
|
||||||
|
@ -126,6 +137,7 @@ class KlipperPlugin(
|
||||||
debug_logging=False,
|
debug_logging=False,
|
||||||
configpath="~/printer.cfg",
|
configpath="~/printer.cfg",
|
||||||
old_config="",
|
old_config="",
|
||||||
|
temp_config="",
|
||||||
logpath="/tmp/klippy.log",
|
logpath="/tmp/klippy.log",
|
||||||
reload_command="RESTART",
|
reload_command="RESTART",
|
||||||
shortStatus_navbar=True,
|
shortStatus_navbar=True,
|
||||||
|
@ -141,71 +153,32 @@ class KlipperPlugin(
|
||||||
configpath = os.path.expanduser(
|
configpath = os.path.expanduser(
|
||||||
self._settings.get(["configuration", "configpath"])
|
self._settings.get(["configuration", "configpath"])
|
||||||
)
|
)
|
||||||
data["config"] = ""
|
standardconfigfile = os.path.join(configpath, "printer.cfg")
|
||||||
try:
|
data["config"] = cfgUtils.get_cfg(self, standardconfigfile)
|
||||||
f = open(configpath, "r")
|
util.send_message(self, "reload", "config", "", data["config"])
|
||||||
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
|
# send the configdata to frontend to update ace editor
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def on_settings_save(self, data):
|
def on_settings_save(self, data):
|
||||||
|
|
||||||
self.log_debug(
|
|
||||||
"Save klipper configs"
|
|
||||||
)
|
|
||||||
|
|
||||||
if "config" in data:
|
if "config" in data:
|
||||||
check_parse = self._settings.get(["configuration", "parse_check"])
|
if cfgUtils.save_cfg(self, data["config"], "printer.cfg"):
|
||||||
self.log_debug("check_parse: {}".format(check_parse))
|
#load the reload command from changed data if it is not existing load the saved setting
|
||||||
|
if util.key_exist(data, "configuration", "reload_command"):
|
||||||
if sys.version_info[0] < 3:
|
reload_command = os.path.expanduser(
|
||||||
data["config"] = data["config"].encode('utf-8')
|
data["configuration"]["reload_command"]
|
||||||
|
)
|
||||||
# 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):
|
|
||||||
self.copy_cfg_to_backup(configpath)
|
|
||||||
|
|
||||||
if 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:
|
else:
|
||||||
#load the reload command from changed data if it is not existing load the saved setting
|
reload_command = self._settings.get(["configuration", "reload_command"])
|
||||||
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":
|
if reload_command != "manually":
|
||||||
# Restart klippy to reload config
|
# Restart klippy to reload config
|
||||||
self._printer.commands(reload_command)
|
self._printer.commands(reload_command)
|
||||||
self.log_info("Restarting Klipper.")
|
logger.log_info(self, "Restarting Klipper.")
|
||||||
# we dont want to write the klipper conf to the octoprint settings
|
# we dont want to write the klipper conf to the octoprint settings
|
||||||
data.pop("config", None)
|
else:
|
||||||
|
# save not sure. saving to the octoprintconfig:
|
||||||
|
self._settings.set(["configuration", "temp_config"], data)
|
||||||
|
data.pop("config", None)
|
||||||
|
|
||||||
# save the rest of changed settings into config.yaml of octoprint
|
# save the rest of changed settings into config.yaml of octoprint
|
||||||
old_debug_logging = self._settings.get_boolean(["configuration", "debug_logging"])
|
old_debug_logging = self._settings.get_boolean(["configuration", "debug_logging"])
|
||||||
|
@ -280,14 +253,14 @@ class KlipperPlugin(
|
||||||
settings.remove(["probePoints"])
|
settings.remove(["probePoints"])
|
||||||
|
|
||||||
if settings.has(["configPath"]):
|
if settings.has(["configPath"]):
|
||||||
self.log_info("migrate setting for: configPath")
|
logger.log_info(self, "migrate setting for: configPath")
|
||||||
settings.set(["config_path"], settings.get(["configPath"]))
|
settings.set(["config_path"], settings.get(["configPath"]))
|
||||||
settings.remove(["configPath"])
|
settings.remove(["configPath"])
|
||||||
|
|
||||||
if current is not None and current < 3:
|
if current is not None and current < 3:
|
||||||
settings = self._settings
|
settings = self._settings
|
||||||
if settings.has(["configuration", "navbar"]):
|
if settings.has(["configuration", "navbar"]):
|
||||||
self.log_info("migrate setting for: configuration/navbar")
|
logger.log_info(self, "migrate setting for: configuration/navbar")
|
||||||
settings.set(["configuration", "shortStatus_navbar"], settings.get(["configuration", "navbar"]))
|
settings.set(["configuration", "shortStatus_navbar"], settings.get(["configuration", "navbar"]))
|
||||||
settings.remove(["configuration", "navbar"])
|
settings.remove(["configuration", "navbar"])
|
||||||
|
|
||||||
|
@ -323,17 +296,29 @@ class KlipperPlugin(
|
||||||
custom_bindings=True
|
custom_bindings=True
|
||||||
),
|
),
|
||||||
dict(type="sidebar",
|
dict(type="sidebar",
|
||||||
custom_bindings=True,
|
custom_bindings=True,
|
||||||
icon="rocket",
|
icon="rocket",
|
||||||
replaces="connection" if self._settings.get_boolean(
|
replaces="connection" if self._settings.get_boolean(
|
||||||
["connection", "replace_connection_panel"]) else ""
|
["connection", "replace_connection_panel"]) else ""
|
||||||
),
|
),
|
||||||
dict(
|
dict(
|
||||||
type="generic",
|
type="generic",
|
||||||
name="Performance Graph",
|
name="Performance Graph",
|
||||||
template="klipper_graph_dialog.jinja2",
|
template="klipper_graph_dialog.jinja2",
|
||||||
custom_bindings=True
|
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(
|
dict(
|
||||||
type="generic",
|
type="generic",
|
||||||
name="Macro Dialog",
|
name="Macro Dialog",
|
||||||
|
@ -342,6 +327,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
|
# -- Asset Plugin
|
||||||
|
|
||||||
def get_assets(self):
|
def get_assets(self):
|
||||||
|
@ -352,8 +343,11 @@ class KlipperPlugin(
|
||||||
"js/klipper_pid_tuning.js",
|
"js/klipper_pid_tuning.js",
|
||||||
"js/klipper_offset.js",
|
"js/klipper_offset.js",
|
||||||
"js/klipper_param_macro.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"]
|
css=["css/klipper.css"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -361,18 +355,19 @@ class KlipperPlugin(
|
||||||
|
|
||||||
def on_event(self, event, payload):
|
def on_event(self, event, payload):
|
||||||
if "UserLoggedIn" == event:
|
if "UserLoggedIn" == event:
|
||||||
self.update_status("info", "Klipper: Standby")
|
util.update_status(self, "info", "Klipper: Standby")
|
||||||
if "Connecting" == event:
|
if "Connecting" == event:
|
||||||
self.update_status("info", "Klipper: Connecting ...")
|
util.update_status(self, "info", "Klipper: Connecting ...")
|
||||||
elif "Connected" == event:
|
elif "Connected" == event:
|
||||||
self.update_status("info", "Klipper: Connected to host")
|
util.update_status(self, "info", "Klipper: Connected to host")
|
||||||
self.log_info(
|
logger.log_info(
|
||||||
|
self,
|
||||||
"Connected to host via {} @{}bps".format(payload["port"], payload["baudrate"]))
|
"Connected to host via {} @{}bps".format(payload["port"], payload["baudrate"]))
|
||||||
elif "Disconnected" == event:
|
elif "Disconnected" == event:
|
||||||
self.update_status("info", "Klipper: Disconnected from host")
|
util.update_status(self, "info", "Klipper: Disconnected from host")
|
||||||
elif "Error" == event:
|
elif "Error" == event:
|
||||||
self.update_status("error", "Klipper: Error")
|
util.update_status(self, "error", "Klipper: Error")
|
||||||
self.log_error(payload["error"])
|
logger.log_error(self, payload["error"])
|
||||||
|
|
||||||
# -- GCODE Hook
|
# -- GCODE Hook
|
||||||
|
|
||||||
|
@ -381,22 +376,22 @@ class KlipperPlugin(
|
||||||
if "FIRMWARE_VERSION" in line:
|
if "FIRMWARE_VERSION" in line:
|
||||||
printerInfo = parse_firmware_line(line)
|
printerInfo = parse_firmware_line(line)
|
||||||
if "FIRMWARE_VERSION" in printerInfo:
|
if "FIRMWARE_VERSION" in printerInfo:
|
||||||
self.log_info("Firmware version: {}".format(
|
logger.log_info(self, "Firmware version: {}".format(
|
||||||
printerInfo["FIRMWARE_VERSION"]))
|
printerInfo["FIRMWARE_VERSION"]))
|
||||||
elif "// probe" in line or "// Failed to verify BLTouch" in line:
|
elif "// probe" in line or "// Failed to verify BLTouch" in line:
|
||||||
msg = line.strip('/')
|
msg = line.strip('/')
|
||||||
self.log_info(msg)
|
logger.log_info(self, msg)
|
||||||
self.write_parsing_response_buffer()
|
self.write_parsing_response_buffer()
|
||||||
elif "//" in line:
|
elif "//" in line:
|
||||||
# add lines with // to a buffer
|
# add lines with // to a buffer
|
||||||
self._message = self._message + line.strip('/')
|
self._message = self._message + line.strip('/')
|
||||||
if not self._parsing_response:
|
if not self._parsing_response:
|
||||||
self.update_status("info", self._message)
|
util.update_status(self, "info", self._message)
|
||||||
self._parsing_response = True
|
self._parsing_response = True
|
||||||
elif "!!" in line:
|
elif "!!" in line:
|
||||||
msg = line.strip('!')
|
msg = line.strip('!')
|
||||||
self.update_status("error", msg)
|
util.update_status(self, "error", msg)
|
||||||
self.log_error(msg)
|
logger.log_error(self, msg)
|
||||||
self.write_parsing_response_buffer()
|
self.write_parsing_response_buffer()
|
||||||
else:
|
else:
|
||||||
self.write_parsing_response_buffer()
|
self.write_parsing_response_buffer()
|
||||||
|
@ -406,7 +401,7 @@ class KlipperPlugin(
|
||||||
# write buffer with // lines after a gcode response without //
|
# write buffer with // lines after a gcode response without //
|
||||||
if self._parsing_response:
|
if self._parsing_response:
|
||||||
self._parsing_response = False
|
self._parsing_response = False
|
||||||
self.log_info(self._message)
|
logger.log_info(self, self._message)
|
||||||
self._message = ""
|
self._message = ""
|
||||||
|
|
||||||
def get_api_commands(self):
|
def get_api_commands(self):
|
||||||
|
@ -414,7 +409,6 @@ class KlipperPlugin(
|
||||||
listLogFiles=[],
|
listLogFiles=[],
|
||||||
getStats=["logFile"],
|
getStats=["logFile"],
|
||||||
reloadConfig=[],
|
reloadConfig=[],
|
||||||
reloadCfgBackup=[],
|
|
||||||
checkConfig=["config"]
|
checkConfig=["config"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -424,12 +418,12 @@ class KlipperPlugin(
|
||||||
logpath = os.path.expanduser(
|
logpath = os.path.expanduser(
|
||||||
self._settings.get(["configuration", "logpath"])
|
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"]) + "*"):
|
for f in glob.glob(self._settings.get(["configuration", "logpath"]) + "*"):
|
||||||
filesize = os.path.getsize(f)
|
filesize = os.path.getsize(f)
|
||||||
|
filemdate = time.strftime("%d.%m.%Y %H:%M",time.localtime(os.path.getctime(f)))
|
||||||
files.append(dict(
|
files.append(dict(
|
||||||
name=os.path.basename(
|
name=os.path.basename(f) + " (" + filemdate + ")",
|
||||||
f) + " ({:.1f} KB)".format(filesize / 1000.0),
|
|
||||||
file=f,
|
file=f,
|
||||||
size=filesize
|
size=filesize
|
||||||
))
|
))
|
||||||
|
@ -441,30 +435,151 @@ class KlipperPlugin(
|
||||||
log_analyzer = KlipperLogAnalyzer.KlipperLogAnalyzer(
|
log_analyzer = KlipperLogAnalyzer.KlipperLogAnalyzer(
|
||||||
data["logFile"])
|
data["logFile"])
|
||||||
return flask.jsonify(log_analyzer.analyze())
|
return flask.jsonify(log_analyzer.analyze())
|
||||||
elif command == "reloadConfig":
|
|
||||||
self.log_debug("reloadConfig")
|
def is_blueprint_protected(self):
|
||||||
return self.reload_cfg()
|
return False
|
||||||
elif command == "reloadCfgBackup":
|
|
||||||
self.log_debug("reloadCfgBackup")
|
def route_hook(self, server_routes, *args, **kwargs):
|
||||||
configpath = os.path.expanduser(
|
from octoprint.server.util.tornado import LargeResponseHandler, path_validation_factory
|
||||||
self._settings.get(["configuration", "configpath"])
|
from octoprint.util import is_hidden_path
|
||||||
)
|
configpath = os.path.expanduser(
|
||||||
return self.copy_cfg_from_backup(configpath)
|
self._settings.get(["configuration", "configpath"])
|
||||||
elif command == "checkConfig":
|
)
|
||||||
if "config" in data:
|
bak_path = os.path.join(self.get_plugin_data_folder(), "configs", "")
|
||||||
#self.write_cfg_backup(data["config"])
|
|
||||||
if self.key_exist(data, "configuration", "parse_check"):
|
return [
|
||||||
check_parse = data["configuration"]["parse_check"]
|
(r"/download/(.*)", LargeResponseHandler, dict(path=configpath,
|
||||||
else:
|
as_attachment=True,
|
||||||
check_parse = self._settings.get(["configuration", "parse_check"])
|
path_validation=path_validation_factory(lambda path: not is_hidden_path(path),
|
||||||
if check_parse and not self.validate_configfile(data["config"]):
|
status_code=404))),
|
||||||
self.log_debug("validateConfig not ok")
|
(r"/download/backup(.*)", LargeResponseHandler, dict(path=bak_path,
|
||||||
self._settings.set(["configuration", "old_config"], data["config"])
|
as_attachment=True,
|
||||||
return flask.jsonify(checkConfig="not OK")
|
path_validation=path_validation_factory(lambda path: not is_hidden_path(path),
|
||||||
else:
|
status_code=404)))
|
||||||
self.log_debug("validateConfig ok")
|
]
|
||||||
self._settings.set(["configuration", "old_config"], "")
|
|
||||||
return flask.jsonify(checkConfig="OK")
|
# 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:
|
||||||
|
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", [])
|
||||||
|
Filecontent = data.get("DataToSave", [])
|
||||||
|
if filename == []:
|
||||||
|
flask.abort(
|
||||||
|
400,
|
||||||
|
description="Invalid request, the filename is not set",
|
||||||
|
)
|
||||||
|
saved = cfgUtils.save_cfg(self, Filecontent, filename)
|
||||||
|
if saved == True:
|
||||||
|
util.send_message(self, "reload", "configlist", "", "")
|
||||||
|
return flask.jsonify(saved = saved)
|
||||||
|
|
||||||
|
# APIs end
|
||||||
|
|
||||||
def get_update_information(self):
|
def get_update_information(self):
|
||||||
return dict(
|
return dict(
|
||||||
|
@ -490,193 +605,6 @@ class KlipperPlugin(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
#-- Helpers
|
|
||||||
def send_message(self, type, subtype, title, payload):
|
|
||||||
"""
|
|
||||||
Send Message over API to FrontEnd
|
|
||||||
"""
|
|
||||||
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<----
|
|
||||||
"""
|
|
||||||
check_parse = self._settings.get(["configuration", "parse_check"])
|
|
||||||
if check_parse:
|
|
||||||
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
|
|
||||||
|
|
||||||
def reload_cfg(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._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"])
|
|
||||||
|
|
||||||
def copy_cfg_from_backup(self, dst):
|
|
||||||
"""
|
|
||||||
Copy the backuped config files in the data folder of OctoKlipper to the given destination
|
|
||||||
"""
|
|
||||||
from shutil import copy
|
|
||||||
|
|
||||||
bak_config_path = os.path.join(self.get_plugin_data_folder(), "configs/")
|
|
||||||
src_files = os.listdir(bak_config_path)
|
|
||||||
nio_files = []
|
|
||||||
self.log_debug("reloadCfgBackupPath:" + src_files)
|
|
||||||
|
|
||||||
for file_name in src_files:
|
|
||||||
full_file_name = os.path.join(bak_config_path, file_name)
|
|
||||||
if os.path.isfile(full_file_name):
|
|
||||||
try:
|
|
||||||
copy(full_file_name, dst)
|
|
||||||
except IOError:
|
|
||||||
self.log_error(
|
|
||||||
"Error: Klipper config file not found at: {}".format(
|
|
||||||
full_file_name)
|
|
||||||
)
|
|
||||||
nio_files.append(full_file_name)
|
|
||||||
else:
|
|
||||||
self.log_debug("File done: " + full_file_name)
|
|
||||||
return nio_files
|
|
||||||
|
|
||||||
def copy_cfg_to_backup(self, src):
|
|
||||||
"""
|
|
||||||
Copy the config file into the data folder of OctoKlipper
|
|
||||||
"""
|
|
||||||
from shutil import copyfile
|
|
||||||
|
|
||||||
filename = os.path.basename(src)
|
|
||||||
dst = os.path.join(self.get_plugin_data_folder(), "configs", "", filename)
|
|
||||||
self.log_debug("CopyCfgBackupPath:" + dst)
|
|
||||||
try:
|
|
||||||
copyfile(src, dst)
|
|
||||||
except IOError:
|
|
||||||
self.log_error(
|
|
||||||
"Error: Couldn't copy Klipper config file to {}".format(
|
|
||||||
dst)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.log_debug("CfgBackup writen")
|
|
||||||
|
|
||||||
__plugin_name__ = "OctoKlipper"
|
__plugin_name__ = "OctoKlipper"
|
||||||
__plugin_pythoncompat__ = ">=2.7,<4"
|
__plugin_pythoncompat__ = ">=2.7,<4"
|
||||||
__plugin_settings_overlay__ = {
|
__plugin_settings_overlay__ = {
|
||||||
|
@ -690,12 +618,12 @@ __plugin_settings_overlay__ = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def __plugin_load__():
|
def __plugin_load__():
|
||||||
global __plugin_implementation__
|
global __plugin_implementation__
|
||||||
global __plugin_hooks__
|
global __plugin_hooks__
|
||||||
__plugin_implementation__ = KlipperPlugin()
|
__plugin_implementation__ = KlipperPlugin()
|
||||||
__plugin_hooks__ = {
|
__plugin_hooks__ = {
|
||||||
|
"octoprint.server.http.routes": __plugin_implementation__.route_hook,
|
||||||
"octoprint.access.permissions": __plugin_implementation__.get_additional_permissions,
|
"octoprint.access.permissions": __plugin_implementation__.get_additional_permissions,
|
||||||
"octoprint.comm.protocol.gcode.received": __plugin_implementation__.on_parse_gcode,
|
"octoprint.comm.protocol.gcode.received": __plugin_implementation__.on_parse_gcode,
|
||||||
"octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information
|
"octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information
|
||||||
|
|
|
@ -0,0 +1,285 @@
|
||||||
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
import glob
|
||||||
|
import os, time, sys
|
||||||
|
|
||||||
|
from . import util, logger
|
||||||
|
from octoprint.util import is_hidden_path
|
||||||
|
import flask
|
||||||
|
from flask_babel import gettext
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
f_counter = 1
|
||||||
|
for f in cfg_files:
|
||||||
|
filesize = os.path.getsize(f)
|
||||||
|
filemdate = time.localtime(os.path.getmtime(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= flask.url_for("index")
|
||||||
|
+ "plugin/klipper/download/"
|
||||||
|
+ os.path.basename(f),
|
||||||
|
))
|
||||||
|
logger.log_debug(self, "list_cfg_files " + str(f_counter) + ": " + f)
|
||||||
|
f_counter += 1
|
||||||
|
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')
|
||||||
|
check_parse = self._settings.get(["configuration", "parse_check"])
|
||||||
|
logger.log_debug(self, "check_parse on filesave: {}".format(check_parse))
|
||||||
|
configpath = os.path.expanduser(self._settings.get(["configuration", "configpath"]))
|
||||||
|
filepath = os.path.join(configpath, filename)
|
||||||
|
|
||||||
|
logger.log_debug(self, "save filepath: {}".format(filepath))
|
||||||
|
|
||||||
|
self._settings.set(["configuration", "temp_config"], content)
|
||||||
|
check = True
|
||||||
|
if check_parse:
|
||||||
|
check=check_cfg(self, content)
|
||||||
|
if check == True:
|
||||||
|
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)
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
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:
|
||||||
|
import configparser
|
||||||
|
except ImportError:
|
||||||
|
import ConfigParser as configparser
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
if a_float:
|
||||||
|
pass
|
||||||
|
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",
|
||||||
|
"OctoKlipper: Invalid Config\n",
|
||||||
|
"Config got not saved!\n\n"
|
||||||
|
+ "Invalid Value for <b>"+x+"</b> in Section: <b>"+y+"</b>\n"
|
||||||
|
+ "{}".format(str(error))
|
||||||
|
)
|
||||||
|
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)
|
||||||
|
logger.log_error(
|
||||||
|
self,
|
||||||
|
"Error: Invalid Klipper config file:\n"
|
||||||
|
+ "{}".format(str(error))
|
||||||
|
)
|
||||||
|
util.send_message(self, "PopUp", "warning", "OctoKlipper: Invalid Config data\n",
|
||||||
|
"Config got not saved!\n\n"
|
||||||
|
+ 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.
|
||||||
|
"""
|
||||||
|
from shutil import copy
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
from shutil import copyfile
|
||||||
|
|
||||||
|
if os.path.isfile(src):
|
||||||
|
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 not src == dst:
|
||||||
|
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
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
import optparse, datetime
|
import optparse, datetime
|
||||||
|
from .. import logger
|
||||||
|
|
||||||
class KlipperLogAnalyzer():
|
class KlipperLogAnalyzer():
|
||||||
MAXBANDWIDTH=25000.
|
MAXBANDWIDTH=25000.
|
||||||
|
@ -81,6 +82,7 @@ class KlipperLogAnalyzer():
|
||||||
out.append(keyparts)
|
out.append(keyparts)
|
||||||
f.close()
|
f.close()
|
||||||
except IOError:
|
except IOError:
|
||||||
|
logger.log_error(self, "Couldn't open log file: {}".format(logname))
|
||||||
print("Couldn't open log file")
|
print("Couldn't open log file")
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
(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.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;
|
||||||
|
});
|
|
@ -1,258 +1,383 @@
|
||||||
.plugin-klipper-sidebar {
|
.plugin-klipper-sidebar {
|
||||||
padding: 1px;
|
padding: 1px;
|
||||||
height: auto;
|
height: auto;
|
||||||
border: 1px solid #aaa;
|
border: 1px solid #aaa;
|
||||||
width: 98%;
|
width: 98%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
li#navbar_plugin_klipper {
|
li#navbar_plugin_klipper {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
max-width:360px;
|
max-width: 360px;
|
||||||
max-height:80px;
|
max-height: 80px;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plugin-klipper-sidebar a {
|
.plugin-klipper-sidebar a {
|
||||||
padding: 2px 2px;
|
padding: 2px 2px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plugin-klipper-sidebar a:hover, .plugin-klipper-sidebar a:active {
|
.plugin-klipper-sidebar a:hover,
|
||||||
cursor: pointer;
|
.plugin-klipper-sidebar a:active {
|
||||||
}
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.plugin-klipper-log {
|
.plugin-klipper-log {
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
height: 400px;
|
height: 400px;
|
||||||
border: 1px solid #eee;
|
border: 1px solid #eee;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plugin-klipper-log .log-item {
|
.plugin-klipper-log .log-item {
|
||||||
margin: 3px auto 0 auto;
|
margin: 3px auto 0 auto;
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
background-color: #efefef;
|
background-color: #efefef;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plugin-klipper-log .error {
|
.plugin-klipper-log .error {
|
||||||
background-color: #eebabb;
|
background-color: #eebabb;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plugin-klipper-log .log-item .ts {
|
.plugin-klipper-log .log-item .ts {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 13%;
|
width: 13%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
padding: 0 0 0 5px;
|
padding: 0 0 0 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plugin-klipper-log .log-item .msg {
|
.plugin-klipper-log .log-item .msg {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 84%;
|
width: 84%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clear-btn {
|
.clear-btn {
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
margin-bottom: 6px;
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#level .controls {
|
#level .controls {
|
||||||
padding: 1px;
|
padding: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul#klipper-settings {
|
ul#klipper-settings {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#klipper-settings a{
|
#klipper-settings a {
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tab_plugin_klipper_main .row-fluid {
|
#tab_plugin_klipper_main .row-fluid {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: row wrap;
|
flex-flow: row wrap;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media all and (max-width: 940px) {
|
@media all and (max-width: 940px) {
|
||||||
#tab_plugin_klipper_main .row-fluid {
|
#tab_plugin_klipper_main .row-fluid {
|
||||||
/* On small screens, we are no longer using row direction but column */
|
/* On small screens, we are no longer using row direction but column */
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#tab_plugin_klipper_main #left-side {
|
#tab_plugin_klipper_main #left-side {
|
||||||
flex: 3 1;
|
flex: 3 1;
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tab_plugin_klipper_main .span8 label {
|
#tab_plugin_klipper_main .span8 label {
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tab_plugin_klipper_main #right-side {
|
#tab_plugin_klipper_main #right-side {
|
||||||
flex: 1 1;
|
flex: 1 1;
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
min-width: 100px;
|
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 {
|
#settings_plugin_klipper {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
height: -webkit-fill-available;
|
height: -webkit-fill-available;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#klipper_backups_dialog div.modal-body textarea {
|
||||||
|
margin-bottom: 0px !important;
|
||||||
|
padding-left: 0px !important;
|
||||||
|
padding-right: 0px !important;
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: -webkit-fill-available;
|
||||||
|
resize: none;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* UIcustomizer fix */
|
/* UIcustomizer fix */
|
||||||
|
|
||||||
body.UICResponsiveMode #settings_dialog_content {
|
body.UICResponsiveMode #settings_dialog_content {
|
||||||
height: calc(100% - 60px);
|
height: calc(100% - 60px);
|
||||||
margin-right: -18px;
|
margin-right: -18px;
|
||||||
margin-top: 50px;
|
margin-top: 50px;
|
||||||
width: calc(100% - 15px);
|
width: calc(100% - 15px);
|
||||||
}
|
}
|
||||||
|
|
||||||
div#settings_plugin_klipper form {
|
div#settings_plugin_klipper form {
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
div#settings_plugin_klipper div.tab-content {
|
div#settings_plugin_klipper div.tab-content {
|
||||||
height: calc(100% - 58px);
|
height: calc(100% - 58px);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
div#settings_plugin_klipper div.tab-footer {
|
div#settings_plugin_klipper div.tab-footer {
|
||||||
height: 20px;
|
height: 20px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
position: relative;
|
position: relative;
|
||||||
border-top: 1px solid #eee;
|
border-top: 1px solid #eee;
|
||||||
padding-top: 3px;
|
padding-top: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div#settings_plugin_klipper div.tab-content div#conf.tab-pane {
|
div#settings_plugin_klipper div.tab-content div#conf.tab-pane {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
min-height: 200px;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
div#settings_plugin_klipper div.tab-content div#conf.tab-pane div.control-group {
|
div#settings_plugin_klipper div.tab-content div#conf.tab-pane div.control-group {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
div#settings_plugin_klipper div.tab-content div#conf.tab-pane div.control-group div.editor-controls{
|
.klipper-settings-tab {
|
||||||
margin-bottom: 0px;
|
height: 100%;
|
||||||
height: 26px;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
div#settings_plugin_klipper div.tab-content div#conf.tab-pane div.control-group div.conf-editor {
|
#settings_plugin_klipper .m-0 {
|
||||||
height: 95%;
|
margin: 0;
|
||||||
height: calc(100% - 28px);
|
|
||||||
width: 99%;
|
|
||||||
width: calc(100% - 4px);
|
|
||||||
margin-top: 5px;
|
|
||||||
flex: 1 1;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
div#settings_plugin_klipper div.tab-content div#conf.tab-pane div.control-group div.conf-editor div#plugin-klipper-config {
|
#settings_plugin_klipper .scroll-y {
|
||||||
font-family: monospace;
|
overflow-y: scroll;
|
||||||
overflow: auto;
|
|
||||||
height: 100%;
|
|
||||||
height: -webkit-fill-available;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
div#settings_plugin_klipper.tab-pane.active form.form-horizontal div.tab-content div#conf.tab-pane.active button.btn.btn-small {
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#klipper_editor {
|
||||||
|
height: 80%;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 979px) {
|
||||||
|
|
||||||
|
div#klipper_editor.modal {
|
||||||
|
height: unset !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
div#klipper_editor .modal-header {
|
||||||
|
height: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#klipper_editor .modal-body {
|
||||||
|
overflow: auto;
|
||||||
|
min-height: 300px;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.klipper-btn-group {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#klipper_editor .modal-body label.checkbox {
|
||||||
|
display: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#klipper_editor .modal-body input[type="text"] {
|
||||||
|
margin-bottom: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#klipper_editor .modal-body input[type="checkbox"] {
|
||||||
|
float: unset;
|
||||||
|
margin-left: unset;
|
||||||
|
vertical-align: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#klipper_editor .modal-body .editor-controls {
|
||||||
|
flex: 0 auto 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#klipper_editor .flex-end {
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#klipper_editor div.conf-editor {
|
||||||
|
width: 99%;
|
||||||
|
width: calc(100% - 4px);
|
||||||
|
margin-top: 5px;
|
||||||
|
overflow: auto;
|
||||||
|
flex-grow : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#klipper_editor div.conf-editor div#plugin-klipper-config {
|
||||||
|
font-family: monospace;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ace_editor {
|
||||||
|
margin: auto;
|
||||||
|
height: 200px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* div#settings_plugin_klipper.tab-pane.active form.form-horizontal div.tab-content div#conf.tab-pane.active button.btn.btn-small ,div#klipper_editor button.btn.btn-small{
|
||||||
width: 30%;
|
width: 30%;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 0px 2px 2px 2px;
|
margin: 0px 2px 2px 2px;
|
||||||
}
|
} */
|
||||||
|
|
||||||
/*checkboxes*/
|
/*checkboxes*/
|
||||||
div#settings_plugin_klipper.tab-pane.active form.form-horizontal div.tab-content div.tab-pane.active input.inline-checkbox {
|
div#settings_plugin_klipper.tab-pane.active form.form-horizontal div.tab-content div.tab-pane.active input.inline-checkbox,
|
||||||
vertical-align: -0.2em;
|
div#klipper_editor input.inline-checkbox {
|
||||||
|
vertical-align: -0.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
div#settings_plugin_klipper.tab-pane.active form.form-horizontal div.tab-content div.tab-pane.active label.inline {
|
div#settings_plugin_klipper.tab-pane.active form.form-horizontal div.tab-content div.tab-pane.active label.inline,
|
||||||
display: 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 {
|
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*/
|
/*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 {
|
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;
|
width: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#macros #item.control-group {
|
#macros #item.control-group {
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
border: 2px solid #ccc;
|
border: 2px solid #ccc;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
background-color: #eeeeee;
|
background-color: #eeeeee;
|
||||||
color: #333;
|
color: #333;
|
||||||
padding-bottom: 2px;
|
padding-bottom: 2px;
|
||||||
padding-top: 2px;
|
padding-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#klipper_graph_dialog {
|
#klipper_graph_dialog {
|
||||||
width: 1050px;
|
width: 90%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#klipper_graph_dialog .full-sized-box{
|
#klipper_graph_dialog .full-sized-box {
|
||||||
width: 1000px;
|
width: 100%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#klipper_graph_dialog form {
|
#klipper_graph_dialog form {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#klipper_graph_dialog select {
|
#klipper_graph_dialog select {
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#klipper_graph_dialog .graph-footer {
|
#klipper_graph_dialog .graph-footer {
|
||||||
bottom:0;
|
bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#klipper_graph_dialog input {
|
#klipper_graph_dialog input {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
#klipper_graph_dialog .status-label {
|
#klipper_graph_dialog .status-label {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin: 5px 0 0 10px;
|
margin: 5px 0 0 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#klipper_graph_dialog .fill-checkbox {
|
#klipper_graph_dialog .fill-checkbox {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0%;
|
top: 0%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#klipper_graph_dialog .help-inline {
|
#klipper_graph_dialog .help-inline {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#klipper_graph_canvas {
|
#klipper_graph_canvas {
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,251 +14,236 @@
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
$(function () {
|
$(function () {
|
||||||
function KlipperViewModel(parameters) {
|
function KlipperViewModel(parameters) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.header = OctoPrint.getRequestHeaders({
|
self.header = OctoPrint.getRequestHeaders({
|
||||||
"content-type": "application/json",
|
"content-type": "application/json",
|
||||||
"cache-control": "no-cache"
|
"cache-control": "no-cache",
|
||||||
});
|
|
||||||
|
|
||||||
self.apiUrl = OctoPrint.getSimpleApiUrl("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.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.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();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
OCTOPRINT_VIEWMODELS.push({
|
|
||||||
construct: KlipperViewModel,
|
|
||||||
dependencies: [
|
|
||||||
"settingsViewModel",
|
|
||||||
"loginStateViewModel",
|
|
||||||
"connectionViewModel",
|
|
||||||
"klipperLevelingViewModel",
|
|
||||||
"klipperMacroDialogViewModel",
|
|
||||||
"accessViewModel"
|
|
||||||
],
|
|
||||||
elements: [
|
|
||||||
"#tab_plugin_klipper_main",
|
|
||||||
"#sidebar_plugin_klipper",
|
|
||||||
"#navbar_plugin_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.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,
|
||||||
|
icon: true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// OctoKlipper settings link
|
||||||
|
self.openOctoKlipperSettings = function (profile_type) {
|
||||||
|
self.consoleMessage("debug", ": openOctoKlipperSettings :");
|
||||||
|
if (!self.hasRight("CONFIG")) return;
|
||||||
|
self.consoleMessage("debug", ": openOctoKlipperSettings : Access okay");
|
||||||
|
$("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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
OCTOPRINT_VIEWMODELS.push({
|
||||||
|
construct: KlipperViewModel,
|
||||||
|
dependencies: [
|
||||||
|
"settingsViewModel",
|
||||||
|
"loginStateViewModel",
|
||||||
|
"connectionViewModel",
|
||||||
|
"klipperLevelingViewModel",
|
||||||
|
"klipperMacroDialogViewModel",
|
||||||
|
"accessViewModel",
|
||||||
|
],
|
||||||
|
elements: [
|
||||||
|
"#tab_plugin_klipper_main",
|
||||||
|
"#sidebar_plugin_klipper",
|
||||||
|
"#navbar_plugin_klipper",
|
||||||
|
],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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 () {
|
||||||
|
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.klipperViewModel.consoleMessage("debug", "listBakFilesdone: " + 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: " + response.text);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
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: " + response.text);
|
||||||
|
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"],
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,234 @@
|
||||||
|
// <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;
|
||||||
|
|
||||||
|
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",
|
||||||
|
});
|
||||||
|
|
||||||
|
self.process = function (config) {
|
||||||
|
self.config = config;
|
||||||
|
self.CfgFilename(config.file);
|
||||||
|
self.CfgContent(config.content);
|
||||||
|
|
||||||
|
if (editor) {
|
||||||
|
editor.session.setValue(config.content);
|
||||||
|
editor.clearSelection();
|
||||||
|
}
|
||||||
|
setInterval(function () {
|
||||||
|
if (editor) {
|
||||||
|
|
||||||
|
|
||||||
|
var modalbodyheight = $('#klipper_editor').height();
|
||||||
|
//$('#conf_editor').height( modalbodyheight - 135 );
|
||||||
|
editor.resize();
|
||||||
|
|
||||||
|
};
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
self.checkSyntax = function () {
|
||||||
|
if (editor.session) {
|
||||||
|
self.klipperViewModel.consoleMessage("debug", "checkSyntax:");
|
||||||
|
|
||||||
|
OctoPrint.plugins.klipper.checkCfg(editor.session.getValue())
|
||||||
|
.done(function (response) {
|
||||||
|
var msg = ""
|
||||||
|
if (response.is_syntax_ok == true) {
|
||||||
|
msg = gettext('Syntax OK')
|
||||||
|
} else {
|
||||||
|
msg = gettext('Syntax NOK')
|
||||||
|
}
|
||||||
|
showMessageDialog(
|
||||||
|
msg,
|
||||||
|
{
|
||||||
|
title: gettext("SyntaxCheck")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
self.saveCfg = function () {
|
||||||
|
if (editor.session) {
|
||||||
|
self.klipperViewModel.consoleMessage("debug", "Save:");
|
||||||
|
|
||||||
|
OctoPrint.plugins.klipper.saveCfg(editor.session.getValue(), self.CfgFilename())
|
||||||
|
.done(function (response) {
|
||||||
|
var msg = ""
|
||||||
|
if (response.saved === true) {
|
||||||
|
msg = gettext('File saved.')
|
||||||
|
} else {
|
||||||
|
msg = gettext('File not saved.')
|
||||||
|
}
|
||||||
|
showMessageDialog(
|
||||||
|
msg,
|
||||||
|
{
|
||||||
|
title: gettext("Save File")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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.temp_config() != "") {
|
||||||
|
self.klipperViewModel.consoleMessage(
|
||||||
|
"info",
|
||||||
|
"lastSession:" +
|
||||||
|
self.settings.settings.plugins.klipper.configuration.temp_config()
|
||||||
|
);
|
||||||
|
if (editor.session) {
|
||||||
|
editor.session.setValue(
|
||||||
|
self.settings.settings.plugins.klipper.configuration.temp_config()
|
||||||
|
);
|
||||||
|
editor.clearSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.reloadFromFile = function () {
|
||||||
|
|
||||||
|
OctoPrint.plugins.klipper.getCfg(self.CfgFilename())
|
||||||
|
.done(function (response) {
|
||||||
|
self.klipperViewModel.consoleMessage("debug", "reloadFromFile: " + response);
|
||||||
|
if (response.response.text != "") {
|
||||||
|
var msg = response.response.text
|
||||||
|
showMessageDialog(
|
||||||
|
msg,
|
||||||
|
{
|
||||||
|
title: gettext("Reload File")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
if (editor) {
|
||||||
|
editor.session.setValue(response.response.config);
|
||||||
|
editor.clearSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.fail(function (response) {
|
||||||
|
var msg = response
|
||||||
|
showMessageDialog(
|
||||||
|
msg,
|
||||||
|
{
|
||||||
|
title: gettext("Reload File")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
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",
|
||||||
|
minLines: 100
|
||||||
|
//maxLines: "Infinity"
|
||||||
|
});
|
||||||
|
|
||||||
|
editor.session.on('change', function (delta) {
|
||||||
|
if (obKlipperConfig) {
|
||||||
|
obKlipperConfig.silentUpdate(editor.getValue());
|
||||||
|
editor.resize();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
OCTOPRINT_VIEWMODELS.push({
|
||||||
|
construct: KlipperEditorViewModel,
|
||||||
|
dependencies: ["settingsViewModel", "klipperViewModel"],
|
||||||
|
elements: ["#klipper_editor"],
|
||||||
|
});
|
||||||
|
});
|
|
@ -13,243 +13,293 @@
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// 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/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
$(function() {
|
$(function () {
|
||||||
$('#klipper-settings a:first').tab('show');
|
$("#klipper-settings a:first").tab("show");
|
||||||
function KlipperSettingsViewModel(parameters) {
|
function KlipperSettingsViewModel(parameters) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var obKlipperConfig = null;
|
|
||||||
var editor = null;
|
|
||||||
|
|
||||||
self.settings = parameters[0];
|
self.settings = parameters[0];
|
||||||
self.klipperViewModel = parameters[1];
|
self.klipperViewModel = parameters[1];
|
||||||
|
self.klipperEditorViewModel = parameters[2];
|
||||||
|
self.klipperBackupViewModel = parameters[3];
|
||||||
|
self.access = parameters[4];
|
||||||
|
|
||||||
self.header = OctoPrint.getRequestHeaders({
|
self.header = OctoPrint.getRequestHeaders({
|
||||||
"content-type": "application/json",
|
"content-type": "application/json",
|
||||||
"cache-control": "no-cache"
|
"cache-control": "no-cache",
|
||||||
});
|
|
||||||
|
|
||||||
self.apiUrl = OctoPrint.getSimpleApiUrl("klipper");
|
|
||||||
|
|
||||||
self.onSettingsBeforeSave = function () {
|
|
||||||
if (editor.session) {
|
|
||||||
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) {
|
|
||||||
if (editor.session) {
|
|
||||||
editor.session.setValue(response["data"]);
|
|
||||||
editor.clearSelection();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.loadCfgBackup = function () {
|
|
||||||
if (editor.session) {
|
|
||||||
var settings = {
|
|
||||||
"crossDomain": true,
|
|
||||||
"url": self.apiUrl,
|
|
||||||
"method": "POST",
|
|
||||||
"headers": self.header,
|
|
||||||
"processData": false,
|
|
||||||
"dataType": "json",
|
|
||||||
"data": JSON.stringify({command: "reloadCfgBackup"})
|
|
||||||
}
|
|
||||||
|
|
||||||
$.ajax(settings).done(function (response) {
|
|
||||||
if (editor.session) {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OCTOPRINT_VIEWMODELS.push({
|
|
||||||
construct: KlipperSettingsViewModel,
|
|
||||||
dependencies: [
|
|
||||||
"settingsViewModel",
|
|
||||||
"klipperViewModel"
|
|
||||||
],
|
|
||||||
elements: ["#settings_plugin_klipper"]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
self.markedForFileRemove = ko.observableArray([]);
|
||||||
|
|
||||||
|
// 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.listCfgFiles = function () {
|
||||||
|
self.klipperViewModel.consoleMessage("debug", "listCfgFiles:");
|
||||||
|
|
||||||
|
OctoPrint.plugins.klipper.listCfg().done(function (response) {
|
||||||
|
self.klipperViewModel.consoleMessage("debug", "listCfgFiles: " + response);
|
||||||
|
self.configs.updateItems(response.files);
|
||||||
|
self.configs.resetPage();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
self.removeCfg = function (config) {
|
||||||
|
if (!self.klipperViewModel.hasRight("CONFIG")) return;
|
||||||
|
|
||||||
|
var perform = function () {
|
||||||
|
OctoPrint.plugins.klipper
|
||||||
|
.deleteCfg(config)
|
||||||
|
.done(function () {
|
||||||
|
self.listCfgFiles();
|
||||||
|
var html = "<p>" + _.sprintf(gettext("All fine</p>"), { name: _.escape(config) });
|
||||||
|
new PNotify({
|
||||||
|
title: gettext("All is fine"),
|
||||||
|
text: html,
|
||||||
|
type: "error",
|
||||||
|
hide: false,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.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",
|
||||||
|
minHeight: "600px",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
self.newFile = function () {
|
||||||
|
if (!self.klipperViewModel.hasRight("CONFIG")) return;
|
||||||
|
var config = {
|
||||||
|
content: "",
|
||||||
|
file: "Change Filename",
|
||||||
|
};
|
||||||
|
self.klipperEditorViewModel.process(config);
|
||||||
|
var editorDialog = $("#klipper_editor");
|
||||||
|
editorDialog.modal({
|
||||||
|
show: "true",
|
||||||
|
backdrop: "static",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
self.showEditUserDialog = 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);
|
||||||
|
|
||||||
|
var editorDialog = $("#klipper_editor");
|
||||||
|
editorDialog.modal({
|
||||||
|
show: "true",
|
||||||
|
backdrop: "static",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
OCTOPRINT_VIEWMODELS.push({
|
||||||
|
construct: KlipperSettingsViewModel,
|
||||||
|
dependencies: ["settingsViewModel", "klipperViewModel", "klipperEditorViewModel", "klipperBackupViewModel", "accessViewModel"],
|
||||||
|
elements: ["#settings_plugin_klipper"],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
<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">×</button>
|
||||||
|
<h3 id="klipper_dialog_label">{{ _('Backups') }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body klipper-column-fluid">
|
||||||
|
<div class="klipper-row-fluid">
|
||||||
|
<div class="klipper-fluid-item-2" data-bind="visible: $root.klipperViewModel.hasRightKo('CONFIG')">
|
||||||
|
<button class="btn btn-small" data-bind="click: listBakFiles" title="{{ _('Refresh file list') }}"><i class="icon-refresh"></i>
|
||||||
|
Refresh</button>
|
||||||
|
<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: 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>
|
||||||
|
|
|
||||||
|
<a href="javascript:void(0)" class="fas fa-download" title="{{ _('Restore') }}"
|
||||||
|
data-bind="css: {disabled: !$root.klipperViewModel.hasRightKo('CONFIG')()}, click: function() { $parent.restoreBak($data.name); }"></a>
|
||||||
|
|
|
||||||
|
<a href="javascript:void(0)" class="fas fa-camera" 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="klipper-fluid-item-3">
|
||||||
|
<textarea readonly data-bind="value: CfgContent" id="klipper_bak_text"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,45 @@
|
||||||
|
<div id="klipper_editor" class="modal hide fade large" tabindex="-1" role="dialog" aria-labelledby="klipper_editor_label" aria-hidden="true">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||||
|
<h3 id="klipper_dialog_label">{{ _('Editor') }}</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="editor-controls">
|
||||||
|
<span class="control-label">Filename:</span>
|
||||||
|
<input type="text" data-bind="value: CfgFilename">
|
||||||
|
<div class="klipper-btn-group">
|
||||||
|
<button class="btn btn-small" data-bind="click: saveCfg" title="{{ _('Save Config') }}">
|
||||||
|
<i class="fas fa-save"></i> {{ _('Save Config') }}
|
||||||
|
</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: reloadFromFile" title="{{ _('Reload from file') }}">
|
||||||
|
<i class="fas fa-upload"></i> {{ _('Reload from file') }}
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-small" data-bind="click: checkSyntax" title="{{ _('Check Syntax') }}">
|
||||||
|
<i class="fas fa-spell-check"></i> {{ _('Check Syntax') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<span class="pull-right">
|
||||||
|
<label class="checkbox">
|
||||||
|
<input class="inline-checkbox" type="checkbox" data-bind="checked: settings.settings.plugins.klipper.configuration.parse_check">
|
||||||
|
{{ _('Check parsing on save') }}
|
||||||
|
</label>
|
||||||
|
<a href='#' style="text-decoration: none;" data-bind="click: minusFontsize" title="{{ _('Decrease Fontsize') }}">
|
||||||
|
<i class="fas fa-search-minus"></i>
|
||||||
|
</a>
|
||||||
|
<a href='#' style="text-decoration: none;" data-bind="click: plusFontsize" title="{{ _('Increase Fontsize') }}">
|
||||||
|
<i class="fas fa-search-plus"></i>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="conf-editor" id="conf_editor">
|
||||||
|
<input id="hdnLoadKlipperConfig" type="hidden" data-bind="value: configBound(CfgContent)">
|
||||||
|
<div id="plugin-klipper-config"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -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 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">
|
<div class="modal-header">
|
||||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||||
<h3 id="klipper_pid_tuning_dialog_label">{{ _('Performance Graph') }}</h3>
|
<h3 id="klipper_dialog_label">{{ _('Performance Graph') }}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="full-sized-box">
|
<div class="full-sized-box">
|
||||||
|
|
|
@ -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 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">
|
<div class="modal-header">
|
||||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||||
<h3 id="klipper_pid_tuning_dialog_label">{{ _('Coordinate Offset') }}</h3>
|
<h3 id="klipper_dialog_label">{{ _('Coordinate Offset') }}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="row-fluid" style="margin-bottom: 15px">
|
<div class="row-fluid" style="margin-bottom: 15px">
|
||||||
|
|
|
@ -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 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">
|
<div class="modal-header">
|
||||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
<button type="button" class="close" data-dismiss="modal">×</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>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="control-group" data-bind="foreach: parameters">
|
<div class="control-group" data-bind="foreach: parameters">
|
||||||
|
|
|
@ -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 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">
|
<div class="modal-header">
|
||||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||||
<h3 id="klipper_pid_tuning_dialog_label">{{ _('PID Tuning') }}</h3>
|
<h3 id="klipper_dialog_label">{{ _('PID Tuning') }}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
|
|
|
@ -1,236 +1,317 @@
|
||||||
<form class="form-horizontal">
|
<form class="form-horizontal">
|
||||||
<ul class="nav nav-pills" id="klipper-settings">
|
<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="#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="#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="#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>
|
<li><a href="#conf" data-toggle="tab" data-profile-type="klipper-config">{{ _('Klipper Configuration') }}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<!-- Basics -->
|
<!-- Basics -->
|
||||||
<div class="tab-pane active" id="basic">
|
<div class="tab-pane active" id="basic">
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label class="control-label">{{ _('Serial Port') }}</label>
|
<label class="control-label">{{ _('Serial Port') }}</label>
|
||||||
<div class="controls">
|
<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>
|
</div>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label class="control-label">{{ _('Replace Connection Panel') }}</label>
|
<label class="control-label">{{ _('Replace Connection Panel') }}</label>
|
||||||
<div class="controls">
|
<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>
|
</div>
|
||||||
|
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label class="control-label">{{ _('Show Short Messages') }}</label>
|
<label class="control-label">{{ _('Show Short Messages') }}</label>
|
||||||
<div class="controls">
|
<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 NavBar') }}"><input type="checkbox"
|
||||||
<label class="checkbox" title="{{ _('on SideBar') }}"><input type="checkbox" data-bind="checked: settings.settings.plugins.klipper.configuration.shortStatus_sidebar"> {{ _('on SideBar') }}</label>
|
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>
|
</div>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label class="control-label">{{ _('Enable debug logging') }}</label>
|
<label class="control-label">{{ _('Enable debug logging') }}</label>
|
||||||
<div class="controls">
|
<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>
|
</div>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label class="control-label">{{ _('Klipper Config File') }}</label>
|
<label class="control-label">{{ _('Klipper Config File') }}</label>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<input type="text" class="input-block-level" data-bind="value: settings.settings.plugins.klipper.configuration.configpath">
|
<input type="text" class="input-block-level" data-bind="value: settings.settings.plugins.klipper.configuration.configpath" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label class="control-label">{{ _('Klipper Log File') }}</label>
|
<label class="control-label">{{ _('Klipper Log File') }}</label>
|
||||||
<div class="controls">
|
<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>
|
</div>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label class="control-label">{{ _('Configuration Reload Command') }}</label>
|
<label class="control-label">{{ _('Configuration Reload Command') }}</label>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<select data-bind="value: settings.settings.plugins.klipper.configuration.reload_command">
|
<select data-bind="value: settings.settings.plugins.klipper.configuration.reload_command">
|
||||||
<option value="RESTART">RESTART</option>
|
<option value="RESTART">RESTART</option>
|
||||||
<option value="FIRMWARE_RESTART">FIRMWARE_RESTART</option>
|
<option value="FIRMWARE_RESTART">FIRMWARE_RESTART</option>
|
||||||
<option value="manually">Manually</option>
|
<option value="manually">Manually</option>
|
||||||
</select>
|
</select>
|
||||||
<span class="help-block">
|
<span class="help-block">
|
||||||
{{ _('The command that is executed when the Klipper configuration changed and needs to be reloaded.<br>
|
{{ _('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.') }}
|
Set this to "Manually" if you don\'t want to immediately restart klipper.') }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
<!-- Macros -->
|
||||||
<!-- Macros -->
|
<div class="tab-pane" id="macros">
|
||||||
<div class="tab-pane" id="macros">
|
<div class="control-group" style="margin-bottom: 0px;">
|
||||||
<div class="control-group" style="margin-bottom: 0px;">
|
<div class="controls" style="margin-left: 82px;">
|
||||||
<div class="controls" style="margin-left: 82px;">
|
|
||||||
<div class="row-fluid">
|
<div class="row-fluid">
|
||||||
<div class="span8" style="text-align: right"><small>{{ _('Add macro button to:') }}</small></div>
|
<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="span1" style="margin: auto;text-align: center"><small>{{ _('Klipper Tab') }}</small></div>
|
||||||
<div class="span2" style="margin: auto;"><small>{{ _('Sidebar') }}</small></div>
|
<div class="span2" style="margin: auto;"><small>{{ _('Sidebar') }}</small></div>
|
||||||
</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>
|
||||||
</div>
|
<div data-bind="foreach: settings.settings.plugins.klipper.macros">
|
||||||
<div class="control-group">
|
<div class="control-group" id="item">
|
||||||
<label class="control-label">{{ _('Feedrate X/Y') }}</label>
|
<label class="control-label">{{ _('Name') }}</label>
|
||||||
<div class="controls">
|
<div class="controls" style="margin-left: 82px;">
|
||||||
<div class="input-append">
|
<div class="row-fluid">
|
||||||
<input type="text" class="input-block-level span3" data-bind="value: settings.settings.plugins.klipper.probe.speed_xy">
|
<div class="span8">
|
||||||
<span class="add-on">mm/min</span>
|
<input type="text" class="input-block-level" data-bind="value: name" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="span1" style="margin: auto; text-align: center;">
|
||||||
</div>
|
<input title="{{ _('Klipper Tab') }}" style="margin: auto;" type="checkbox" class="input-block-level" data-bind="checked: tab" />
|
||||||
<div class="control-group">
|
</div>
|
||||||
<h5>{{ _('Probe Points') }}</h5>
|
<div class="span1" style="margin: auto; text-align: center;">
|
||||||
<div class="controls">
|
<input title="{{ _('Sidebar') }}" style="margin: auto;" type="checkbox" class="input-block-level" data-bind="checked: sidebar" />
|
||||||
<div class="row-fluid">
|
</div>
|
||||||
<div class="span3">name</div>
|
<div class="span2" style="margin: auto; text-align: center;">
|
||||||
<div class="span3">x(mm)</div>
|
<a href='#' style="vertical-align: bottom;" data-bind='click: $parent.moveMacroUp' class="fa fa-chevron-up"></a>
|
||||||
<div class="span3">y(mm)</div>
|
<a href='#' style="vertical-align: bottom;" data-bind='click: $parent.moveMacroDown' class="fa fa-chevron-down"></a>
|
||||||
<div class="span3"> </div>
|
<a href='#' style="vertical-align: bottom;" data-bind='click: $parent.removeMacro' class="fa fa-trash-o"></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div data-bind="foreach: settings.settings.plugins.klipper.probe.points" class="control-group">
|
<label class="control-label">{{ _('Command') }}</label>
|
||||||
<label class="control-label" data-bind="text: $index"></label>
|
<div class="controls" style="margin-left: 82px;">
|
||||||
<div class="controls">
|
<div class="row-fluid">
|
||||||
<div class="row-fluid">
|
<div class="span12" style="margin-top:2px;">
|
||||||
<div class="span3"><input type="text" class="input-block-level" data-bind="value: name"></div>
|
<textarea rows="2" class="block" data-bind="value: macro">
|
||||||
<div class="span3"><input type="text" class="input-block-level" data-bind="value: x"></div>
|
</textarea>
|
||||||
<div class="span3"><input type="text" class="input-block-level" data-bind="value: y"></div>
|
</div>
|
||||||
<div class="span3">
|
</div>
|
||||||
<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>
|
</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>
|
||||||
<div class="control-group">
|
<!-- Leveling -->
|
||||||
<div class="controls">
|
<div class="tab-pane" id="level">
|
||||||
<a href='#' data-bind="click: addProbePoint" title="{{ _('Add Point') }}" class="fa fa-plus-circle"></a> {{ _('Add Point') }}
|
<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')">
|
||||||
|
<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: listCfgFiles" title="{{ _('Refresh file list') }}">
|
||||||
|
<i class="icon-refresh"></i> {{ _('Refresh Files') }}
|
||||||
|
</button>
|
||||||
|
<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: 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>
|
||||||
|
|
|
||||||
|
<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>
|
||||||
|
|
|
||||||
|
<a href="javascript:void(0)" class="fas fa-pencil-alt" title="{{ _('Edit') }}"
|
||||||
|
data-bind="css: {disabled: !$root.klipperViewModel.hasRightKo('CONFIG')()}, click: function() { $parent.showEditUserDialog($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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Klipper Conf -->
|
<div class="tab-footer">
|
||||||
<div class="tab-pane" id="conf">
|
<a href="https://www.paypal.com/donate/?business=7P63W664NF8LA&item_name=OctoKlipper" class="btn btn-mini" target="_blank">
|
||||||
<div class="control-group">
|
<i class="fab fa-paypal"> {{ _('Donate') }}</i>
|
||||||
<script src="plugin/klipper/static/js/lib/ace/ace.min.js" type="text/javascript" charset="utf-8"></script>
|
</a>
|
||||||
<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: loadCfgBackup"
|
|
||||||
title="{{ _('Reload last version') }}">
|
|
||||||
<i class="fas fa-redo"></i> {{ _('Reload last version') }}
|
|
||||||
</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>
|
|
||||||
|
|
||||||
<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>
|
</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>
|
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<label for="connection_printers" data-bind="css: {disabled: !connectionState.isErrorOrClosed()}, enable: connectionState.isErrorOrClosed() && loginState.isUser()">{{ _('Printer Profile') }}</label>
|
<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>
|
<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="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" data-bind="visible: $root.loginState.hasPermissionKo($root.access.permissions.PLUGIN_KLIPPER_CONFIG), click: function() {openOctoKlipperSettings('klipper-config');}">{{ _('Open Klipper config') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- ko if: settings.settings.plugins.klipper.configuration.shortStatus_sidebar -->
|
<!-- 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>
|
<a title="{{ _('Go to OctoKlipper Tab') }}" data-bind="text: shortStatus_sidebar, click: navbarClicked"></a>
|
||||||
</div>
|
</div>
|
||||||
<!-- /ko -->
|
<!-- /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">
|
<div class="controls">
|
||||||
<label class="control-label small"><i class="icon-list-alt"></i> {{ _('Macros') }}</label>
|
<label class="control-label small"><i class="icon-list-alt"></i> {{ _('Macros') }}</label>
|
||||||
<div data-bind="foreach: settings.settings.plugins.klipper.macros">
|
<div data-bind="foreach: settings.settings.plugins.klipper.macros">
|
||||||
|
|
|
@ -8,11 +8,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button class="btn btn-mini pull-right clear-btn" data-bind="click: onClearLog" title="{{ _('Clear Log') }}">
|
||||||
class="btn btn-mini pull-right clear-btn"
|
|
||||||
data-bind="click: onClearLog"
|
|
||||||
title="{{ _('Clear Log') }}"
|
|
||||||
>
|
|
||||||
<i class="fa fa-trash"></i> {{ _('Clear Log') }}
|
<i class="fa fa-trash"></i> {{ _('Clear Log') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -21,18 +17,13 @@
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<label class="control-label"></label>
|
<label class="control-label"></label>
|
||||||
<button
|
<button class="btn btn-block btn-small" data-bind="click: onGetStatus, enable: isActive()"
|
||||||
class="btn btn-block btn-small"
|
title="{{ _('Query Klipper for its current status') }}">
|
||||||
data-bind="click: onGetStatus, enable: isActive()"
|
|
||||||
title="{{ _('Query Klipper for its current status') }}"
|
|
||||||
>
|
|
||||||
<i class="fa icon-black fa-info-circle"></i> {{ _("Get Status") }}
|
<i class="fa icon-black fa-info-circle"></i> {{ _("Get Status") }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button class="btn btn-block btn-small"
|
||||||
class="btn btn-block btn-small"
|
data-bind="visible: $root.loginState.hasPermissionKo($root.access.permissions.PLUGIN_KLIPPER_CONFIG), click: function() {openOctoKlipperSettings('klipper-config');}"
|
||||||
data-bind="visible: hasRight('CONFIG', 'Ko'), click: function() {openOctoKlipperSettings('klipper-config');}"
|
title="{{ _('Open the Klipper configuration file') }}">
|
||||||
title="{{ _('Open the Klipper configuration file') }}"
|
|
||||||
>
|
|
||||||
<i class="fa icon-black fa-file-code-o"></i>
|
<i class="fa icon-black fa-file-code-o"></i>
|
||||||
{{ _("Open Klipper config") }}
|
{{ _("Open Klipper config") }}
|
||||||
</button>
|
</button>
|
||||||
|
@ -40,70 +31,44 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<label class="control-label small"
|
<label class="control-label small"><i class="icon-refresh"></i> {{ _("Restart") }}</label>
|
||||||
><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') }}">
|
||||||
<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") }}
|
{{ _("Host") }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button class="btn btn-block btn-small" data-bind="click: onRestartFirmware, enable: isActive()"
|
||||||
class="btn btn-block btn-small"
|
title="{{ _('Similar to a host restart, but also clears any error state from the micro-controller') }}">
|
||||||
data-bind="click: onRestartFirmware, enable: isActive()"
|
|
||||||
title="{{ _('Similar to a host restart, but also clears any error state from the micro-controller') }}"
|
|
||||||
>
|
|
||||||
{{ _("Firmware") }}
|
{{ _("Firmware") }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<label class="control-label"
|
<label class="control-label"><i class="icon-wrench"></i> {{ _("Tools") }}</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.') }}">
|
||||||
<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") }}
|
{{ _("Assisted Bed Leveling") }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button class="btn btn-block btn-small" data-bind="click: showPidTuningDialog, enable: isActive()"
|
||||||
class="btn btn-block btn-small"
|
title="{{ _('Determines optimal PID parameters by heat cycling the hotend/bed.') }}">
|
||||||
data-bind="click: showPidTuningDialog, enable: isActive()"
|
|
||||||
title="{{ _('Determines optimal PID parameters by heat cycling the hotend/bed.') }}"
|
|
||||||
>
|
|
||||||
{{ _("PID Tuning") }}
|
{{ _("PID Tuning") }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button class="btn btn-block btn-small" data-bind="click: showOffsetDialog, enable: isActive()"
|
||||||
class="btn btn-block btn-small"
|
title="{{ _('Sets a offset for subsequent GCODE coordinates.') }}">
|
||||||
data-bind="click: showOffsetDialog, enable: isActive()"
|
|
||||||
title="{{ _('Sets a offset for subsequent GCODE coordinates.') }}"
|
|
||||||
>
|
|
||||||
{{ _("Coordinate Offset") }}
|
{{ _("Coordinate Offset") }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button class="btn btn-block btn-small" data-bind="click: showGraphDialog"
|
||||||
class="btn btn-block btn-small"
|
title="{{ _('Assists in debugging performance issues by analyzing the Klipper log files.') }}">
|
||||||
data-bind="click: showGraphDialog"
|
|
||||||
title="{{ _('Assists in debugging performance issues by analyzing the Klipper log files.') }}"
|
|
||||||
>
|
|
||||||
{{ _("Analyze Klipper Log") }}
|
{{ _("Analyze Klipper Log") }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="controls" data-bind="visible: hasRight('MACRO', 'Ko')">
|
<div class="controls" data-bind="visible: $root.loginState.hasPermissionKo($root.access.permissions.PLUGIN_KLIPPER_MACRO)">
|
||||||
<label class="control-label"
|
<label class="control-label"><i class="icon-list-alt"></i> {{ _("Macros") }}</label>
|
||||||
><i class="icon-list-alt"></i> {{ _("Macros") }}</label
|
|
||||||
>
|
|
||||||
<div data-bind="foreach: settings.settings.plugins.klipper.macros">
|
<div data-bind="foreach: settings.settings.plugins.klipper.macros">
|
||||||
<!-- ko if: tab -->
|
<!-- ko if: tab -->
|
||||||
<button
|
<button class="btn btn-block btn-small" data-bind="text: name, click: $parent.executeMacro, enable: $parent.isActive()">
|
||||||
class="btn btn-block btn-small"
|
</button>
|
||||||
data-bind="text: name, click: $parent.executeMacro, enable: $parent.isActive()"
|
|
||||||
></button>
|
|
||||||
<!-- /ko -->
|
<!-- /ko -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
||||||
|
)
|
Loading…
Reference in New Issue