Merge branch 'feat/multipleCfg'
This commit is contained in:
parent
d70198b14b
commit
b2575c55db
|
@ -9,7 +9,9 @@ charset = utf-8
|
|||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = space
|
||||
max_line_length = 90
|
||||
max_line_length = 180
|
||||
indent_size = 2
|
||||
|
||||
|
||||
[**.py]
|
||||
indent_size = 4
|
||||
|
|
|
@ -14,16 +14,22 @@
|
|||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
import datetime
|
||||
import logging
|
||||
import octoprint.plugin
|
||||
import octoprint.plugin.core
|
||||
import glob
|
||||
import os
|
||||
import time
|
||||
import sys
|
||||
|
||||
from octoprint.server import NO_CONTENT
|
||||
from octoprint.util import is_hidden_path
|
||||
from octoprint.util import get_formatted_size
|
||||
from . import util, cfgUtils, logger
|
||||
from octoprint.util.comm import parse_firmware_line
|
||||
from octoprint.access.permissions import Permissions, ADMIN_GROUP
|
||||
from .modules import KlipperLogAnalyzer
|
||||
from octoprint.server.util.flask import restricted_access
|
||||
import flask
|
||||
from flask_babel import gettext
|
||||
|
||||
|
@ -35,13 +41,16 @@ except ImportError:
|
|||
if sys.version_info[0] < 3:
|
||||
import StringIO
|
||||
|
||||
MAX_UPLOAD_SIZE = 5 * 1024 * 1024 # 5Mb
|
||||
|
||||
class KlipperPlugin(
|
||||
octoprint.plugin.StartupPlugin,
|
||||
octoprint.plugin.TemplatePlugin,
|
||||
octoprint.plugin.SettingsPlugin,
|
||||
octoprint.plugin.AssetPlugin,
|
||||
octoprint.plugin.SimpleApiPlugin,
|
||||
octoprint.plugin.EventHandlerPlugin):
|
||||
octoprint.plugin.EventHandlerPlugin,
|
||||
octoprint.plugin.BlueprintPlugin):
|
||||
|
||||
_parsing_response = False
|
||||
_parsing_check_response = True
|
||||
|
@ -74,8 +83,10 @@ class KlipperPlugin(
|
|||
self._settings.global_set(
|
||||
["serial", "additionalPorts"], additional_ports)
|
||||
self._settings.save()
|
||||
self.log_info(
|
||||
"Added klipper serial port {} to list of additional ports.".format(klipper_port))
|
||||
logger.log_info(
|
||||
self,
|
||||
"Added klipper serial port {} to list of additional ports.".format(klipper_port)
|
||||
)
|
||||
|
||||
# -- Settings Plugin
|
||||
|
||||
|
@ -126,6 +137,7 @@ class KlipperPlugin(
|
|||
debug_logging=False,
|
||||
configpath="~/printer.cfg",
|
||||
old_config="",
|
||||
temp_config="",
|
||||
logpath="/tmp/klippy.log",
|
||||
reload_command="RESTART",
|
||||
shortStatus_navbar=True,
|
||||
|
@ -141,59 +153,17 @@ class KlipperPlugin(
|
|||
configpath = os.path.expanduser(
|
||||
self._settings.get(["configuration", "configpath"])
|
||||
)
|
||||
data["config"] = ""
|
||||
try:
|
||||
f = open(configpath, "r")
|
||||
data["config"] = f.read()
|
||||
f.close()
|
||||
except IOError:
|
||||
self.log_error(
|
||||
"Error: Klipper config file not found at: {}".format(
|
||||
configpath)
|
||||
)
|
||||
else:
|
||||
self.send_message("reload", "config", "", data["config"])
|
||||
standardconfigfile = os.path.join(configpath, "printer.cfg")
|
||||
data["config"] = cfgUtils.get_cfg(self, standardconfigfile)
|
||||
util.send_message(self, "reload", "config", "", data["config"])
|
||||
# send the configdata to frontend to update ace editor
|
||||
return data
|
||||
|
||||
def on_settings_save(self, data):
|
||||
|
||||
self.log_debug(
|
||||
"Save klipper configs"
|
||||
)
|
||||
|
||||
if "config" in data:
|
||||
check_parse = self._settings.get(["configuration", "parse_check"])
|
||||
self.log_debug("check_parse: {}".format(check_parse))
|
||||
|
||||
if sys.version_info[0] < 3:
|
||||
data["config"] = data["config"].encode('utf-8')
|
||||
|
||||
# check for configpath if it was changed during changing of the configfile
|
||||
if self.key_exist(data, "configuration", "configpath"):
|
||||
configpath = os.path.expanduser(
|
||||
data["configuration"]["configpath"]
|
||||
)
|
||||
else:
|
||||
# if the configpath was not changed during changing the printer.cfg. Then the configpath would not be in data[]
|
||||
configpath = os.path.expanduser(
|
||||
self._settings.get(["configuration", "configpath"])
|
||||
)
|
||||
if self.file_exist(configpath):
|
||||
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:
|
||||
if cfgUtils.save_cfg(self, data["config"], "printer.cfg"):
|
||||
#load the reload command from changed data if it is not existing load the saved setting
|
||||
if self.key_exist(data, "configuration", "reload_command"):
|
||||
if util.key_exist(data, "configuration", "reload_command"):
|
||||
reload_command = os.path.expanduser(
|
||||
data["configuration"]["reload_command"]
|
||||
)
|
||||
|
@ -203,8 +173,11 @@ class KlipperPlugin(
|
|||
if reload_command != "manually":
|
||||
# Restart klippy to reload config
|
||||
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
|
||||
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
|
||||
|
@ -280,14 +253,14 @@ class KlipperPlugin(
|
|||
settings.remove(["probePoints"])
|
||||
|
||||
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.remove(["configPath"])
|
||||
|
||||
if current is not None and current < 3:
|
||||
settings = self._settings
|
||||
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.remove(["configuration", "navbar"])
|
||||
|
||||
|
@ -334,6 +307,18 @@ class KlipperPlugin(
|
|||
template="klipper_graph_dialog.jinja2",
|
||||
custom_bindings=True
|
||||
),
|
||||
dict(
|
||||
type="generic",
|
||||
name="Config Backups",
|
||||
template="klipper_backups_dialog.jinja2",
|
||||
custom_bindings=True
|
||||
),
|
||||
dict(
|
||||
type="generic",
|
||||
name="Config Editor",
|
||||
template="klipper_editor.jinja2",
|
||||
custom_bindings=True
|
||||
),
|
||||
dict(
|
||||
type="generic",
|
||||
name="Macro Dialog",
|
||||
|
@ -342,6 +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
|
||||
|
||||
def get_assets(self):
|
||||
|
@ -352,8 +343,11 @@ class KlipperPlugin(
|
|||
"js/klipper_pid_tuning.js",
|
||||
"js/klipper_offset.js",
|
||||
"js/klipper_param_macro.js",
|
||||
"js/klipper_graph.js"
|
||||
"js/klipper_graph.js",
|
||||
"js/klipper_backup.js",
|
||||
"js/klipper_editor.js"
|
||||
],
|
||||
clientjs=["clientjs/klipper.js"],
|
||||
css=["css/klipper.css"]
|
||||
)
|
||||
|
||||
|
@ -361,18 +355,19 @@ class KlipperPlugin(
|
|||
|
||||
def on_event(self, event, payload):
|
||||
if "UserLoggedIn" == event:
|
||||
self.update_status("info", "Klipper: Standby")
|
||||
util.update_status(self, "info", "Klipper: Standby")
|
||||
if "Connecting" == event:
|
||||
self.update_status("info", "Klipper: Connecting ...")
|
||||
util.update_status(self, "info", "Klipper: Connecting ...")
|
||||
elif "Connected" == event:
|
||||
self.update_status("info", "Klipper: Connected to host")
|
||||
self.log_info(
|
||||
util.update_status(self, "info", "Klipper: Connected to host")
|
||||
logger.log_info(
|
||||
self,
|
||||
"Connected to host via {} @{}bps".format(payload["port"], payload["baudrate"]))
|
||||
elif "Disconnected" == event:
|
||||
self.update_status("info", "Klipper: Disconnected from host")
|
||||
util.update_status(self, "info", "Klipper: Disconnected from host")
|
||||
elif "Error" == event:
|
||||
self.update_status("error", "Klipper: Error")
|
||||
self.log_error(payload["error"])
|
||||
util.update_status(self, "error", "Klipper: Error")
|
||||
logger.log_error(self, payload["error"])
|
||||
|
||||
# -- GCODE Hook
|
||||
|
||||
|
@ -381,22 +376,22 @@ class KlipperPlugin(
|
|||
if "FIRMWARE_VERSION" in line:
|
||||
printerInfo = parse_firmware_line(line)
|
||||
if "FIRMWARE_VERSION" in printerInfo:
|
||||
self.log_info("Firmware version: {}".format(
|
||||
logger.log_info(self, "Firmware version: {}".format(
|
||||
printerInfo["FIRMWARE_VERSION"]))
|
||||
elif "// probe" in line or "// Failed to verify BLTouch" in line:
|
||||
msg = line.strip('/')
|
||||
self.log_info(msg)
|
||||
logger.log_info(self, msg)
|
||||
self.write_parsing_response_buffer()
|
||||
elif "//" in line:
|
||||
# add lines with // to a buffer
|
||||
self._message = self._message + line.strip('/')
|
||||
if not self._parsing_response:
|
||||
self.update_status("info", self._message)
|
||||
util.update_status(self, "info", self._message)
|
||||
self._parsing_response = True
|
||||
elif "!!" in line:
|
||||
msg = line.strip('!')
|
||||
self.update_status("error", msg)
|
||||
self.log_error(msg)
|
||||
util.update_status(self, "error", msg)
|
||||
logger.log_error(self, msg)
|
||||
self.write_parsing_response_buffer()
|
||||
else:
|
||||
self.write_parsing_response_buffer()
|
||||
|
@ -406,7 +401,7 @@ class KlipperPlugin(
|
|||
# write buffer with // lines after a gcode response without //
|
||||
if self._parsing_response:
|
||||
self._parsing_response = False
|
||||
self.log_info(self._message)
|
||||
logger.log_info(self, self._message)
|
||||
self._message = ""
|
||||
|
||||
def get_api_commands(self):
|
||||
|
@ -414,7 +409,6 @@ class KlipperPlugin(
|
|||
listLogFiles=[],
|
||||
getStats=["logFile"],
|
||||
reloadConfig=[],
|
||||
reloadCfgBackup=[],
|
||||
checkConfig=["config"]
|
||||
)
|
||||
|
||||
|
@ -424,12 +418,12 @@ class KlipperPlugin(
|
|||
logpath = os.path.expanduser(
|
||||
self._settings.get(["configuration", "logpath"])
|
||||
)
|
||||
if self.file_exist(logpath):
|
||||
if util.file_exist(self, logpath):
|
||||
for f in glob.glob(self._settings.get(["configuration", "logpath"]) + "*"):
|
||||
filesize = os.path.getsize(f)
|
||||
filemdate = time.strftime("%d.%m.%Y %H:%M",time.localtime(os.path.getctime(f)))
|
||||
files.append(dict(
|
||||
name=os.path.basename(
|
||||
f) + " ({:.1f} KB)".format(filesize / 1000.0),
|
||||
name=os.path.basename(f) + " (" + filemdate + ")",
|
||||
file=f,
|
||||
size=filesize
|
||||
))
|
||||
|
@ -441,30 +435,151 @@ class KlipperPlugin(
|
|||
log_analyzer = KlipperLogAnalyzer.KlipperLogAnalyzer(
|
||||
data["logFile"])
|
||||
return flask.jsonify(log_analyzer.analyze())
|
||||
elif command == "reloadConfig":
|
||||
self.log_debug("reloadConfig")
|
||||
return self.reload_cfg()
|
||||
elif command == "reloadCfgBackup":
|
||||
self.log_debug("reloadCfgBackup")
|
||||
|
||||
def is_blueprint_protected(self):
|
||||
return False
|
||||
|
||||
def route_hook(self, server_routes, *args, **kwargs):
|
||||
from octoprint.server.util.tornado import LargeResponseHandler, path_validation_factory
|
||||
from octoprint.util import is_hidden_path
|
||||
configpath = os.path.expanduser(
|
||||
self._settings.get(["configuration", "configpath"])
|
||||
)
|
||||
return self.copy_cfg_from_backup(configpath)
|
||||
elif command == "checkConfig":
|
||||
if "config" in data:
|
||||
#self.write_cfg_backup(data["config"])
|
||||
if self.key_exist(data, "configuration", "parse_check"):
|
||||
check_parse = data["configuration"]["parse_check"]
|
||||
else:
|
||||
check_parse = self._settings.get(["configuration", "parse_check"])
|
||||
if check_parse and not self.validate_configfile(data["config"]):
|
||||
self.log_debug("validateConfig not ok")
|
||||
self._settings.set(["configuration", "old_config"], data["config"])
|
||||
return flask.jsonify(checkConfig="not OK")
|
||||
else:
|
||||
self.log_debug("validateConfig ok")
|
||||
self._settings.set(["configuration", "old_config"], "")
|
||||
return flask.jsonify(checkConfig="OK")
|
||||
bak_path = os.path.join(self.get_plugin_data_folder(), "configs", "")
|
||||
|
||||
return [
|
||||
(r"/download/(.*)", LargeResponseHandler, dict(path=configpath,
|
||||
as_attachment=True,
|
||||
path_validation=path_validation_factory(lambda path: not is_hidden_path(path),
|
||||
status_code=404))),
|
||||
(r"/download/backup(.*)", LargeResponseHandler, dict(path=bak_path,
|
||||
as_attachment=True,
|
||||
path_validation=path_validation_factory(lambda path: not is_hidden_path(path),
|
||||
status_code=404)))
|
||||
]
|
||||
|
||||
# API for Backups
|
||||
# Get Content of a Backupconfig
|
||||
@octoprint.plugin.BlueprintPlugin.route("/backup/<filename>", methods=["GET"])
|
||||
@restricted_access
|
||||
@Permissions.PLUGIN_KLIPPER_CONFIG.require(403)
|
||||
def get_backup(self, filename):
|
||||
data_folder = self.get_plugin_data_folder()
|
||||
full_path = os.path.realpath(os.path.join(data_folder, "configs", filename))
|
||||
response = cfgUtils.get_cfg(self, full_path)
|
||||
return flask.jsonify(response = response)
|
||||
|
||||
# Delete a Backupconfig
|
||||
@octoprint.plugin.BlueprintPlugin.route("/backup/<filename>", methods=["DELETE"])
|
||||
@restricted_access
|
||||
@Permissions.PLUGIN_KLIPPER_CONFIG.require(403)
|
||||
def delete_backup(self, filename):
|
||||
data_folder = self.get_plugin_data_folder()
|
||||
full_path = os.path.realpath(os.path.join(data_folder, "configs", filename))
|
||||
if (
|
||||
full_path.startswith(data_folder)
|
||||
and os.path.exists(full_path)
|
||||
and not is_hidden_path(full_path)
|
||||
):
|
||||
try:
|
||||
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):
|
||||
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_pythoncompat__ = ">=2.7,<4"
|
||||
__plugin_settings_overlay__ = {
|
||||
|
@ -690,12 +618,12 @@ __plugin_settings_overlay__ = {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
def __plugin_load__():
|
||||
global __plugin_implementation__
|
||||
global __plugin_hooks__
|
||||
__plugin_implementation__ = KlipperPlugin()
|
||||
__plugin_hooks__ = {
|
||||
"octoprint.server.http.routes": __plugin_implementation__.route_hook,
|
||||
"octoprint.access.permissions": __plugin_implementation__.get_additional_permissions,
|
||||
"octoprint.comm.protocol.gcode.received": __plugin_implementation__.on_parse_gcode,
|
||||
"octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information
|
||||
|
|
|
@ -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 optparse, datetime
|
||||
from .. import logger
|
||||
|
||||
class KlipperLogAnalyzer():
|
||||
MAXBANDWIDTH=25000.
|
||||
|
@ -81,6 +82,7 @@ class KlipperLogAnalyzer():
|
|||
out.append(keyparts)
|
||||
f.close()
|
||||
except IOError:
|
||||
logger.log_error(self, "Couldn't open log file: {}".format(logname))
|
||||
print("Couldn't open log file")
|
||||
return out
|
||||
|
||||
|
|
|
@ -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;
|
||||
});
|
|
@ -23,7 +23,8 @@ li#navbar_plugin_klipper {
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
.plugin-klipper-sidebar a:hover, .plugin-klipper-sidebar a:active {
|
||||
.plugin-klipper-sidebar a:hover,
|
||||
.plugin-klipper-sidebar a:active {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
@ -82,7 +83,7 @@ ul#klipper-settings {
|
|||
|
||||
#tab_plugin_klipper_main .row-fluid {
|
||||
display: flex;
|
||||
flex: row wrap;
|
||||
flex-flow: row wrap;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
|
@ -108,13 +109,57 @@ ul#klipper-settings {
|
|||
min-width: 100px;
|
||||
}
|
||||
|
||||
.klipper-row-fluid {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.klipper-column-fluid {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.klipper-fluid-item-1 {
|
||||
flex: 1 auto;
|
||||
}
|
||||
|
||||
.klipper-fluid-item-2 {
|
||||
flex: 2 auto;
|
||||
}
|
||||
|
||||
.klipper-fluid-item-3 {
|
||||
flex: 3 auto;
|
||||
}
|
||||
|
||||
.gap {
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
@media all and (max-width: 940px) {
|
||||
.klipper-row-fluid {
|
||||
/* On small screens, we are no longer using row direction but column */
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
#settings_plugin_klipper {
|
||||
height: 100%;
|
||||
height: -webkit-fill-available;
|
||||
}
|
||||
|
||||
/* UIcustomizer fix */
|
||||
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 */
|
||||
body.UICResponsiveMode #settings_dialog_content {
|
||||
height: calc(100% - 60px);
|
||||
margin-right: -18px;
|
||||
|
@ -143,6 +188,7 @@ div#settings_plugin_klipper div.tab-footer {
|
|||
|
||||
div#settings_plugin_klipper div.tab-content div#conf.tab-pane {
|
||||
height: 100%;
|
||||
min-height: 200px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
@ -153,40 +199,119 @@ div#settings_plugin_klipper div.tab-content div#conf.tab-pane div.control-group
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
div#settings_plugin_klipper div.tab-content div#conf.tab-pane div.control-group div.editor-controls{
|
||||
margin-bottom: 0px;
|
||||
height: 26px;
|
||||
.klipper-settings-tab {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div#settings_plugin_klipper div.tab-content div#conf.tab-pane div.control-group div.conf-editor {
|
||||
height: 95%;
|
||||
height: calc(100% - 28px);
|
||||
#settings_plugin_klipper .m-0 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#settings_plugin_klipper .scroll-y {
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
#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;
|
||||
flex: 1 1;
|
||||
overflow: auto;
|
||||
flex-grow : 1;
|
||||
}
|
||||
|
||||
div#settings_plugin_klipper div.tab-content div#conf.tab-pane div.control-group div.conf-editor div#plugin-klipper-config {
|
||||
div#klipper_editor div.conf-editor div#plugin-klipper-config {
|
||||
font-family: monospace;
|
||||
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 {
|
||||
.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%;
|
||||
display: inline-block;
|
||||
margin: 0px 2px 2px 2px;
|
||||
}
|
||||
} */
|
||||
|
||||
/*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,
|
||||
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,
|
||||
div#klipper_editor .inline {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
|
@ -210,11 +335,11 @@ div#settings_plugin_klipper.tab-pane.active form.form-horizontal div.tab-content
|
|||
}
|
||||
|
||||
#klipper_graph_dialog {
|
||||
width: 1050px;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
#klipper_graph_dialog .full-sized-box {
|
||||
width: 1000px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,10 +19,11 @@ $(function () {
|
|||
|
||||
self.header = OctoPrint.getRequestHeaders({
|
||||
"content-type": "application/json",
|
||||
"cache-control": "no-cache"
|
||||
"cache-control": "no-cache",
|
||||
});
|
||||
|
||||
self.apiUrl = OctoPrint.getSimpleApiUrl("klipper");
|
||||
self.Url = OctoPrint.getBlueprintUrl("klipper");
|
||||
|
||||
self.settings = parameters[0];
|
||||
self.loginState = parameters[1];
|
||||
|
@ -41,20 +42,17 @@ $(function () {
|
|||
title: title,
|
||||
text: message,
|
||||
type: popupType,
|
||||
hide: false
|
||||
hide: false,
|
||||
icon: true
|
||||
});
|
||||
};
|
||||
|
||||
self.onSettingsShown = function () {
|
||||
self.reloadConfig();
|
||||
}
|
||||
|
||||
self.showLevelingDialog = function () {
|
||||
var dialog = $("#klipper_leveling_dialog");
|
||||
dialog.modal({
|
||||
show: "true",
|
||||
backdrop: "static",
|
||||
keyboard: false
|
||||
keyboard: false,
|
||||
});
|
||||
self.levelingViewModel.initView();
|
||||
};
|
||||
|
@ -64,7 +62,7 @@ $(function () {
|
|||
dialog.modal({
|
||||
show: "true",
|
||||
backdrop: "static",
|
||||
keyboard: false
|
||||
keyboard: false,
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -72,7 +70,7 @@ $(function () {
|
|||
var dialog = $("#klipper_offset_dialog");
|
||||
dialog.modal({
|
||||
show: "true",
|
||||
backdrop: "static"
|
||||
backdrop: "static",
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -81,7 +79,7 @@ $(function () {
|
|||
dialog.modal({
|
||||
show: "true",
|
||||
minHeight: "500px",
|
||||
maxHeight: "600px"
|
||||
maxHeight: "600px",
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -102,7 +100,7 @@ $(function () {
|
|||
var dialog = $("#klipper_macro_dialog");
|
||||
dialog.modal({
|
||||
show: "true",
|
||||
backdrop: "static"
|
||||
backdrop: "static",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -142,7 +140,7 @@ $(function () {
|
|||
break;
|
||||
case "status":
|
||||
if (data.payload.length > 36) {
|
||||
var shortText = data.payload.substring(0, 31) + " [..]"
|
||||
var shortText = data.payload.substring(0, 31) + " [..]";
|
||||
self.shortStatus_navbar(shortText);
|
||||
} else {
|
||||
self.shortStatus_navbar(data.payload);
|
||||
|
@ -153,39 +151,26 @@ $(function () {
|
|||
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();
|
||||
var timestamp =
|
||||
today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
|
||||
}
|
||||
self.logMessages.push({
|
||||
time: timestamp,
|
||||
type: type,
|
||||
msg: message.replace(/\n/gi, "<br>")
|
||||
msg: message.replace(/\n/gi, "<br />"),
|
||||
});
|
||||
};
|
||||
|
||||
self.consoleMessage = function (type, message) {
|
||||
if (self.settings.settings.plugins.klipper.configuration.debug_logging() === true) {
|
||||
if (
|
||||
self.settings.settings.plugins.klipper.configuration.debug_logging() === true
|
||||
) {
|
||||
if (type == "info") {
|
||||
console.info("OctoKlipper : " + message);
|
||||
} else if (type == "debug") {
|
||||
|
@ -194,27 +179,9 @@ $(function () {
|
|||
console.error("OctoKlipper : " + message);
|
||||
}
|
||||
}
|
||||
return
|
||||
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();
|
||||
};
|
||||
|
@ -223,19 +190,37 @@ $(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);
|
||||
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
|
||||
);
|
||||
}
|
||||
return self.loginState.hasPermission(arg);
|
||||
};
|
||||
|
||||
// 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) {
|
||||
|
@ -253,12 +238,12 @@ $(function () {
|
|||
"connectionViewModel",
|
||||
"klipperLevelingViewModel",
|
||||
"klipperMacroDialogViewModel",
|
||||
"accessViewModel"
|
||||
"accessViewModel",
|
||||
],
|
||||
elements: [
|
||||
"#tab_plugin_klipper_main",
|
||||
"#sidebar_plugin_klipper",
|
||||
"#navbar_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"],
|
||||
});
|
||||
});
|
|
@ -14,82 +14,264 @@
|
|||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
$(function () {
|
||||
$('#klipper-settings a:first').tab('show');
|
||||
$("#klipper-settings a:first").tab("show");
|
||||
function KlipperSettingsViewModel(parameters) {
|
||||
var self = this;
|
||||
var obKlipperConfig = null;
|
||||
var editor = null;
|
||||
|
||||
self.settings = parameters[0];
|
||||
self.klipperViewModel = parameters[1];
|
||||
self.klipperEditorViewModel = parameters[2];
|
||||
self.klipperBackupViewModel = parameters[3];
|
||||
self.access = parameters[4];
|
||||
|
||||
self.header = OctoPrint.getRequestHeaders({
|
||||
"content-type": "application/json",
|
||||
"cache-control": "no-cache"
|
||||
"cache-control": "no-cache",
|
||||
});
|
||||
|
||||
self.apiUrl = OctoPrint.getSimpleApiUrl("klipper");
|
||||
self.markedForFileRemove = ko.observableArray([]);
|
||||
|
||||
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()})
|
||||
}
|
||||
// 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
|
||||
);
|
||||
|
||||
$.ajax(settings).done(function (response) {
|
||||
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: '',
|
||||
name: "Macro",
|
||||
macro: "",
|
||||
sidebar: true,
|
||||
tab: 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.moveItemUp(self.settings.settings.plugins.klipper.macros, macro);
|
||||
};
|
||||
|
||||
self.moveMacroDown = function (macro) {
|
||||
self.moveItemDown(self.settings.settings.plugins.klipper.macros, 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.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.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(self.settings.settings.plugins.klipper.probe.points, macro);
|
||||
};
|
||||
|
||||
self.moveItemDown = function (list, item) {
|
||||
var i = list().indexOf(item);
|
||||
|
@ -97,7 +279,7 @@ $(function() {
|
|||
var rawList = list();
|
||||
list.splice(i, 2, rawList[i + 1], rawList[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.moveItemUp = function (list, item) {
|
||||
var i = list().indexOf(item);
|
||||
|
@ -105,151 +287,19 @@ $(function() {
|
|||
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
|
||||
}
|
||||
}
|
||||
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"
|
||||
],
|
||||
elements: ["#settings_plugin_klipper"]
|
||||
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 class="modal-header">
|
||||
<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 class="modal-body">
|
||||
<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 class="modal-header">
|
||||
<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 class="modal-body">
|
||||
<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 class="modal-header">
|
||||
<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 class="modal-body">
|
||||
<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 class="modal-header">
|
||||
<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 class="modal-body">
|
||||
<div class="control-group">
|
||||
|
|
|
@ -11,39 +11,43 @@
|
|||
<div class="control-group">
|
||||
<label class="control-label">{{ _('Serial Port') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: settings.settings.plugins.klipper.connection.port">
|
||||
<input type="text" class="input-block-level" data-bind="value: settings.settings.plugins.klipper.connection.port" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">{{ _('Replace Connection Panel') }}</label>
|
||||
<div class="controls">
|
||||
<input class="controls-checkbox" title="{{ _('Replace Connection Panel') }}" type="checkbox" data-bind="checked: settings.settings.plugins.klipper.connection.replace_connection_panel">
|
||||
<input class="controls-checkbox" title="{{ _('Replace Connection Panel') }}" type="checkbox"
|
||||
data-bind="checked: settings.settings.plugins.klipper.connection.replace_connection_panel">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label">{{ _('Show Short Messages') }}</label>
|
||||
<div class="controls">
|
||||
<label class="checkbox" title="{{ _('on NavBar') }}"><input type="checkbox" data-bind="checked: settings.settings.plugins.klipper.configuration.shortStatus_navbar"> {{ _('on NavBar') }}</label>
|
||||
<label class="checkbox" title="{{ _('on SideBar') }}"><input type="checkbox" data-bind="checked: settings.settings.plugins.klipper.configuration.shortStatus_sidebar"> {{ _('on SideBar') }}</label>
|
||||
<label class="checkbox" title="{{ _('on NavBar') }}"><input type="checkbox"
|
||||
data-bind="checked: settings.settings.plugins.klipper.configuration.shortStatus_navbar" /> {{ _('on NavBar') }}</label>
|
||||
<label class="checkbox" title="{{ _('on SideBar') }}"><input type="checkbox"
|
||||
data-bind="checked: settings.settings.plugins.klipper.configuration.shortStatus_sidebar" /> {{ _('on SideBar') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">{{ _('Enable debug logging') }}</label>
|
||||
<div class="controls">
|
||||
<input class="controls-checkbox" title="{{ _('Enable debug logging') }}" type="checkbox" data-bind="checked: settings.settings.plugins.klipper.configuration.debug_logging">
|
||||
<input class="controls-checkbox" title="{{ _('Enable debug logging') }}" type="checkbox"
|
||||
data-bind="checked: settings.settings.plugins.klipper.configuration.debug_logging" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">{{ _('Klipper Config File') }}</label>
|
||||
<div 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 class="control-group">
|
||||
<label class="control-label">{{ _('Klipper Log File') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: settings.settings.plugins.klipper.configuration.logpath">
|
||||
<input type="text" class="input-block-level" data-bind="value: settings.settings.plugins.klipper.configuration.logpath" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
|
@ -55,11 +59,19 @@
|
|||
<option value="manually">Manually</option>
|
||||
</select>
|
||||
<span class="help-block">
|
||||
{{ _('The command that is executed when the Klipper configuration changed and needs to be reloaded.<br>
|
||||
{{ _('The command that is executed when the Klipper configuration changed and needs to be reloaded.<br />
|
||||
Set this to "Manually" if you don\'t want to immediately restart klipper.') }}
|
||||
</span>
|
||||
</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>
|
||||
<!-- Macros -->
|
||||
<div class="tab-pane" id="macros">
|
||||
|
@ -111,7 +123,7 @@
|
|||
</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>
|
||||
{{ _('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">
|
||||
|
@ -127,7 +139,8 @@
|
|||
<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>
|
||||
{{ _('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>
|
||||
|
@ -135,7 +148,7 @@
|
|||
<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">
|
||||
<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>
|
||||
|
@ -145,7 +158,7 @@
|
|||
<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">
|
||||
<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>
|
||||
|
@ -155,7 +168,7 @@
|
|||
<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">
|
||||
<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>
|
||||
|
@ -164,7 +177,7 @@
|
|||
<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">
|
||||
<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>
|
||||
|
@ -184,9 +197,9 @@
|
|||
<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"><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>
|
||||
|
@ -203,27 +216,95 @@
|
|||
</div>
|
||||
<!-- Klipper Conf -->
|
||||
<div class="tab-pane" id="conf">
|
||||
<div class="control-group">
|
||||
<script src="plugin/klipper/static/js/lib/ace/ace.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="plugin/klipper/static/js/lib/ace/theme-monokai.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="plugin/klipper/static/js/lib/ace/mode-klipper_config.js" type="text/javascript"></script>
|
||||
<div class="editor-controls">
|
||||
<button class="btn btn-small" data-bind="click: loadCfgBackup"
|
||||
title="{{ _('Reload last version') }}">
|
||||
<i class="fas fa-redo"></i> {{ _('Reload last version') }}
|
||||
<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: reloadFromFile'
|
||||
title="{{ _('Reload from file') }}">
|
||||
<i class="fas fa-upload"></i> {{ _('Reload from file') }}
|
||||
<button class="btn btn-small" data-bind="click: listCfgFiles" title="{{ _('Refresh file list') }}">
|
||||
<i class="icon-refresh"></i> {{ _('Refresh Files') }}
|
||||
</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 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>
|
||||
<div class="conf-editor">
|
||||
<input id="hdnLoadKlipperConfig" type="hidden" data-bind="value: configBound(settings.settings.plugins.klipper.config)" />
|
||||
<div id="plugin-klipper-config"></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>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<label for="connection_printers" data-bind="css: {disabled: !connectionState.isErrorOrClosed()}, enable: connectionState.isErrorOrClosed() && loginState.isUser()">{{ _('Printer Profile') }}</label>
|
||||
<select id="connection_printers" data-bind="options: connectionState.printerOptions, optionsText: 'name', optionsValue: 'id', value: connectionState.selectedPrinter, css: {disabled: !connectionState.isErrorOrClosed()}, enable: connectionState.isErrorOrClosed() && loginState.isUser()"></select>
|
||||
<button class="btn btn-block" data-bind="click: connectionState.connect, text: connectionState.buttonText(), enable: loginState.isUser()">{{ _('Connect') }}</button>
|
||||
<button class="btn btn-block" data-bind="visible: hasRight('CONFIG', 'Ko'), click: function() {openOctoKlipperSettings('klipper-config');}">{{ _('Open Klipper config') }}</button>
|
||||
<button class="btn btn-block" data-bind="visible: $root.loginState.hasPermissionKo($root.access.permissions.PLUGIN_KLIPPER_CONFIG), click: function() {openOctoKlipperSettings('klipper-config');}">{{ _('Open Klipper config') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ko if: settings.settings.plugins.klipper.configuration.shortStatus_sidebar -->
|
||||
|
@ -11,7 +11,7 @@
|
|||
<a title="{{ _('Go to OctoKlipper Tab') }}" data-bind="text: shortStatus_sidebar, click: navbarClicked"></a>
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
<div class="control-group" data-bind="visible: hasRight('MACRO', 'Ko')">
|
||||
<div class="control-group" data-bind="visible: $root.loginState.hasPermissionKo($root.access.permissions.PLUGIN_KLIPPER_MACRO)">
|
||||
<div class="controls">
|
||||
<label class="control-label small"><i class="icon-list-alt"></i> {{ _('Macros') }}</label>
|
||||
<div data-bind="foreach: settings.settings.plugins.klipper.macros">
|
||||
|
|
|
@ -8,11 +8,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="btn btn-mini pull-right clear-btn"
|
||||
data-bind="click: onClearLog"
|
||||
title="{{ _('Clear Log') }}"
|
||||
>
|
||||
<button class="btn btn-mini pull-right clear-btn" data-bind="click: onClearLog" title="{{ _('Clear Log') }}">
|
||||
<i class="fa fa-trash"></i> {{ _('Clear Log') }}
|
||||
</button>
|
||||
</div>
|
||||
|
@ -21,18 +17,13 @@
|
|||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="control-label"></label>
|
||||
<button
|
||||
class="btn btn-block btn-small"
|
||||
data-bind="click: onGetStatus, enable: isActive()"
|
||||
title="{{ _('Query Klipper for its current status') }}"
|
||||
>
|
||||
<button class="btn btn-block btn-small" data-bind="click: onGetStatus, enable: isActive()"
|
||||
title="{{ _('Query Klipper for its current status') }}">
|
||||
<i class="fa icon-black fa-info-circle"></i> {{ _("Get Status") }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-block btn-small"
|
||||
data-bind="visible: hasRight('CONFIG', 'Ko'), click: function() {openOctoKlipperSettings('klipper-config');}"
|
||||
title="{{ _('Open the Klipper configuration file') }}"
|
||||
>
|
||||
<button class="btn btn-block btn-small"
|
||||
data-bind="visible: $root.loginState.hasPermissionKo($root.access.permissions.PLUGIN_KLIPPER_CONFIG), click: function() {openOctoKlipperSettings('klipper-config');}"
|
||||
title="{{ _('Open the Klipper configuration file') }}">
|
||||
<i class="fa icon-black fa-file-code-o"></i>
|
||||
{{ _("Open Klipper config") }}
|
||||
</button>
|
||||
|
@ -40,70 +31,44 @@
|
|||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="control-label small"
|
||||
><i class="icon-refresh"></i> {{ _("Restart") }}</label
|
||||
>
|
||||
<button
|
||||
class="btn btn-block btn-small"
|
||||
data-bind="click: onRestartHost, enable: isActive()"
|
||||
title="{{ _('This will cause the host software to reload its config and perform an internal reset') }}"
|
||||
>
|
||||
<label class="control-label small"><i class="icon-refresh"></i> {{ _("Restart") }}</label>
|
||||
<button class="btn btn-block btn-small" data-bind="click: onRestartHost, enable: isActive()"
|
||||
title="{{ _('This will cause the host software to reload its config and perform an internal reset') }}">
|
||||
{{ _("Host") }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-block btn-small"
|
||||
data-bind="click: onRestartFirmware, enable: isActive()"
|
||||
title="{{ _('Similar to a host restart, but also clears any error state from the micro-controller') }}"
|
||||
>
|
||||
<button class="btn btn-block btn-small" data-bind="click: onRestartFirmware, enable: isActive()"
|
||||
title="{{ _('Similar to a host restart, but also clears any error state from the micro-controller') }}">
|
||||
{{ _("Firmware") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="control-label"
|
||||
><i class="icon-wrench"></i> {{ _("Tools") }}</label
|
||||
>
|
||||
<button
|
||||
class="btn btn-block btn-small"
|
||||
data-bind="click: showLevelingDialog, enable: isActive()"
|
||||
title="{{ _('Assists in manually leveling your printbed by moving the head to a configurable set of positions in sequence.') }}"
|
||||
>
|
||||
<label class="control-label"><i class="icon-wrench"></i> {{ _("Tools") }}</label>
|
||||
<button class="btn btn-block btn-small" data-bind="click: showLevelingDialog, enable: isActive()"
|
||||
title="{{ _('Assists in manually leveling your printbed by moving the head to a configurable set of positions in sequence.') }}">
|
||||
{{ _("Assisted Bed Leveling") }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-block btn-small"
|
||||
data-bind="click: showPidTuningDialog, enable: isActive()"
|
||||
title="{{ _('Determines optimal PID parameters by heat cycling the hotend/bed.') }}"
|
||||
>
|
||||
<button class="btn btn-block btn-small" data-bind="click: showPidTuningDialog, enable: isActive()"
|
||||
title="{{ _('Determines optimal PID parameters by heat cycling the hotend/bed.') }}">
|
||||
{{ _("PID Tuning") }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-block btn-small"
|
||||
data-bind="click: showOffsetDialog, enable: isActive()"
|
||||
title="{{ _('Sets a offset for subsequent GCODE coordinates.') }}"
|
||||
>
|
||||
<button class="btn btn-block btn-small" data-bind="click: showOffsetDialog, enable: isActive()"
|
||||
title="{{ _('Sets a offset for subsequent GCODE coordinates.') }}">
|
||||
{{ _("Coordinate Offset") }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-block btn-small"
|
||||
data-bind="click: showGraphDialog"
|
||||
title="{{ _('Assists in debugging performance issues by analyzing the Klipper log files.') }}"
|
||||
>
|
||||
<button class="btn btn-block btn-small" data-bind="click: showGraphDialog"
|
||||
title="{{ _('Assists in debugging performance issues by analyzing the Klipper log files.') }}">
|
||||
{{ _("Analyze Klipper Log") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="controls" data-bind="visible: hasRight('MACRO', 'Ko')">
|
||||
<label class="control-label"
|
||||
><i class="icon-list-alt"></i> {{ _("Macros") }}</label
|
||||
>
|
||||
<div class="controls" data-bind="visible: $root.loginState.hasPermissionKo($root.access.permissions.PLUGIN_KLIPPER_MACRO)">
|
||||
<label class="control-label"><i class="icon-list-alt"></i> {{ _("Macros") }}</label>
|
||||
<div data-bind="foreach: settings.settings.plugins.klipper.macros">
|
||||
<!-- ko if: tab -->
|
||||
<button
|
||||
class="btn btn-block btn-small"
|
||||
data-bind="text: name, click: $parent.executeMacro, enable: $parent.isActive()"
|
||||
></button>
|
||||
<button class="btn btn-block btn-small" data-bind="text: name, click: $parent.executeMacro, enable: $parent.isActive()">
|
||||
</button>
|
||||
<!-- /ko -->
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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