Skip to content

Commit 0fce710

Browse files
committed
Add klipper service restart button
1 parent 1827962 commit 0fce710

File tree

3 files changed

+127
-20
lines changed

3 files changed

+127
-20
lines changed

octoprint_moonraker_connector/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from urllib.parse import urljoin
44

55
import requests
6+
import subprocess
67
from flask import jsonify
78
from flask_babel import gettext
89

@@ -93,6 +94,21 @@ def on_api_get(self, request):
9394
)
9495
return jsonify(response.model_dump(by_alias=True))
9596

97+
def get_api_commands(self):
98+
return dict(
99+
restart_klipper_service=[]
100+
)
101+
102+
def on_api_command(self, command, data):
103+
if command == "restart_klipper_service":
104+
if hasattr(self, "_client") and self._client.is_connected():
105+
self._client.restart_klipper_service(self)
106+
107+
return jsonify(success=True, message="Restart sent")
108+
else:
109+
self._logger.warning("Moonraker client not connected.")
110+
return jsonify(success=False, message="Moonraker client not connected.")
111+
96112
def is_api_protected(self):
97113
return True
98114

octoprint_moonraker_connector/client.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,17 @@ def on_printer_objects_subscribed(future: Future) -> None:
621621

622622
##~~ Method calls & callbacks
623623

624+
def restart_klipper_service(self) -> Future:
625+
def on_result(future: Future) -> None:
626+
try:
627+
results = future.result()
628+
except Exception as e:
629+
self._logger.exception(f"Error while restarting klipper service: {e}")
630+
631+
self.call_method("machine.service.restart", {"service": "klipper"}).add_done_callback(
632+
on_result
633+
)
634+
624635
def query_printer_objects(self, objs: list[str] = None) -> Future:
625636
if objs is None:
626637
objs = self._subbed_objs

octoprint_moonraker_connector/static/js/moonraker_connector.js

Lines changed: 100 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,56 +7,133 @@ $(function () {
77
self.settingsViewModel = parameters[2];
88
self.printerState = parameters[3];
99

10-
self.btnRestartClick = function() {
10+
self.isMoonrakerReady = ko.pureComputed(function() {
11+
// I'd like to check for moonraker connection
12+
// and see if klipper is connected, as some commands
13+
// can be sent even if !isOperational() and that's be
14+
// a better check, that or restart klipper maybe belongs
15+
// in the Connection pane?
16+
return self.printerState.isOperational();
17+
});
18+
19+
self.btnReloadConfigClick = function() {
1120
OctoPrint.control.sendGcode('RESTART');
1221
}
1322

1423
self.btnFirmwareRestartClick = function() {
1524
OctoPrint.control.sendGcode('FIRMWARE_RESTART');
1625
}
1726

27+
self.btnKlipperRestartClick = function() {
28+
showConfirmationDialog({
29+
message: gettext("<strong>This will restart the Klipper service.</strong></p><p>This might disrupt any ongoing operations related to Klipper."),
30+
onproceed: function() {
31+
OctoPrint.simpleApiCommand(
32+
"moonraker_connector",
33+
"restart_klipper_service"
34+
).done(function(response) {
35+
if (response.success) {
36+
new PNotify({
37+
title: gettext("Success"),
38+
text: gettext("Restart command sent"),
39+
type: "success"
40+
});
41+
} else {
42+
new PNotify({
43+
title: gettext("Failed"),
44+
text: gettext("Failed to send restart command: ") + response.message,
45+
type: "error"
46+
});
47+
}
48+
}).fail(function(jqXHR, textStatus, errorThrown) {
49+
new PNotify({
50+
title: gettext("Error"),
51+
text: gettext("Failed to execute command: ") + jqXHR.responseJSON.message,
52+
type: "error"
53+
});
54+
});
55+
}
56+
});
57+
}
58+
1859
self.initializeButton = function() {
1960
var buttonContainer = $('#job_print')[0].parentElement;
61+
62+
var parentContainer = document.createElement("div");
63+
parentContainer.id = "moonraker_connector_wrapper";
64+
2065
var container = document.createElement("div");
2166
container.classList.add("row-fluid", "print-control");
2267
container.style.marginTop = "10px";
23-
container.setAttribute("data-bind", "visible: $root.loginState.hasPermissionKo($root.access.permissions.PRINT)");
68+
container.setAttribute("data-bind", "visible: isOperational() && loginState.isUser()");
2469

25-
var btnRestart = document.createElement("button");
26-
btnRestart.id = "job_restart";
27-
btnRestart.title = "Reload configuration file and performs an internal reset of the host software. It does not clear the error state from the micro-controller.";
28-
btnRestart.classList.add("btn");
29-
btnRestart.classList.add("span6");
30-
btnRestart.addEventListener("click", self.btnRestartClick);
70+
var btnReloadConfig = document.createElement("button");
71+
btnReloadConfig.id = "job_reload_config";
72+
btnReloadConfig.title = gettext("Reload configuration file and performs an internal reset of the host software. It does not clear the error state from the micro-controller.");
73+
btnReloadConfig.classList.add("btn");
74+
btnReloadConfig.classList.add("span6");
75+
btnReloadConfig.setAttribute("data-bind", "enable: isOperational() && loginState.isUser()");
76+
btnReloadConfig.addEventListener("click", self.btnReloadConfigClick);
3177

32-
var btnRestartIcon = document.createElement("i");
33-
btnRestartIcon.classList.add("fa", "fa-redo");
34-
btnRestart.appendChild(btnRestartIcon);
78+
var btnReloadConfigIcon = document.createElement("i");
79+
btnReloadConfigIcon.classList.add("fas", "fa-sync-alt");
80+
btnReloadConfigIcon.style.marginRight = "5px";
81+
btnReloadConfig.appendChild(btnReloadConfigIcon);
3582

36-
var btnRestartText = document.createElement("span");
37-
btnRestartText.textContent = " Restart";
38-
btnRestart.appendChild(btnRestartText);
83+
var btnReloadConfigText = document.createElement("span");
84+
btnReloadConfigText.textContent = gettext("Reload Config");
85+
btnReloadConfig.appendChild(btnReloadConfigText);
3986

40-
container.appendChild(btnRestart);
87+
container.appendChild(btnReloadConfig);
4188

4289
var btnFirmwareRestart = document.createElement("button");
4390
btnFirmwareRestart.id = "job_firmware_restart";
44-
btnFirmwareRestart.title = "Reload configuration file and performs an internal reset of the host software, but it also clears any error states from the micro-controller.";
91+
btnFirmwareRestart.title = gettext("Reload configuration file and performs an internal reset of the host software, but it also clears any error states from the micro-controller.");
4592
btnFirmwareRestart.classList.add("btn");
4693
btnFirmwareRestart.classList.add("span6");
94+
btnFirmwareRestart.setAttribute("data-bind", "enable: isOperational() && loginState.isUser()");
4795
btnFirmwareRestart.addEventListener("click", self.btnFirmwareRestartClick);
4896

4997
var btnFirmwareRestartIcon = document.createElement("i");
50-
btnFirmwareRestartIcon.classList.add("fa", "fa-sync");
98+
btnFirmwareRestartIcon.classList.add("fas", "fa-microchip");
99+
btnFirmwareRestartIcon.style.marginRight = "5px";
51100
btnFirmwareRestart.appendChild(btnFirmwareRestartIcon);
52101

53102
var btnFirmwareRestartText = document.createElement("span");
54-
btnFirmwareRestartText.textContent = " Firmware Restart";
103+
btnFirmwareRestartText.textContent = gettext("Firmware Restart");
55104
btnFirmwareRestart.appendChild(btnFirmwareRestartText);
56105

57106
container.appendChild(btnFirmwareRestart);
58107

59-
buttonContainer.after(container);
108+
parentContainer.append(container);
109+
110+
var container2 = document.createElement("div");
111+
container2.classList.add("row-fluid", "print-control");
112+
container2.style.marginTop = "10px";
113+
container2.setAttribute("data-bind", "visible: isOperational() && loginState.isUser()");
114+
115+
var btnKlipperRestart = document.createElement("button");
116+
btnKlipperRestart.id = "job_klipper_restart";
117+
btnKlipperRestart.title = gettext("Restart klipper process.");
118+
btnKlipperRestart.classList.add("btn");
119+
btnKlipperRestart.classList.add("span12");
120+
btnKlipperRestart.setAttribute("data-bind", "enable: isOperational() && loginState.isUser()");
121+
btnKlipperRestart.addEventListener("click", self.btnKlipperRestartClick);
122+
123+
var btnKlipperRestartIcon = document.createElement("i");
124+
btnKlipperRestartIcon.classList.add("fas", "fa-power-off");
125+
btnKlipperRestartIcon.style.marginRight = "5px";
126+
btnKlipperRestart.appendChild(btnKlipperRestartIcon);
127+
128+
var btnKlipperRestartText = document.createElement("span");
129+
btnKlipperRestartText.textContent = gettext("Restart Klipper Service");
130+
btnKlipperRestart.appendChild(btnKlipperRestartText);
131+
132+
container2.appendChild(btnKlipperRestart);
133+
134+
parentContainer.append(container2);
135+
136+
buttonContainer.after(parentContainer);
60137
};
61138

62139
self.webcams = ko.observableArray([]);
@@ -199,6 +276,9 @@ $(function () {
199276
"settingsViewModel",
200277
"printerStateViewModel"
201278
],
202-
elements: ["#webcam_plugin_moonraker_connector"]
279+
elements: [
280+
"#moonraker_connector_wrapper",
281+
"#webcam_plugin_moonraker_connector"
282+
]
203283
});
204284
});

0 commit comments

Comments
 (0)