mirror of
https://github.com/dw-0/kiauh.git
synced 2025-12-26 09:13:35 +05:00
Compare commits
338 Commits
v6.0.0-alp
...
69dbf68760
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69dbf68760 | ||
|
|
ffc262a89b | ||
|
|
cf35da5ff4 | ||
|
|
f34392bfd5 | ||
|
|
ea991644cd | ||
|
|
fe0bfc5376 | ||
|
|
fdfdf18dd2 | ||
|
|
bbf64eec9c | ||
|
|
a0076698d5 | ||
|
|
547194e950 | ||
|
|
14973c4d98 | ||
|
|
f49f7b2fee | ||
|
|
5d678c4ff2 | ||
|
|
184c5ac3ff | ||
|
|
27b7651e11 | ||
|
|
660481af5a | ||
|
|
9b2960594e | ||
|
|
db3a9ca622 | ||
|
|
3f428df9d6 | ||
|
|
8aec1e437a | ||
|
|
231e9d134a | ||
|
|
ddab9e7c96 | ||
|
|
f60d0b923c | ||
|
|
3e6d3d9015 | ||
|
|
69a0fe2dfb | ||
|
|
13611ccd52 | ||
|
|
ea4409ee54 | ||
|
|
4f39edd06c | ||
|
|
77128ac7f8 | ||
|
|
16d4fdbcfe | ||
|
|
9e66c8093b | ||
|
|
88f784348b | ||
|
|
1fc50848b0 | ||
|
|
acde067e68 | ||
|
|
96daf966ee | ||
|
|
0d7a471a03 | ||
|
|
f00d41b1bf | ||
|
|
f578247b74 | ||
|
|
a7c67721b6 | ||
|
|
32742943a0 | ||
|
|
871bedb76b | ||
|
|
fee2dd0bda | ||
|
|
e5bcab5d85 | ||
|
|
31ea6c2e5a | ||
|
|
1384f7328a | ||
|
|
6bf55b5f69 | ||
|
|
398705b176 | ||
|
|
ed2e318d0e | ||
|
|
75ac8a22d5 | ||
|
|
005e2d3339 | ||
|
|
bdb2c85e9b | ||
|
|
7e251eb31e | ||
|
|
64ea337e7e | ||
|
|
1cd9414cae | ||
|
|
2391f491bb | ||
|
|
92ed67ddd2 | ||
|
|
0cb1e35b06 | ||
|
|
7632c3c980 | ||
|
|
c1f600f539 | ||
|
|
01deab7c64 | ||
|
|
e530c75307 | ||
|
|
9655f9ba5c | ||
|
|
94e95671ca | ||
|
|
9ec12ba0b8 | ||
|
|
372712ba32 | ||
|
|
6b7057882b | ||
|
|
481394abf9 | ||
|
|
7c9dcea359 | ||
|
|
956666605c | ||
|
|
8a620cdbd4 | ||
|
|
6636365cb7 | ||
|
|
61618d064d | ||
|
|
59e619ea0f | ||
|
|
2ad11d68de | ||
|
|
7444ae8cea | ||
|
|
103a7b61b3 | ||
|
|
dbe15e3a32 | ||
|
|
e421a12daf | ||
|
|
3734ef0568 | ||
|
|
08c10fdded | ||
|
|
cfc45a9746 | ||
|
|
205c84b3c3 | ||
|
|
e63eb47ee9 | ||
|
|
af57b9670d | ||
|
|
b758b3887b | ||
|
|
5eff560627 | ||
|
|
93ba579232 | ||
|
|
5c090e88c3 | ||
|
|
c2dfabb326 | ||
|
|
08640e5b17 | ||
|
|
802eaccf57 | ||
|
|
c6cc3fc0f6 | ||
|
|
7b9f9b1a67 | ||
|
|
fbab9a769a | ||
|
|
60f8aef69b | ||
|
|
f73ee6e6a0 | ||
|
|
d414be609a | ||
|
|
df45c5955e | ||
|
|
70ad635e3d | ||
|
|
6570400f9e | ||
|
|
aafcba9f40 | ||
|
|
91162a7070 | ||
|
|
74c70189af | ||
|
|
017f1d4597 | ||
|
|
0dfe7672b8 | ||
|
|
b3df3e7b5c | ||
|
|
01afe1fe77 | ||
|
|
ac0478b062 | ||
|
|
6eb06772b4 | ||
|
|
d6317ad439 | ||
|
|
e28869be1a | ||
|
|
51993e367d | ||
|
|
a03e943ebf | ||
|
|
fc8fedc9f6 | ||
|
|
7f79f68209 | ||
|
|
a44508ead5 | ||
|
|
9342c94096 | ||
|
|
ea78ba25e6 | ||
|
|
63cae491f3 | ||
|
|
05b5664062 | ||
|
|
a4b149c11a | ||
|
|
3b2bc05746 | ||
|
|
72663ef71c | ||
|
|
8730fc395e | ||
|
|
3885405366 | ||
|
|
e986dfbf4c | ||
|
|
79b4f3eefe | ||
|
|
bf0385e3c9 | ||
|
|
750bf1caaf | ||
|
|
27455dfc64 | ||
|
|
940f7cfbf1 | ||
|
|
e5d0e97b82 | ||
|
|
799892500a | ||
|
|
5f1e42b88b | ||
|
|
09dc961646 | ||
|
|
40e382c9a1 | ||
|
|
9864dd0c7f | ||
|
|
d84adee7f9 | ||
|
|
c17c3e9bd4 | ||
|
|
074344cf7c | ||
|
|
42667ad792 | ||
|
|
9804411d74 | ||
|
|
067a102b6b | ||
|
|
4a5d1a971a | ||
|
|
6407664e3e | ||
|
|
65617ca971 | ||
|
|
e05a42630e | ||
|
|
be228210bd | ||
|
|
b70ac0dfd7 | ||
|
|
af48738221 | ||
|
|
9d2cb72aa4 | ||
|
|
8c3397ea78 | ||
|
|
7d3d46ac07 | ||
|
|
3da7aedd7f | ||
|
|
8d343853f1 | ||
|
|
1f2d724189 | ||
|
|
1a29324e6a | ||
|
|
5225e70e83 | ||
|
|
51f0713c5a | ||
|
|
d420daca26 | ||
|
|
cb62909f41 | ||
|
|
02eebff571 | ||
|
|
36b295bd1b | ||
|
|
372c9c0b7d | ||
|
|
c67ea2245d | ||
|
|
fda99bb70a | ||
|
|
2c1c94c904 | ||
|
|
b020f10967 | ||
|
|
aa1b435da5 | ||
|
|
449317b118 | ||
|
|
336414c43c | ||
|
|
cd63034b74 | ||
|
|
8de7ab7e11 | ||
|
|
c2b0ca5b19 | ||
|
|
ecb673a088 | ||
|
|
da4c5fe109 | ||
|
|
bb769fdf6d | ||
|
|
409aa3da25 | ||
|
|
0b41d63496 | ||
|
|
44301c0c87 | ||
|
|
ace47e2873 | ||
|
|
06801a47eb | ||
|
|
1484ebf445 | ||
|
|
4547ac571a | ||
|
|
b2dd5d8ed7 | ||
|
|
e50ce1fc71 | ||
|
|
417180f724 | ||
|
|
39f0bd8b0a | ||
|
|
dc87d30770 | ||
|
|
aaf5216275 | ||
|
|
ebdfadac07 | ||
|
|
cac73cc58d | ||
|
|
78dbf31576 | ||
|
|
fef8b58510 | ||
|
|
72e3a56e4f | ||
|
|
e64aa94df4 | ||
|
|
58719a4ca0 | ||
|
|
59a83aee12 | ||
|
|
7104eb078f | ||
|
|
341ecb325c | ||
|
|
e3a6d8a0ab | ||
|
|
0183988d5d | ||
|
|
03c3ed20f3 | ||
|
|
5c1c98b6b8 | ||
|
|
ef13c130e0 | ||
|
|
2acd74cbd9 | ||
|
|
00665109c2 | ||
|
|
a5dce136f3 | ||
|
|
4ffa057931 | ||
|
|
ed35dc9e03 | ||
|
|
7ec055f562 | ||
|
|
9eb0531cdf | ||
|
|
84cda99af8 | ||
|
|
5f823c2d3a | ||
|
|
758a783ede | ||
|
|
682baaa105 | ||
|
|
601ccb2191 | ||
|
|
c0caab13b3 | ||
|
|
7c754de08e | ||
|
|
9dc556e7e4 | ||
|
|
655b781aef | ||
|
|
14aafd558a | ||
|
|
bd1aa1ae2b | ||
|
|
8df75dc8d0 | ||
|
|
5c37b68463 | ||
|
|
1620efe56c | ||
|
|
7fd91e6cef | ||
|
|
750cb7b307 | ||
|
|
384503c4f5 | ||
|
|
2a4fcf3a3a | ||
|
|
573dc7c3c9 | ||
|
|
05b4ef2d18 | ||
|
|
863c62511c | ||
|
|
be5f345a7c | ||
|
|
948927cfd3 | ||
|
|
34ebe5d15e | ||
|
|
3bef6ecb85 | ||
|
|
5ace920d3e | ||
|
|
2f34253bad | ||
|
|
0447bc4405 | ||
|
|
7cb2231584 | ||
|
|
5a3d21c40b | ||
|
|
ad56b51e70 | ||
|
|
c6999f1990 | ||
|
|
bc30cf418b | ||
|
|
ee81ee4c0c | ||
|
|
35911604af | ||
|
|
77f1089041 | ||
|
|
7820155094 | ||
|
|
c28d5c28b9 | ||
|
|
cda6d31a7c | ||
|
|
9a657daffd | ||
|
|
85b4b68f16 | ||
|
|
dfbce3b489 | ||
|
|
f3b0e45e39 | ||
|
|
83e5d9c0d5 | ||
|
|
8f44187568 | ||
|
|
625a808484 | ||
|
|
ad0dbf63b8 | ||
|
|
9dedf38079 | ||
|
|
1b4c76d080 | ||
|
|
d20d82aeac | ||
|
|
16a28ffda0 | ||
|
|
a9367cc064 | ||
|
|
b165d88855 | ||
|
|
6c59d58193 | ||
|
|
b4f5c3c1ac | ||
|
|
b69ecbc9b5 | ||
|
|
fc9fa39eee | ||
|
|
142b4498a3 | ||
|
|
012b6c4bb7 | ||
|
|
8aeb01aca0 | ||
|
|
da533fdd67 | ||
|
|
8cb0754296 | ||
|
|
7a6590e86a | ||
|
|
2f0feb317e | ||
|
|
b9479db766 | ||
|
|
14132fc34b | ||
|
|
3d5e83d5ab | ||
|
|
edd5f5c6fd | ||
|
|
8ff0b9d81d | ||
|
|
22e8e314db | ||
|
|
12bd8eb799 | ||
|
|
4915896099 | ||
|
|
cd38970bbd | ||
|
|
b8640f45a6 | ||
|
|
5fb4444f03 | ||
|
|
926ba1acb4 | ||
|
|
c2e7ee98df | ||
|
|
3865266da1 | ||
|
|
b83f642a13 | ||
|
|
30b4414469 | ||
|
|
1178d3c730 | ||
|
|
59d8867c8c | ||
|
|
80a953a587 | ||
|
|
a80f0bb0e8 | ||
|
|
78cefddb2e | ||
|
|
b20613819e | ||
|
|
1836beab42 | ||
|
|
545397f598 | ||
|
|
f709cf84e7 | ||
|
|
f62c10dc8b | ||
|
|
e121ba8a62 | ||
|
|
9a1a66aa64 | ||
|
|
420b193f4b | ||
|
|
de20f0c412 | ||
|
|
57f34b07c6 | ||
|
|
e35e44a76a | ||
|
|
bfb10c742b | ||
|
|
458c89a78a | ||
|
|
6128e35d45 | ||
|
|
279d000bb0 | ||
|
|
a4a3d5eecb | ||
|
|
1392ca9f82 | ||
|
|
47121f6875 | ||
|
|
d0d2404132 | ||
|
|
6ed5395f17 | ||
|
|
be805c169b | ||
|
|
eaf12db27e | ||
|
|
fe8767113b | ||
|
|
2148d95cf4 | ||
|
|
682be48e8d | ||
|
|
68369753fd | ||
|
|
44ed3b6ddf | ||
|
|
e12e578098 | ||
|
|
515a42f098 | ||
|
|
f9ecad0eca | ||
|
|
fb09acf660 | ||
|
|
093da73dd1 | ||
|
|
c9e8c4807e | ||
|
|
09e874214b | ||
|
|
623bd7553b | ||
|
|
1e0c74b549 | ||
|
|
358c666da9 | ||
|
|
84a530be7d | ||
|
|
bfff3019cb | ||
|
|
2a100c2934 | ||
|
|
ce0daa52ae |
@@ -2,54 +2,13 @@
|
|||||||
|
|
||||||
This document covers possible important changes to KIAUH.
|
This document covers possible important changes to KIAUH.
|
||||||
|
|
||||||
### 2024-08-31 (v6.0.0-alpha.1)
|
|
||||||
Long time no see, but here we are again!
|
|
||||||
A lot has happened in the background, but now it is time to take it out into the wild.
|
|
||||||
|
|
||||||
#### KIAUH has now reached version 6! Well, at least in an alpha state...
|
|
||||||
|
|
||||||
The project has seen a complete rewrite of the script from scratch in Python.
|
|
||||||
It requires Python 3.8 or newer to run. Because this update is still in an alpha state, bugs may or will occur.
|
|
||||||
During startup, you will be asked if you want to start the new version 6 or the old version 5.
|
|
||||||
As long as version 6 is in a pre-release state, version 5 will still be available. If there are any critical issues
|
|
||||||
with the new version that were overlooked, you can always switch back to the old version.
|
|
||||||
|
|
||||||
In case you selected not to get asked about which version to start (option 3 or 4 in the startup dialog) and you want to
|
|
||||||
revert that decision, you will find a line called `version_to_launch=` within the `.kiauh.ini` file in your home directory.
|
|
||||||
Just delete that line, save the file and restart KIAUH. KIAUH will then ask you again which version you want to start.
|
|
||||||
|
|
||||||
Here is a list of the most important changes to KIAUH in regard to version 6:
|
|
||||||
- The majority of features available in KIAUH v5 are still available; they just got migrated from Bash to Python.
|
|
||||||
- It is now possible to add new/remove instances to/from existing multi-instance installations of Klipper and Moonraker
|
|
||||||
- KIAUH now has an Extension-System. This allows contributors to add new installers to KIAUH without having to modify the main script.
|
|
||||||
- You will now find some of the features that were previously available in the Installer-Menu in the Extensions-Menu.
|
|
||||||
- The current extensions are:
|
|
||||||
- G-Code Shell Command (previously found in the Advanced-Menu)
|
|
||||||
- Mainsail Theme Installer (previously found in the Advanced-Menu)
|
|
||||||
- Klipper-Backup (new in v6!)
|
|
||||||
- Moonraker Telegram Bot (previously found in the Installer-Menu)
|
|
||||||
- PrettyGCode for Klipper (previously found in the Installer-Menu)
|
|
||||||
- Obico for Klipper (previously found in the Installer-Menu)
|
|
||||||
- The following additional extensions are planned, but not yet available:
|
|
||||||
- Spoolman (available in v5 in the Installer-Menu)
|
|
||||||
- OctoApp (available in v5 in the Installer-Menu)
|
|
||||||
- KIAUH has its own config file now
|
|
||||||
- The file has some default values for the currently supported options
|
|
||||||
- There might be more options in the future
|
|
||||||
- It is located in KIAUH's root directory and is called `default.kiauh.cfg`
|
|
||||||
- DO NOT EDIT the default file directly, instead make a copy of it and call it `kiauh.cfg`
|
|
||||||
- Settings changed via the Advanced-Menu will be written to the `kiauh.cfg`
|
|
||||||
- Support for OctoPrint was removed
|
|
||||||
|
|
||||||
Feel free to give version 6 a try and report any bugs or issues you encounter! Every feedback is appreciated.
|
|
||||||
|
|
||||||
### 2023-06-17
|
### 2023-06-17
|
||||||
KIAUH has now added support for installing Mobileraker's companion!
|
KIAUH has now added support for installing Mobileraker's companion!
|
||||||
Mobileraker is a free and Open Source Android and iOS App for Klipper, utilizing the Moonraker API, allowing you
|
Mobileraker is a free and Open Source Android and iOS App for Klipper, utilizing the Moonraker API, allowing you
|
||||||
to control your printer. Thank you to [Clon1998](https://github.com/Clon1998) for adding this feature!
|
to control your printer. Thank you to [Clon1998](https://github.com/Clon1998) for adding this feature!
|
||||||
|
|
||||||
### 2023-02-03
|
### 2023-02-03
|
||||||
The installer for MJPG-Streamer got replaced by crowsnest. It is an improved webcam service, utilizing ustreamer.
|
The installer for MJPG-Streamer got replaced by crowsnest. It is an improved webcam service, utilizing ustreamer.
|
||||||
Please have a look here for additional info about crowsnest and how to configure it: https://github.com/mainsail-crew/crowsnest \
|
Please have a look here for additional info about crowsnest and how to configure it: https://github.com/mainsail-crew/crowsnest \
|
||||||
It's unsure if the previous MJPG-Streamer installer will be updated and make its way back into KIAUH.
|
It's unsure if the previous MJPG-Streamer installer will be updated and make its way back into KIAUH.
|
||||||
A big thanks to [KwadFan](https://github.com/KwadFan) for writing the crowsnest implementation.
|
A big thanks to [KwadFan](https://github.com/KwadFan) for writing the crowsnest implementation.
|
||||||
@@ -156,7 +115,7 @@ membership for example caused issues when installing mjpg-streamer while not usi
|
|||||||
Other issues could occur when trying to flash an MCU on Debian or Ubuntu distributions where a user might not be part
|
Other issues could occur when trying to flash an MCU on Debian or Ubuntu distributions where a user might not be part
|
||||||
of the dialout group by default. A check for the tty group is also done. The tty group is needed for setting
|
of the dialout group by default. A check for the tty group is also done. The tty group is needed for setting
|
||||||
up a linux MCU (currently not yet supported by KIAUH).
|
up a linux MCU (currently not yet supported by KIAUH).
|
||||||
* There is an issue when trying to install Mainsail or Fluidd on Ubuntu 21.10. Permissions on that distro seem to have seen a rework
|
* There is an issue when trying to install Mainsail or Fluidd on Ubuntu 21.10. Permissions on that distro seem to have seen a rework
|
||||||
in comparison to 20.04 and users will be greeted with an "Error 403 - Permission denied" message after installing one of Klippers webinterfaces.
|
in comparison to 20.04 and users will be greeted with an "Error 403 - Permission denied" message after installing one of Klippers webinterfaces.
|
||||||
I still have to figure out a viable solution for that.
|
I still have to figure out a viable solution for that.
|
||||||
|
|
||||||
|
|||||||
2
kiauh.sh
2
kiauh.sh
@@ -164,6 +164,7 @@ function main() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
check_if_ratos
|
check_if_ratos
|
||||||
check_euid
|
check_euid
|
||||||
init_logfile
|
init_logfile
|
||||||
@@ -172,3 +173,4 @@ kiauh_update_dialog
|
|||||||
read_kiauh_ini
|
read_kiauh_ini
|
||||||
init_ini
|
init_ini
|
||||||
main
|
main
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ from core.logger import Logger
|
|||||||
from utils.fs_utils import run_remove_routines
|
from utils.fs_utils import run_remove_routines
|
||||||
from utils.input_utils import get_selection_input
|
from utils.input_utils import get_selection_input
|
||||||
from utils.instance_utils import get_instances
|
from utils.instance_utils import get_instances
|
||||||
from utils.sys_utils import unit_file_exists
|
|
||||||
|
|
||||||
|
|
||||||
def run_klipper_removal(
|
def run_klipper_removal(
|
||||||
@@ -36,7 +35,7 @@ def run_klipper_removal(
|
|||||||
else:
|
else:
|
||||||
Logger.print_info("No Klipper Services installed! Skipped ...")
|
Logger.print_info("No Klipper Services installed! Skipped ...")
|
||||||
|
|
||||||
if (remove_dir or remove_env) and unit_file_exists("klipper", suffix="service"):
|
if (remove_dir or remove_env) and klipper_instances:
|
||||||
Logger.print_info("There are still other Klipper services installed:")
|
Logger.print_info("There are still other Klipper services installed:")
|
||||||
Logger.print_info(f"● '{KLIPPER_DIR}' was not removed.", prefix=False)
|
Logger.print_info(f"● '{KLIPPER_DIR}' was not removed.", prefix=False)
|
||||||
Logger.print_info(f"● '{KLIPPER_ENV_DIR}' was not removed.", prefix=False)
|
Logger.print_info(f"● '{KLIPPER_ENV_DIR}' was not removed.", prefix=False)
|
||||||
|
|||||||
@@ -234,6 +234,8 @@ def display_moonraker_info(moonraker_list: List[Moonraker]) -> bool:
|
|||||||
"The following Klipper instances will be installed:",
|
"The following Klipper instances will be installed:",
|
||||||
*[f"● klipper-{m.suffix}" for m in moonraker_list],
|
*[f"● klipper-{m.suffix}" for m in moonraker_list],
|
||||||
],
|
],
|
||||||
|
padding_top=0,
|
||||||
|
padding_bottom=0,
|
||||||
)
|
)
|
||||||
_input: bool = get_confirm("Proceed with installation?")
|
_input: bool = get_confirm("Proceed with installation?")
|
||||||
return _input
|
return _input
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ def check_user_groups() -> None:
|
|||||||
"INFO:",
|
"INFO:",
|
||||||
"Relog required for group assignments to take effect!",
|
"Relog required for group assignments to take effect!",
|
||||||
],
|
],
|
||||||
|
padding_bottom=0,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not get_confirm(f"Add user '{CURRENT_USER}' to group(s) now?"):
|
if not get_confirm(f"Add user '{CURRENT_USER}' to group(s) now?"):
|
||||||
|
|||||||
@@ -35,11 +35,11 @@ class KlipperRemoveMenu(BaseMenu):
|
|||||||
|
|
||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
self.options = {
|
self.options = {
|
||||||
"a": Option(method=self.toggle_all),
|
"a": Option(method=self.toggle_all, menu=False),
|
||||||
"1": Option(method=self.toggle_remove_klipper_service),
|
"1": Option(method=self.toggle_remove_klipper_service, menu=False),
|
||||||
"2": Option(method=self.toggle_remove_klipper_dir),
|
"2": Option(method=self.toggle_remove_klipper_dir, menu=False),
|
||||||
"3": Option(method=self.toggle_remove_klipper_env),
|
"3": Option(method=self.toggle_remove_klipper_env, menu=False),
|
||||||
"c": Option(method=self.run_removal_process),
|
"c": Option(method=self.run_removal_process, menu=False),
|
||||||
}
|
}
|
||||||
|
|
||||||
def print_menu(self) -> None:
|
def print_menu(self) -> None:
|
||||||
|
|||||||
@@ -47,10 +47,10 @@ class KlipperBuildFirmwareMenu(BaseMenu):
|
|||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
if len(self.missing_deps) == 0:
|
if len(self.missing_deps) == 0:
|
||||||
self.input_label_txt = "Press ENTER to continue"
|
self.input_label_txt = "Press ENTER to continue"
|
||||||
self.default_option = Option(method=self.start_build_process)
|
self.default_option = Option(method=self.start_build_process, menu=False)
|
||||||
else:
|
else:
|
||||||
self.input_label_txt = "Press ENTER to install dependencies"
|
self.input_label_txt = "Press ENTER to install dependencies"
|
||||||
self.default_option = Option(method=self.install_missing_deps)
|
self.default_option = Option(method=self.install_missing_deps, menu=False)
|
||||||
|
|
||||||
def print_menu(self) -> None:
|
def print_menu(self) -> None:
|
||||||
header = " [ Build Firmware Menu ] "
|
header = " [ Build Firmware Menu ] "
|
||||||
@@ -81,7 +81,6 @@ class KlipperBuildFirmwareMenu(BaseMenu):
|
|||||||
line = f"{COLOR_RED}Dependencies are missing!{RESET_FORMAT}"
|
line = f"{COLOR_RED}Dependencies are missing!{RESET_FORMAT}"
|
||||||
|
|
||||||
menu += f"║ {line:<62} ║\n"
|
menu += f"║ {line:<62} ║\n"
|
||||||
menu += "╟───────────────────────────────────────────────────────╢\n"
|
|
||||||
|
|
||||||
print(menu, end="")
|
print(menu, end="")
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class KlipperNoFirmwareErrorMenu(BaseMenu):
|
|||||||
self.previous_menu = previous_menu
|
self.previous_menu = previous_menu
|
||||||
|
|
||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
self.default_option = Option(method=self.go_back)
|
self.default_option = Option(self.go_back, False)
|
||||||
|
|
||||||
def print_menu(self) -> None:
|
def print_menu(self) -> None:
|
||||||
header = "!!! NO FIRMWARE FILE FOUND !!!"
|
header = "!!! NO FIRMWARE FILE FOUND !!!"
|
||||||
@@ -79,7 +79,7 @@ class KlipperNoBoardTypesErrorMenu(BaseMenu):
|
|||||||
self.previous_menu = previous_menu
|
self.previous_menu = previous_menu
|
||||||
|
|
||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
self.default_option = Option(method=self.go_back)
|
self.default_option = Option(self.go_back, False)
|
||||||
|
|
||||||
def print_menu(self) -> None:
|
def print_menu(self) -> None:
|
||||||
header = "!!! ERROR GETTING BOARD LIST !!!"
|
header = "!!! ERROR GETTING BOARD LIST !!!"
|
||||||
|
|||||||
@@ -61,8 +61,8 @@ class KlipperFlashMethodMenu(BaseMenu):
|
|||||||
|
|
||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
self.options = {
|
self.options = {
|
||||||
"1": Option(self.select_regular),
|
"1": Option(self.select_regular, menu=False),
|
||||||
"2": Option(self.select_sdcard),
|
"2": Option(self.select_sdcard, menu=False),
|
||||||
}
|
}
|
||||||
|
|
||||||
def print_menu(self) -> None:
|
def print_menu(self) -> None:
|
||||||
@@ -123,10 +123,10 @@ class KlipperFlashCommandMenu(BaseMenu):
|
|||||||
|
|
||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
self.options = {
|
self.options = {
|
||||||
"1": Option(self.select_flash),
|
"1": Option(self.select_flash, menu=False),
|
||||||
"2": Option(self.select_serialflash),
|
"2": Option(self.select_serialflash, menu=False),
|
||||||
}
|
}
|
||||||
self.default_option = Option(self.select_flash)
|
self.default_option = Option(self.select_flash, menu=False)
|
||||||
|
|
||||||
def print_menu(self) -> None:
|
def print_menu(self) -> None:
|
||||||
menu = textwrap.dedent(
|
menu = textwrap.dedent(
|
||||||
@@ -174,9 +174,9 @@ class KlipperSelectMcuConnectionMenu(BaseMenu):
|
|||||||
|
|
||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
self.options = {
|
self.options = {
|
||||||
"1": Option(method=self.select_usb),
|
"1": Option(method=self.select_usb, menu=False),
|
||||||
"2": Option(method=self.select_dfu),
|
"2": Option(method=self.select_dfu, menu=False),
|
||||||
"3": Option(method=self.select_usb_dfu),
|
"3": Option(method=self.select_usb_dfu, menu=False),
|
||||||
}
|
}
|
||||||
|
|
||||||
def print_menu(self) -> None:
|
def print_menu(self) -> None:
|
||||||
@@ -249,7 +249,7 @@ class KlipperSelectMcuIdMenu(BaseMenu):
|
|||||||
self.flash_options = FlashOptions()
|
self.flash_options = FlashOptions()
|
||||||
self.mcu_list = self.flash_options.mcu_list
|
self.mcu_list = self.flash_options.mcu_list
|
||||||
self.input_label_txt = "Select MCU to flash"
|
self.input_label_txt = "Select MCU to flash"
|
||||||
self.footer_type = FooterType.BACK
|
self.footer_type = FooterType.BACK_HELP
|
||||||
|
|
||||||
def set_previous_menu(self, previous_menu: Type[BaseMenu] | None) -> None:
|
def set_previous_menu(self, previous_menu: Type[BaseMenu] | None) -> None:
|
||||||
self.previous_menu = (
|
self.previous_menu = (
|
||||||
@@ -260,12 +260,13 @@ class KlipperSelectMcuIdMenu(BaseMenu):
|
|||||||
|
|
||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
self.options = {
|
self.options = {
|
||||||
f"{i}": Option(self.flash_mcu, f"{i}") for i in range(len(self.mcu_list))
|
f"{i}": Option(self.flash_mcu, False, f"{i}")
|
||||||
|
for i in range(len(self.mcu_list))
|
||||||
}
|
}
|
||||||
|
|
||||||
def print_menu(self) -> None:
|
def print_menu(self) -> None:
|
||||||
header = "!!! ATTENTION !!!"
|
header = "!!! ATTENTION !!!"
|
||||||
header2 = f"[{COLOR_CYAN}List of detected MCUs{RESET_FORMAT}]"
|
header2 = f"[{COLOR_CYAN}List of available MCUs{RESET_FORMAT}]"
|
||||||
color = COLOR_RED
|
color = COLOR_RED
|
||||||
count = 62 - len(color) - len(RESET_FORMAT)
|
count = 62 - len(color) - len(RESET_FORMAT)
|
||||||
menu = textwrap.dedent(
|
menu = textwrap.dedent(
|
||||||
@@ -277,21 +278,15 @@ class KlipperSelectMcuIdMenu(BaseMenu):
|
|||||||
║ ONLY flash a firmware created for the respective MCU! ║
|
║ ONLY flash a firmware created for the respective MCU! ║
|
||||||
║ ║
|
║ ║
|
||||||
╟{header2:─^64}╢
|
╟{header2:─^64}╢
|
||||||
║ ║
|
|
||||||
"""
|
"""
|
||||||
)[1:]
|
)[1:]
|
||||||
|
|
||||||
for i, mcu in enumerate(self.mcu_list):
|
for i, mcu in enumerate(self.mcu_list):
|
||||||
mcu = mcu.split("/")[-1]
|
mcu = mcu.split("/")[-1]
|
||||||
menu += f"║ {i}) {COLOR_CYAN}{mcu:<51}{RESET_FORMAT}║\n"
|
menu += f" ● MCU #{i}: {COLOR_CYAN}{mcu}{RESET_FORMAT}\n"
|
||||||
|
menu += "╟───────────────────────────┬───────────────────────────╢"
|
||||||
|
|
||||||
menu += textwrap.dedent(
|
print(menu, end="\n")
|
||||||
"""
|
|
||||||
║ ║
|
|
||||||
╟───────────────────────────────────────────────────────╢
|
|
||||||
"""
|
|
||||||
)[1:]
|
|
||||||
print(menu, end="")
|
|
||||||
|
|
||||||
def flash_mcu(self, **kwargs):
|
def flash_mcu(self, **kwargs):
|
||||||
try:
|
try:
|
||||||
@@ -328,7 +323,7 @@ class KlipperSelectSDFlashBoardMenu(BaseMenu):
|
|||||||
|
|
||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
self.options = {
|
self.options = {
|
||||||
f"{i}": Option(self.board_select, f"{i}")
|
f"{i}": Option(self.board_select, False, f"{i}")
|
||||||
for i in range(len(self.available_boards))
|
for i in range(len(self.available_boards))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,8 +344,8 @@ class KlipperSelectSDFlashBoardMenu(BaseMenu):
|
|||||||
|
|
||||||
for i, board in enumerate(self.available_boards):
|
for i, board in enumerate(self.available_boards):
|
||||||
line = f" {i}) {board}"
|
line = f" {i}) {board}"
|
||||||
menu += f"║{line:<55}║\n"
|
menu += f"|{line:<55}|\n"
|
||||||
menu += "╟───────────────────────────────────────────────────────╢"
|
|
||||||
print(menu, end="")
|
print(menu, end="")
|
||||||
|
|
||||||
def board_select(self, **kwargs):
|
def board_select(self, **kwargs):
|
||||||
@@ -398,11 +393,11 @@ class KlipperFlashOverviewMenu(BaseMenu):
|
|||||||
|
|
||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
self.options = {
|
self.options = {
|
||||||
"y": Option(self.execute_flash),
|
"Y": Option(self.execute_flash, menu=False),
|
||||||
"n": Option(self.abort_process),
|
"N": Option(self.abort_process, menu=False),
|
||||||
}
|
}
|
||||||
|
|
||||||
self.default_option = Option(self.execute_flash)
|
self.default_option = Option(self.execute_flash, menu=False)
|
||||||
|
|
||||||
def print_menu(self) -> None:
|
def print_menu(self) -> None:
|
||||||
header = "!!! ATTENTION !!!"
|
header = "!!! ATTENTION !!!"
|
||||||
@@ -412,7 +407,7 @@ class KlipperFlashOverviewMenu(BaseMenu):
|
|||||||
method = self.flash_options.flash_method.value
|
method = self.flash_options.flash_method.value
|
||||||
command = self.flash_options.flash_command.value
|
command = self.flash_options.flash_command.value
|
||||||
conn_type = self.flash_options.connection_type.value
|
conn_type = self.flash_options.connection_type.value
|
||||||
mcu = self.flash_options.selected_mcu.split("/")[-1]
|
mcu = self.flash_options.selected_mcu
|
||||||
board = self.flash_options.selected_board
|
board = self.flash_options.selected_board
|
||||||
baudrate = self.flash_options.selected_baudrate
|
baudrate = self.flash_options.selected_baudrate
|
||||||
subheader = f"[{COLOR_CYAN}Overview{RESET_FORMAT}]"
|
subheader = f"[{COLOR_CYAN}Overview{RESET_FORMAT}]"
|
||||||
@@ -426,37 +421,26 @@ class KlipperFlashOverviewMenu(BaseMenu):
|
|||||||
║ sure everything is correct, start the process. If any ║
|
║ sure everything is correct, start the process. If any ║
|
||||||
║ parameter needs to be changed, you can go back (B) ║
|
║ parameter needs to be changed, you can go back (B) ║
|
||||||
║ step by step or abort and start from the beginning. ║
|
║ step by step or abort and start from the beginning. ║
|
||||||
║{subheader:─^64}║
|
║{subheader:-^64}║
|
||||||
║ ║
|
|
||||||
"""
|
"""
|
||||||
)[1:]
|
)[1:]
|
||||||
|
|
||||||
menu += textwrap.dedent(
|
menu += f" ● MCU: {COLOR_CYAN}{mcu}{RESET_FORMAT}\n"
|
||||||
f"""
|
menu += f" ● Connection: {COLOR_CYAN}{conn_type}{RESET_FORMAT}\n"
|
||||||
║ MCU: {COLOR_CYAN}{mcu:<48}{RESET_FORMAT} ║
|
menu += f" ● Flash method: {COLOR_CYAN}{method}{RESET_FORMAT}\n"
|
||||||
║ Connection: {COLOR_CYAN}{conn_type:<41}{RESET_FORMAT} ║
|
menu += f" ● Flash command: {COLOR_CYAN}{command}{RESET_FORMAT}\n"
|
||||||
║ Flash method: {COLOR_CYAN}{method:<39}{RESET_FORMAT} ║
|
|
||||||
║ Flash command: {COLOR_CYAN}{command:<38}{RESET_FORMAT} ║
|
|
||||||
"""
|
|
||||||
)[1:]
|
|
||||||
|
|
||||||
if self.flash_options.flash_method is FlashMethod.SD_CARD:
|
if self.flash_options.flash_method is FlashMethod.SD_CARD:
|
||||||
menu += textwrap.dedent(
|
menu += f" ● Board type: {COLOR_CYAN}{board}{RESET_FORMAT}\n"
|
||||||
f"""
|
menu += f" ● Baudrate: {COLOR_CYAN}{baudrate}{RESET_FORMAT}\n"
|
||||||
║ Board type: {COLOR_CYAN}{board:<41}{RESET_FORMAT} ║
|
|
||||||
║ Baudrate: {COLOR_CYAN}{baudrate:<43}{RESET_FORMAT} ║
|
|
||||||
"""
|
|
||||||
)[1:]
|
|
||||||
|
|
||||||
menu += textwrap.dedent(
|
menu += textwrap.dedent(
|
||||||
"""
|
"""
|
||||||
║ ║
|
|
||||||
╟───────────────────────────────────────────────────────╢
|
╟───────────────────────────────────────────────────────╢
|
||||||
║ Y) Start flash process ║
|
║ Y) Start flash process ║
|
||||||
║ N) Abort - Return to Advanced Menu ║
|
║ N) Abort - Return to Advanced Menu ║
|
||||||
╟───────────────────────────────────────────────────────╢
|
|
||||||
"""
|
"""
|
||||||
)[1:]
|
)
|
||||||
print(menu, end="")
|
print(menu, end="")
|
||||||
|
|
||||||
def execute_flash(self, **kwargs):
|
def execute_flash(self, **kwargs):
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ def install_klipperscreen() -> None:
|
|||||||
"KlipperScreens update manager configuration for Moonraker "
|
"KlipperScreens update manager configuration for Moonraker "
|
||||||
"will not be added to any moonraker.conf.",
|
"will not be added to any moonraker.conf.",
|
||||||
],
|
],
|
||||||
|
padding_top=0,
|
||||||
|
padding_bottom=0,
|
||||||
)
|
)
|
||||||
if not get_confirm(
|
if not get_confirm(
|
||||||
"Continue KlipperScreen installation?",
|
"Continue KlipperScreen installation?",
|
||||||
@@ -103,8 +105,8 @@ def patch_klipperscreen_update_manager(instances: List[Moonraker]) -> None:
|
|||||||
options=[
|
options=[
|
||||||
("type", "git_repo"),
|
("type", "git_repo"),
|
||||||
("path", KLIPPERSCREEN_DIR.as_posix()),
|
("path", KLIPPERSCREEN_DIR.as_posix()),
|
||||||
("origin", KLIPPERSCREEN_REPO),
|
("orgin", KLIPPERSCREEN_REPO),
|
||||||
("managed_services", "KlipperScreen"),
|
("manages_servcies", "KlipperScreen"),
|
||||||
("env", f"{KLIPPERSCREEN_ENV_DIR}/bin/python"),
|
("env", f"{KLIPPERSCREEN_ENV_DIR}/bin/python"),
|
||||||
("requirements", KLIPPERSCREEN_REQ_FILE.as_posix()),
|
("requirements", KLIPPERSCREEN_REQ_FILE.as_posix()),
|
||||||
("install_script", KLIPPERSCREEN_INSTALL_SCRIPT.as_posix()),
|
("install_script", KLIPPERSCREEN_INSTALL_SCRIPT.as_posix()),
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class LogUploadMenu(BaseMenu):
|
|||||||
|
|
||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
self.options = {
|
self.options = {
|
||||||
f"{index}": Option(self.upload, opt_index=f"{index}")
|
f"{index}": Option(self.upload, False, opt_index=f"{index}")
|
||||||
for index in range(len(self.logfile_list))
|
for index in range(len(self.logfile_list))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,12 +35,12 @@ class MoonrakerRemoveMenu(BaseMenu):
|
|||||||
|
|
||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
self.options = {
|
self.options = {
|
||||||
"a": Option(method=self.toggle_all),
|
"a": Option(method=self.toggle_all, menu=False),
|
||||||
"1": Option(method=self.toggle_remove_moonraker_service),
|
"1": Option(method=self.toggle_remove_moonraker_service, menu=False),
|
||||||
"2": Option(method=self.toggle_remove_moonraker_dir),
|
"2": Option(method=self.toggle_remove_moonraker_dir, menu=False),
|
||||||
"3": Option(method=self.toggle_remove_moonraker_env),
|
"3": Option(method=self.toggle_remove_moonraker_env, menu=False),
|
||||||
"4": Option(method=self.toggle_remove_moonraker_polkit),
|
"4": Option(method=self.toggle_remove_moonraker_polkit, menu=False),
|
||||||
"c": Option(method=self.run_removal_process),
|
"c": Option(method=self.run_removal_process, menu=False),
|
||||||
}
|
}
|
||||||
|
|
||||||
def print_menu(self) -> None:
|
def print_menu(self) -> None:
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ from core.logger import Logger
|
|||||||
from utils.fs_utils import run_remove_routines
|
from utils.fs_utils import run_remove_routines
|
||||||
from utils.input_utils import get_selection_input
|
from utils.input_utils import get_selection_input
|
||||||
from utils.instance_utils import get_instances
|
from utils.instance_utils import get_instances
|
||||||
from utils.sys_utils import unit_file_exists
|
|
||||||
|
|
||||||
|
|
||||||
def run_moonraker_removal(
|
def run_moonraker_removal(
|
||||||
@@ -38,8 +37,7 @@ def run_moonraker_removal(
|
|||||||
else:
|
else:
|
||||||
Logger.print_info("No Moonraker Services installed! Skipped ...")
|
Logger.print_info("No Moonraker Services installed! Skipped ...")
|
||||||
|
|
||||||
delete_remaining: bool = remove_polkit or remove_dir or remove_env
|
if (remove_polkit or remove_dir or remove_env) and instances:
|
||||||
if delete_remaining and unit_file_exists("moonraker", suffix="service"):
|
|
||||||
Logger.print_info("There are still other Moonraker services installed")
|
Logger.print_info("There are still other Moonraker services installed")
|
||||||
Logger.print_info(
|
Logger.print_info(
|
||||||
"● Moonraker PolicyKit rules were not removed.", prefix=False
|
"● Moonraker PolicyKit rules were not removed.", prefix=False
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ def install_octoeverywhere() -> None:
|
|||||||
"It is safe to run the installer again to link your "
|
"It is safe to run the installer again to link your "
|
||||||
"printer or repair any issues.",
|
"printer or repair any issues.",
|
||||||
],
|
],
|
||||||
|
padding_top=0,
|
||||||
|
padding_bottom=0,
|
||||||
)
|
)
|
||||||
if not get_confirm("Re-run OctoEverywhere installation?"):
|
if not get_confirm("Re-run OctoEverywhere installation?"):
|
||||||
Logger.print_info("Exiting OctoEverywhere for Klipper installation ...")
|
Logger.print_info("Exiting OctoEverywhere for Klipper installation ...")
|
||||||
@@ -83,6 +85,8 @@ def install_octoeverywhere() -> None:
|
|||||||
"\n\n",
|
"\n\n",
|
||||||
"The setup will apply the same names to OctoEverywhere!",
|
"The setup will apply the same names to OctoEverywhere!",
|
||||||
],
|
],
|
||||||
|
padding_top=0,
|
||||||
|
padding_bottom=0,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not get_confirm(
|
if not get_confirm(
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ def print_moonraker_not_found_dialog() -> None:
|
|||||||
"another machine in your network. Otherwise Mainsail will NOT work "
|
"another machine in your network. Otherwise Mainsail will NOT work "
|
||||||
"correctly.",
|
"correctly.",
|
||||||
],
|
],
|
||||||
|
padding_top=0,
|
||||||
|
padding_bottom=0,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -34,6 +36,8 @@ def print_client_already_installed_dialog(name: str) -> None:
|
|||||||
f"{name} seems to be already installed!",
|
f"{name} seems to be already installed!",
|
||||||
f"If you continue, your current {name} installation will be overwritten.",
|
f"If you continue, your current {name} installation will be overwritten.",
|
||||||
],
|
],
|
||||||
|
padding_top=0,
|
||||||
|
padding_bottom=0,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -53,6 +57,8 @@ def print_client_port_select_dialog(
|
|||||||
"The following ports were found to be in use already:",
|
"The following ports were found to be in use already:",
|
||||||
*[f"● {port}" for port in ports_in_use],
|
*[f"● {port}" for port in ports_in_use],
|
||||||
],
|
],
|
||||||
|
padding_top=0,
|
||||||
|
padding_bottom=0,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -71,6 +77,8 @@ def print_install_client_config_dialog(client: BaseWebClient) -> None:
|
|||||||
"If you already use these macros skip this step. Otherwise you should "
|
"If you already use these macros skip this step. Otherwise you should "
|
||||||
"consider to answer with 'Y' to download the recommended macros.",
|
"consider to answer with 'Y' to download the recommended macros.",
|
||||||
],
|
],
|
||||||
|
padding_top=0,
|
||||||
|
padding_bottom=0,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ from core.types import ComponentStatus
|
|||||||
from utils.common import get_install_status
|
from utils.common import get_install_status
|
||||||
from utils.fs_utils import create_symlink, remove_file
|
from utils.fs_utils import create_symlink, remove_file
|
||||||
from utils.git_utils import (
|
from utils.git_utils import (
|
||||||
get_latest_remote_tag,
|
get_latest_tag,
|
||||||
get_latest_unstable_tag,
|
get_latest_unstable_tag,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ def get_local_client_version(client: BaseWebClient) -> str | None:
|
|||||||
|
|
||||||
def get_remote_client_version(client: BaseWebClient) -> str | None:
|
def get_remote_client_version(client: BaseWebClient) -> str | None:
|
||||||
try:
|
try:
|
||||||
if (tag := get_latest_remote_tag(client.repo_path)) != "":
|
if (tag := get_latest_tag(client.repo_path)) != "":
|
||||||
return str(tag)
|
return str(tag)
|
||||||
return None
|
return None
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
@@ -38,11 +38,11 @@ class ClientRemoveMenu(BaseMenu):
|
|||||||
|
|
||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
self.options = {
|
self.options = {
|
||||||
"a": Option(method=self.toggle_all),
|
"a": Option(method=self.toggle_all, menu=False),
|
||||||
"1": Option(method=self.toggle_rm_client),
|
"1": Option(method=self.toggle_rm_client, menu=False),
|
||||||
"2": Option(method=self.toggle_rm_client_config),
|
"2": Option(method=self.toggle_rm_client_config, menu=False),
|
||||||
"3": Option(method=self.toggle_backup_config_json),
|
"3": Option(method=self.toggle_backup_config_json, menu=False),
|
||||||
"c": Option(method=self.run_removal_process),
|
"c": Option(method=self.run_removal_process, menu=False),
|
||||||
}
|
}
|
||||||
|
|
||||||
def print_menu(self) -> None:
|
def print_menu(self) -> None:
|
||||||
|
|||||||
@@ -17,10 +17,6 @@ from core.logger import Logger
|
|||||||
from utils.common import get_current_date
|
from utils.common import get_current_date
|
||||||
|
|
||||||
|
|
||||||
class BackupManagerException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
# noinspection PyUnusedLocal
|
||||||
# noinspection PyMethodMayBeStatic
|
# noinspection PyMethodMayBeStatic
|
||||||
class BackupManager:
|
class BackupManager:
|
||||||
@@ -69,7 +65,7 @@ class BackupManager:
|
|||||||
|
|
||||||
def backup_directory(
|
def backup_directory(
|
||||||
self, name: str, source: Path, target: Path | None = None
|
self, name: str, source: Path, target: Path | None = None
|
||||||
) -> Path | None:
|
) -> None:
|
||||||
Logger.print_status(f"Creating backup of {name} in {target} ...")
|
Logger.print_status(f"Creating backup of {name} in {target} ...")
|
||||||
|
|
||||||
if source is None or not Path(source).exists():
|
if source is None or not Path(source).exists():
|
||||||
@@ -80,15 +76,15 @@ class BackupManager:
|
|||||||
try:
|
try:
|
||||||
date = get_current_date().get("date")
|
date = get_current_date().get("date")
|
||||||
time = get_current_date().get("time")
|
time = get_current_date().get("time")
|
||||||
backup_target = target.joinpath(f"{name.lower()}-{date}-{time}")
|
shutil.copytree(
|
||||||
shutil.copytree(source, backup_target, ignore=self.ignore_folders_func)
|
source,
|
||||||
|
target.joinpath(f"{name.lower()}-{date}-{time}"),
|
||||||
|
ignore=self.ignore_folders_func,
|
||||||
|
)
|
||||||
Logger.print_ok("Backup successful!")
|
Logger.print_ok("Backup successful!")
|
||||||
|
|
||||||
return backup_target
|
|
||||||
|
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
Logger.print_error(f"Unable to backup directory '{source}':\n{e}")
|
Logger.print_error(f"Unable to backup directory '{source}':\n{e}")
|
||||||
raise BackupManagerException(f"Unable to backup directory '{source}':\n{e}")
|
return
|
||||||
|
|
||||||
def ignore_folders_func(self, dirpath, filenames) -> List[str]:
|
def ignore_folders_func(self, dirpath, filenames) -> List[str]:
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ class BaseInstance:
|
|||||||
self.log_dir,
|
self.log_dir,
|
||||||
self.gcodes_dir,
|
self.gcodes_dir,
|
||||||
self.comms_dir,
|
self.comms_dir,
|
||||||
self.sysd_dir,
|
|
||||||
]
|
]
|
||||||
|
|
||||||
def _set_is_legacy_instance(self) -> bool:
|
def _set_is_legacy_instance(self) -> bool:
|
||||||
|
|||||||
@@ -92,8 +92,8 @@ class Logger:
|
|||||||
center_content: bool = False,
|
center_content: bool = False,
|
||||||
custom_title: str | None = None,
|
custom_title: str | None = None,
|
||||||
custom_color: DialogCustomColor | None = None,
|
custom_color: DialogCustomColor | None = None,
|
||||||
margin_top: int = 0,
|
padding_top: int = 1,
|
||||||
margin_bottom: int = 0,
|
padding_bottom: int = 1,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Prints a dialog with the given title and content.
|
Prints a dialog with the given title and content.
|
||||||
@@ -106,8 +106,8 @@ class Logger:
|
|||||||
:param center_content: Whether to center the content or not.
|
:param center_content: Whether to center the content or not.
|
||||||
:param custom_title: A custom title for the dialog.
|
:param custom_title: A custom title for the dialog.
|
||||||
:param custom_color: A custom color for the dialog.
|
:param custom_color: A custom color for the dialog.
|
||||||
:param margin_top: The number of empty lines to print before the dialog.
|
:param padding_top: The number of empty lines to print before the dialog.
|
||||||
:param margin_bottom: The number of empty lines to print after the dialog.
|
:param padding_bottom: The number of empty lines to print after the dialog.
|
||||||
"""
|
"""
|
||||||
dialog_color = Logger._get_dialog_color(title, custom_color)
|
dialog_color = Logger._get_dialog_color(title, custom_color)
|
||||||
dialog_title = Logger._get_dialog_title(title, custom_title)
|
dialog_title = Logger._get_dialog_title(title, custom_title)
|
||||||
@@ -116,12 +116,12 @@ class Logger:
|
|||||||
top = Logger._format_top_border(dialog_color)
|
top = Logger._format_top_border(dialog_color)
|
||||||
bottom = Logger._format_bottom_border()
|
bottom = Logger._format_bottom_border()
|
||||||
|
|
||||||
print("\n" * margin_top)
|
print("\n" * padding_top)
|
||||||
print(
|
print(
|
||||||
f"{top}{dialog_title_formatted}{dialog_content}{bottom}",
|
f"{top}{dialog_title_formatted}{dialog_content}{bottom}",
|
||||||
end="",
|
end="",
|
||||||
)
|
)
|
||||||
print("\n" * margin_bottom)
|
print("\n" * padding_bottom)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_dialog_title(
|
def _get_dialog_title(
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, Callable, Type
|
from typing import Any, Callable
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -18,14 +18,13 @@ class Option:
|
|||||||
"""
|
"""
|
||||||
Represents a menu option.
|
Represents a menu option.
|
||||||
:param method: Method that will be used to call the menu option
|
:param method: Method that will be used to call the menu option
|
||||||
|
:param menu: Flag for singaling that another menu will be opened
|
||||||
:param opt_index: Can be used to pass the user input to the menu option
|
:param opt_index: Can be used to pass the user input to the menu option
|
||||||
:param opt_data: Can be used to pass any additional data to the menu option
|
:param opt_data: Can be used to pass any additional data to the menu option
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __repr__(self):
|
method: Callable | None = None
|
||||||
return f"Option(method={self.method.__name__}, opt_index={self.opt_index}, opt_data={self.opt_data})"
|
menu: bool = False
|
||||||
|
|
||||||
method: Type[Callable]
|
|
||||||
opt_index: str = ""
|
opt_index: str = ""
|
||||||
opt_data: Any = None
|
opt_data: Any = None
|
||||||
|
|
||||||
|
|||||||
@@ -43,13 +43,13 @@ class AdvancedMenu(BaseMenu):
|
|||||||
|
|
||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
self.options = {
|
self.options = {
|
||||||
"1": Option(method=self.build),
|
"1": Option(method=self.build, menu=True),
|
||||||
"2": Option(method=self.flash),
|
"2": Option(method=self.flash, menu=False),
|
||||||
"3": Option(method=self.build_flash),
|
"3": Option(method=self.build_flash, menu=False),
|
||||||
"4": Option(method=self.get_id),
|
"4": Option(method=self.get_id, menu=False),
|
||||||
"5": Option(method=self.klipper_rollback),
|
"5": Option(method=self.klipper_rollback, menu=True),
|
||||||
"6": Option(method=self.moonraker_rollback),
|
"6": Option(method=self.moonraker_rollback, menu=True),
|
||||||
"7": Option(method=self.change_hostname),
|
"7": Option(method=self.change_hostname, menu=True),
|
||||||
}
|
}
|
||||||
|
|
||||||
def print_menu(self) -> None:
|
def print_menu(self) -> None:
|
||||||
|
|||||||
@@ -43,15 +43,15 @@ class BackupMenu(BaseMenu):
|
|||||||
|
|
||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
self.options = {
|
self.options = {
|
||||||
"1": Option(method=self.backup_klipper),
|
"1": Option(method=self.backup_klipper, menu=False),
|
||||||
"2": Option(method=self.backup_moonraker),
|
"2": Option(method=self.backup_moonraker, menu=False),
|
||||||
"3": Option(method=self.backup_printer_config),
|
"3": Option(method=self.backup_printer_config, menu=False),
|
||||||
"4": Option(method=self.backup_moonraker_db),
|
"4": Option(method=self.backup_moonraker_db, menu=False),
|
||||||
"5": Option(method=self.backup_mainsail),
|
"5": Option(method=self.backup_mainsail, menu=False),
|
||||||
"6": Option(method=self.backup_fluidd),
|
"6": Option(method=self.backup_fluidd, menu=False),
|
||||||
"7": Option(method=self.backup_mainsail_config),
|
"7": Option(method=self.backup_mainsail_config, menu=False),
|
||||||
"8": Option(method=self.backup_fluidd_config),
|
"8": Option(method=self.backup_fluidd_config, menu=False),
|
||||||
"9": Option(method=self.backup_klipperscreen),
|
"9": Option(method=self.backup_klipperscreen, menu=False),
|
||||||
}
|
}
|
||||||
|
|
||||||
def print_menu(self) -> None:
|
def print_menu(self) -> None:
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ from core.constants import (
|
|||||||
)
|
)
|
||||||
from core.logger import Logger
|
from core.logger import Logger
|
||||||
from core.menus import FooterType, Option
|
from core.menus import FooterType, Option
|
||||||
from utils.input_utils import get_selection_input
|
|
||||||
|
|
||||||
|
|
||||||
def clear() -> None:
|
def clear() -> None:
|
||||||
@@ -124,12 +123,12 @@ class BaseMenu(metaclass=PostInitCaller):
|
|||||||
|
|
||||||
# conditionally add options based on footer type
|
# conditionally add options based on footer type
|
||||||
if self.footer_type is FooterType.QUIT:
|
if self.footer_type is FooterType.QUIT:
|
||||||
self.options["q"] = Option(method=self.__exit)
|
self.options["q"] = Option(method=self.__exit, menu=False)
|
||||||
if self.footer_type is FooterType.BACK:
|
if self.footer_type is FooterType.BACK:
|
||||||
self.options["b"] = Option(method=self.__go_back)
|
self.options["b"] = Option(method=self.__go_back, menu=False)
|
||||||
if self.footer_type is FooterType.BACK_HELP:
|
if self.footer_type is FooterType.BACK_HELP:
|
||||||
self.options["b"] = Option(method=self.__go_back)
|
self.options["b"] = Option(method=self.__go_back, menu=False)
|
||||||
self.options["h"] = Option(method=self.__go_to_help)
|
self.options["h"] = Option(method=self.__go_to_help, menu=False)
|
||||||
# if defined, add the default option to the options dict
|
# if defined, add the default option to the options dict
|
||||||
if self.default_option is not None:
|
if self.default_option is not None:
|
||||||
self.options[""] = self.default_option
|
self.options[""] = self.default_option
|
||||||
@@ -142,7 +141,7 @@ class BaseMenu(metaclass=PostInitCaller):
|
|||||||
def __go_to_help(self, **kwargs) -> None:
|
def __go_to_help(self, **kwargs) -> None:
|
||||||
if self.help_menu is None:
|
if self.help_menu is None:
|
||||||
return
|
return
|
||||||
self.help_menu(previous_menu=self.__class__).run()
|
self.help_menu(previous_menu=self).run()
|
||||||
|
|
||||||
def __exit(self, **kwargs) -> None:
|
def __exit(self, **kwargs) -> None:
|
||||||
Logger.print_ok("###### Happy printing!", False)
|
Logger.print_ok("###### Happy printing!", False)
|
||||||
@@ -178,20 +177,43 @@ class BaseMenu(metaclass=PostInitCaller):
|
|||||||
self.print_menu()
|
self.print_menu()
|
||||||
self.print_footer()
|
self.print_footer()
|
||||||
|
|
||||||
|
def validate_user_input(self, usr_input: str) -> Option:
|
||||||
|
"""
|
||||||
|
Validate the user input and either return an Option, a string or None
|
||||||
|
:param usr_input: The user input in form of a string
|
||||||
|
:return: Option, str or None
|
||||||
|
"""
|
||||||
|
usr_input = usr_input.lower()
|
||||||
|
option = self.options.get(usr_input, Option(None, False, "", None))
|
||||||
|
|
||||||
|
# if option/usr_input is None/empty string, we execute the menus default option if specified
|
||||||
|
if (option is None or usr_input == "") and self.default_option is not None:
|
||||||
|
self.default_option.opt_index = usr_input
|
||||||
|
return self.default_option
|
||||||
|
|
||||||
|
# user selected a regular option
|
||||||
|
option.opt_index = usr_input
|
||||||
|
return option
|
||||||
|
|
||||||
|
def handle_user_input(self) -> Option:
|
||||||
|
"""Handle the user input, return the validated input or print an error."""
|
||||||
|
while True:
|
||||||
|
print(f"{COLOR_CYAN}###### {self.input_label_txt}: {RESET_FORMAT}", end="")
|
||||||
|
usr_input = input().lower()
|
||||||
|
validated_input = self.validate_user_input(usr_input)
|
||||||
|
|
||||||
|
if validated_input.method is not None:
|
||||||
|
return validated_input
|
||||||
|
else:
|
||||||
|
Logger.print_error("Invalid input!", False)
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
"""Start the menu lifecycle. When this function returns, the lifecycle of the menu ends."""
|
"""Start the menu lifecycle. When this function returns, the lifecycle of the menu ends."""
|
||||||
try:
|
try:
|
||||||
self.display_menu()
|
self.display_menu()
|
||||||
option = get_selection_input(self.input_label_txt, self.options)
|
option = self.handle_user_input()
|
||||||
selected_option: Option = self.options.get(option)
|
option.method(opt_index=option.opt_index, opt_data=option.opt_data)
|
||||||
|
|
||||||
selected_option.method(
|
|
||||||
opt_index=selected_option.opt_index,
|
|
||||||
opt_data=selected_option.opt_data,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.run()
|
self.run()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
Logger.print_error(
|
Logger.print_error(
|
||||||
f"An unexpected error occured:\n{e}\n{traceback.format_exc()}"
|
f"An unexpected error occured:\n{e}\n{traceback.format_exc()}"
|
||||||
|
|||||||
@@ -40,16 +40,16 @@ class InstallMenu(BaseMenu):
|
|||||||
|
|
||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
self.options = {
|
self.options = {
|
||||||
"1": Option(method=self.install_klipper),
|
"1": Option(method=self.install_klipper, menu=False),
|
||||||
"2": Option(method=self.install_moonraker),
|
"2": Option(method=self.install_moonraker, menu=False),
|
||||||
"3": Option(method=self.install_mainsail),
|
"3": Option(method=self.install_mainsail, menu=False),
|
||||||
"4": Option(method=self.install_fluidd),
|
"4": Option(method=self.install_fluidd, menu=False),
|
||||||
"5": Option(method=self.install_mainsail_config),
|
"5": Option(method=self.install_mainsail_config, menu=False),
|
||||||
"6": Option(method=self.install_fluidd_config),
|
"6": Option(method=self.install_fluidd_config, menu=False),
|
||||||
"7": Option(method=self.install_klipperscreen),
|
"7": Option(method=self.install_klipperscreen, menu=False),
|
||||||
"8": Option(method=self.install_mobileraker),
|
"8": Option(method=self.install_mobileraker, menu=False),
|
||||||
"9": Option(method=self.install_crowsnest),
|
"9": Option(method=self.install_crowsnest, menu=False),
|
||||||
"10": Option(method=self.install_octoeverywhere),
|
"10": Option(method=self.install_octoeverywhere, menu=False),
|
||||||
}
|
}
|
||||||
|
|
||||||
def print_menu(self) -> None:
|
def print_menu(self) -> None:
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ from core.menus.settings_menu import SettingsMenu
|
|||||||
from core.menus.update_menu import UpdateMenu
|
from core.menus.update_menu import UpdateMenu
|
||||||
from core.types import ComponentStatus, StatusMap, StatusText
|
from core.types import ComponentStatus, StatusMap, StatusText
|
||||||
from extensions.extensions_menu import ExtensionsMenu
|
from extensions.extensions_menu import ExtensionsMenu
|
||||||
from utils.common import get_kiauh_version
|
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
# noinspection PyUnusedLocal
|
||||||
@@ -56,9 +55,7 @@ class MainMenu(BaseMenu):
|
|||||||
self.header: bool = True
|
self.header: bool = True
|
||||||
self.footer_type: FooterType = FooterType.QUIT
|
self.footer_type: FooterType = FooterType.QUIT
|
||||||
|
|
||||||
self.version = ""
|
self.kl_status = self.kl_repo = self.mr_status = self.mr_repo = ""
|
||||||
self.kl_status = self.kl_owner = self.kl_repo = ""
|
|
||||||
self.mr_status = self.mr_owner = self.mr_repo = ""
|
|
||||||
self.ms_status = self.fl_status = self.ks_status = self.mb_status = ""
|
self.ms_status = self.fl_status = self.ks_status = self.mb_status = ""
|
||||||
self.cn_status = self.cc_status = self.oe_status = ""
|
self.cn_status = self.cc_status = self.oe_status = ""
|
||||||
self._init_status()
|
self._init_status()
|
||||||
@@ -69,14 +66,14 @@ class MainMenu(BaseMenu):
|
|||||||
|
|
||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
self.options = {
|
self.options = {
|
||||||
"0": Option(method=self.log_upload_menu),
|
"0": Option(method=self.log_upload_menu, menu=True),
|
||||||
"1": Option(method=self.install_menu),
|
"1": Option(method=self.install_menu, menu=True),
|
||||||
"2": Option(method=self.update_menu),
|
"2": Option(method=self.update_menu, menu=True),
|
||||||
"3": Option(method=self.remove_menu),
|
"3": Option(method=self.remove_menu, menu=True),
|
||||||
"4": Option(method=self.advanced_menu),
|
"4": Option(method=self.advanced_menu, menu=True),
|
||||||
"5": Option(method=self.backup_menu),
|
"5": Option(method=self.backup_menu, menu=True),
|
||||||
"e": Option(method=self.extension_menu),
|
"e": Option(method=self.extension_menu, menu=True),
|
||||||
"s": Option(method=self.settings_menu),
|
"s": Option(method=self.settings_menu, menu=True),
|
||||||
}
|
}
|
||||||
|
|
||||||
def _init_status(self) -> None:
|
def _init_status(self) -> None:
|
||||||
@@ -89,7 +86,6 @@ class MainMenu(BaseMenu):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _fetch_status(self) -> None:
|
def _fetch_status(self) -> None:
|
||||||
self.version = get_kiauh_version()
|
|
||||||
self._get_component_status("kl", get_klipper_status)
|
self._get_component_status("kl", get_klipper_status)
|
||||||
self._get_component_status("mr", get_moonraker_status)
|
self._get_component_status("mr", get_moonraker_status)
|
||||||
self._get_component_status("ms", get_client_status, MainsailData())
|
self._get_component_status("ms", get_client_status, MainsailData())
|
||||||
@@ -104,7 +100,6 @@ class MainMenu(BaseMenu):
|
|||||||
status_data: ComponentStatus = status_fn(*args)
|
status_data: ComponentStatus = status_fn(*args)
|
||||||
code: int = status_data.status
|
code: int = status_data.status
|
||||||
status: StatusText = StatusMap[code]
|
status: StatusText = StatusMap[code]
|
||||||
owner: str = status_data.owner
|
|
||||||
repo: str = status_data.repo
|
repo: str = status_data.repo
|
||||||
instance_count: int = status_data.instances
|
instance_count: int = status_data.instances
|
||||||
|
|
||||||
@@ -113,7 +108,6 @@ class MainMenu(BaseMenu):
|
|||||||
count_txt = f": {instance_count}"
|
count_txt = f": {instance_count}"
|
||||||
|
|
||||||
setattr(self, f"{name}_status", self._format_by_code(code, status, count_txt))
|
setattr(self, f"{name}_status", self._format_by_code(code, status, count_txt))
|
||||||
setattr(self, f"{name}_owner", f"{COLOR_CYAN}{owner}{RESET_FORMAT}")
|
|
||||||
setattr(self, f"{name}_repo", f"{COLOR_CYAN}{repo}{RESET_FORMAT}")
|
setattr(self, f"{name}_repo", f"{COLOR_CYAN}{repo}{RESET_FORMAT}")
|
||||||
|
|
||||||
def _format_by_code(self, code: int, status: str, count: str) -> str:
|
def _format_by_code(self, code: int, status: str, count: str) -> str:
|
||||||
@@ -131,7 +125,7 @@ class MainMenu(BaseMenu):
|
|||||||
self._fetch_status()
|
self._fetch_status()
|
||||||
|
|
||||||
header = " [ Main Menu ] "
|
header = " [ Main Menu ] "
|
||||||
footer1 = f"{COLOR_CYAN}{self.version}{RESET_FORMAT}"
|
footer1 = f"{COLOR_CYAN}KIAUH v6.0.0{RESET_FORMAT}"
|
||||||
footer2 = f"Changelog: {COLOR_MAGENTA}https://git.io/JnmlX{RESET_FORMAT}"
|
footer2 = f"Changelog: {COLOR_MAGENTA}https://git.io/JnmlX{RESET_FORMAT}"
|
||||||
color = COLOR_CYAN
|
color = COLOR_CYAN
|
||||||
count = 62 - len(color) - len(RESET_FORMAT)
|
count = 62 - len(color) - len(RESET_FORMAT)
|
||||||
@@ -143,19 +137,17 @@ class MainMenu(BaseMenu):
|
|||||||
║ {color}{header:~^{count}}{RESET_FORMAT} ║
|
║ {color}{header:~^{count}}{RESET_FORMAT} ║
|
||||||
╟──────────────────┬────────────────────────────────────╢
|
╟──────────────────┬────────────────────────────────────╢
|
||||||
║ 0) [Log-Upload] │ Klipper: {self.kl_status:<{pad1}} ║
|
║ 0) [Log-Upload] │ Klipper: {self.kl_status:<{pad1}} ║
|
||||||
║ │ Owner: {self.kl_owner:<{pad1}} ║
|
║ │ Repo: {self.kl_repo:<{pad1}} ║
|
||||||
║ 1) [Install] │ Repo: {self.kl_repo:<{pad1}} ║
|
║ 1) [Install] ├────────────────────────────────────╢
|
||||||
║ 2) [Update] ├────────────────────────────────────╢
|
║ 2) [Update] │ Moonraker: {self.mr_status:<{pad1}} ║
|
||||||
║ 3) [Remove] │ Moonraker: {self.mr_status:<{pad1}} ║
|
║ 3) [Remove] │ Repo: {self.mr_repo:<{pad1}} ║
|
||||||
║ 4) [Advanced] │ Owner: {self.mr_owner:<{pad1}} ║
|
║ 4) [Advanced] ├────────────────────────────────────╢
|
||||||
║ 5) [Backup] │ Repo: {self.mr_repo:<{pad1}} ║
|
║ 5) [Backup] │ Mainsail: {self.ms_status:<{pad2}} ║
|
||||||
║ ├────────────────────────────────────╢
|
|
||||||
║ S) [Settings] │ Mainsail: {self.ms_status:<{pad2}} ║
|
|
||||||
║ │ Fluidd: {self.fl_status:<{pad2}} ║
|
║ │ Fluidd: {self.fl_status:<{pad2}} ║
|
||||||
║ Community: │ Client-Config: {self.cc_status:<{pad2}} ║
|
║ S) [Settings] │ Client-Config: {self.cc_status:<{pad2}} ║
|
||||||
║ E) [Extensions] │ ║
|
║ │ ║
|
||||||
║ │ KlipperScreen: {self.ks_status:<{pad2}} ║
|
║ Community: │ KlipperScreen: {self.ks_status:<{pad2}} ║
|
||||||
║ │ Mobileraker: {self.mb_status:<{pad2}} ║
|
║ E) [Extensions] │ Mobileraker: {self.mb_status:<{pad2}} ║
|
||||||
║ │ OctoEverywhere: {self.oe_status:<{pad2}} ║
|
║ │ OctoEverywhere: {self.oe_status:<{pad2}} ║
|
||||||
║ │ Crowsnest: {self.cn_status:<{pad2}} ║
|
║ │ Crowsnest: {self.cn_status:<{pad2}} ║
|
||||||
╟──────────────────┼────────────────────────────────────╢
|
╟──────────────────┼────────────────────────────────────╢
|
||||||
|
|||||||
@@ -41,14 +41,14 @@ class RemoveMenu(BaseMenu):
|
|||||||
|
|
||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
self.options = {
|
self.options = {
|
||||||
"1": Option(method=self.remove_klipper),
|
"1": Option(method=self.remove_klipper, menu=True),
|
||||||
"2": Option(method=self.remove_moonraker),
|
"2": Option(method=self.remove_moonraker, menu=True),
|
||||||
"3": Option(method=self.remove_mainsail),
|
"3": Option(method=self.remove_mainsail, menu=True),
|
||||||
"4": Option(method=self.remove_fluidd),
|
"4": Option(method=self.remove_fluidd, menu=True),
|
||||||
"5": Option(method=self.remove_klipperscreen),
|
"5": Option(method=self.remove_klipperscreen, menu=True),
|
||||||
"6": Option(method=self.remove_mobileraker),
|
"6": Option(method=self.remove_mobileraker, menu=True),
|
||||||
"7": Option(method=self.remove_crowsnest),
|
"7": Option(method=self.remove_crowsnest, menu=True),
|
||||||
"8": Option(method=self.remove_octoeverywhere),
|
"8": Option(method=self.remove_octoeverywhere, menu=True),
|
||||||
}
|
}
|
||||||
|
|
||||||
def print_menu(self) -> None:
|
def print_menu(self) -> None:
|
||||||
|
|||||||
@@ -8,16 +8,24 @@
|
|||||||
# ======================================================================= #
|
# ======================================================================= #
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import shutil
|
||||||
import textwrap
|
import textwrap
|
||||||
from typing import Literal, Tuple, Type
|
from pathlib import Path
|
||||||
|
from typing import Tuple, Type
|
||||||
|
|
||||||
|
from components.klipper import KLIPPER_DIR
|
||||||
|
from components.klipper.klipper import Klipper
|
||||||
|
from components.moonraker import MOONRAKER_DIR
|
||||||
|
from components.moonraker.moonraker import Moonraker
|
||||||
from core.constants import COLOR_CYAN, COLOR_GREEN, RESET_FORMAT
|
from core.constants import COLOR_CYAN, COLOR_GREEN, RESET_FORMAT
|
||||||
|
from core.instance_manager.instance_manager import InstanceManager
|
||||||
from core.logger import DialogType, Logger
|
from core.logger import DialogType, Logger
|
||||||
from core.menus import Option
|
from core.menus import Option
|
||||||
from core.menus.base_menu import BaseMenu
|
from core.menus.base_menu import BaseMenu
|
||||||
from core.settings.kiauh_settings import KiauhSettings, RepoSettings
|
from core.settings.kiauh_settings import KiauhSettings
|
||||||
from procedures.switch_repo import run_switch_repo_routine
|
from utils.git_utils import git_clone_wrapper
|
||||||
from utils.input_utils import get_confirm, get_string_input
|
from utils.input_utils import get_confirm, get_string_input
|
||||||
|
from utils.instance_utils import get_instances
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
# noinspection PyUnusedLocal
|
||||||
@@ -40,11 +48,11 @@ class SettingsMenu(BaseMenu):
|
|||||||
|
|
||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
self.options = {
|
self.options = {
|
||||||
"1": Option(method=self.set_klipper_repo),
|
"1": Option(method=self.set_klipper_repo, menu=True),
|
||||||
"2": Option(method=self.set_moonraker_repo),
|
"2": Option(method=self.set_moonraker_repo, menu=True),
|
||||||
"3": Option(method=self.toggle_mainsail_release),
|
"3": Option(method=self.toggle_mainsail_release, menu=True),
|
||||||
"4": Option(method=self.toggle_fluidd_release),
|
"4": Option(method=self.toggle_fluidd_release, menu=False),
|
||||||
"5": Option(method=self.toggle_backup_before_update),
|
"5": Option(method=self.toggle_backup_before_update, menu=False),
|
||||||
}
|
}
|
||||||
|
|
||||||
def print_menu(self) -> None:
|
def print_menu(self) -> None:
|
||||||
@@ -97,28 +105,22 @@ class SettingsMenu(BaseMenu):
|
|||||||
self.mainsail_unstable = self.settings.mainsail.unstable_releases
|
self.mainsail_unstable = self.settings.mainsail.unstable_releases
|
||||||
self.fluidd_unstable = self.settings.fluidd.unstable_releases
|
self.fluidd_unstable = self.settings.fluidd.unstable_releases
|
||||||
|
|
||||||
def _format_repo_str(self, repo_name: Literal["klipper", "moonraker"]) -> None:
|
def _format_repo_str(self, repo_name: str) -> None:
|
||||||
repo: RepoSettings = self.settings[repo_name]
|
repo = self.settings.get(repo_name, "repo_url")
|
||||||
repo_str = f"{'/'.join(repo.repo_url.rsplit('/', 2)[-2:])}"
|
repo = f"{'/'.join(repo.rsplit('/', 2)[-2:])}"
|
||||||
branch_str = f"({COLOR_CYAN}@ {repo.branch}{RESET_FORMAT})"
|
branch = self.settings.get(repo_name, "branch")
|
||||||
|
branch = f"({COLOR_CYAN}@ {branch}{RESET_FORMAT})"
|
||||||
setattr(
|
setattr(self, f"{repo_name}_repo", f"{COLOR_CYAN}{repo}{RESET_FORMAT} {branch}")
|
||||||
self,
|
|
||||||
f"{repo_name}_repo",
|
|
||||||
f"{COLOR_CYAN}{repo_str}{RESET_FORMAT} {branch_str}",
|
|
||||||
)
|
|
||||||
|
|
||||||
def _gather_input(self) -> Tuple[str, str]:
|
def _gather_input(self) -> Tuple[str, str]:
|
||||||
Logger.print_dialog(
|
Logger.print_dialog(
|
||||||
DialogType.ATTENTION,
|
DialogType.ATTENTION,
|
||||||
[
|
[
|
||||||
"There is no input validation in place! Make sure your the input is "
|
"There is no input validation in place! Make sure your"
|
||||||
"valid and has no typos or invalid characters! For the change to take "
|
" input is valid and has no typos! For any change to"
|
||||||
"effect, the new repository will be cloned. A backup of the old "
|
" take effect, the repository must be cloned again. "
|
||||||
"repository will be created.",
|
"Make sure you don't have any ongoing prints running, "
|
||||||
"\n\n",
|
"as the services will be restarted!"
|
||||||
"Make sure you don't have any ongoing prints running, as the services "
|
|
||||||
"will be restarted during this process! You will loose any ongoing print!",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
repo = get_string_input(
|
repo = get_string_input(
|
||||||
@@ -132,7 +134,7 @@ class SettingsMenu(BaseMenu):
|
|||||||
|
|
||||||
return repo, branch
|
return repo, branch
|
||||||
|
|
||||||
def _set_repo(self, repo_name: Literal["klipper", "moonraker"]) -> None:
|
def _set_repo(self, repo_name: str) -> None:
|
||||||
repo_url, branch = self._gather_input()
|
repo_url, branch = self._gather_input()
|
||||||
display_name = repo_name.capitalize()
|
display_name = repo_name.capitalize()
|
||||||
Logger.print_dialog(
|
Logger.print_dialog(
|
||||||
@@ -146,13 +148,10 @@ class SettingsMenu(BaseMenu):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if get_confirm("Apply changes?", allow_go_back=True):
|
if get_confirm("Apply changes?", allow_go_back=True):
|
||||||
repo: RepoSettings = self.settings[repo_name]
|
self.settings.set(repo_name, "repo_url", repo_url)
|
||||||
repo.repo_url = repo_url
|
self.settings.set(repo_name, "branch", branch)
|
||||||
repo.branch = branch
|
|
||||||
|
|
||||||
self.settings.save()
|
self.settings.save()
|
||||||
self._load_settings()
|
self._load_settings()
|
||||||
|
|
||||||
Logger.print_ok("Changes saved!")
|
Logger.print_ok("Changes saved!")
|
||||||
else:
|
else:
|
||||||
Logger.print_info(
|
Logger.print_info(
|
||||||
@@ -162,10 +161,31 @@ class SettingsMenu(BaseMenu):
|
|||||||
|
|
||||||
Logger.print_status(f"Switching to {display_name}'s new source repository ...")
|
Logger.print_status(f"Switching to {display_name}'s new source repository ...")
|
||||||
self._switch_repo(repo_name)
|
self._switch_repo(repo_name)
|
||||||
|
Logger.print_ok(f"Switched to {repo_url} at branch {branch}!")
|
||||||
|
|
||||||
def _switch_repo(self, name: Literal["klipper", "moonraker"]) -> None:
|
def _switch_repo(self, name: str) -> None:
|
||||||
repo: RepoSettings = self.settings[name]
|
target_dir: Path
|
||||||
run_switch_repo_routine(name, repo)
|
if name == "klipper":
|
||||||
|
target_dir = KLIPPER_DIR
|
||||||
|
_type = Klipper
|
||||||
|
elif name == "moonraker":
|
||||||
|
target_dir = MOONRAKER_DIR
|
||||||
|
_type = Moonraker
|
||||||
|
else:
|
||||||
|
Logger.print_error("Invalid repository name!")
|
||||||
|
return
|
||||||
|
|
||||||
|
if target_dir.exists():
|
||||||
|
shutil.rmtree(target_dir)
|
||||||
|
|
||||||
|
instances = get_instances(_type)
|
||||||
|
InstanceManager.stop_all(instances)
|
||||||
|
|
||||||
|
repo = self.settings.get(name, "repo_url")
|
||||||
|
branch = self.settings.get(name, "branch")
|
||||||
|
git_clone_wrapper(repo, target_dir, branch)
|
||||||
|
|
||||||
|
InstanceManager.start_all(instances)
|
||||||
|
|
||||||
def set_klipper_repo(self, **kwargs) -> None:
|
def set_klipper_repo(self, **kwargs) -> None:
|
||||||
self._set_repo("klipper")
|
self._set_repo("klipper")
|
||||||
|
|||||||
@@ -50,13 +50,13 @@ from core.logger import DialogType, Logger
|
|||||||
from core.menus import Option
|
from core.menus import Option
|
||||||
from core.menus.base_menu import BaseMenu
|
from core.menus.base_menu import BaseMenu
|
||||||
from core.spinner import Spinner
|
from core.spinner import Spinner
|
||||||
from core.types import ComponentStatus
|
|
||||||
from utils.input_utils import get_confirm
|
from utils.input_utils import get_confirm
|
||||||
from utils.sys_utils import (
|
from utils.sys_utils import (
|
||||||
get_upgradable_packages,
|
get_upgradable_packages,
|
||||||
update_system_package_lists,
|
update_system_package_lists,
|
||||||
upgrade_system_packages,
|
upgrade_system_packages,
|
||||||
)
|
)
|
||||||
|
from core.types import ComponentStatus
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
# noinspection PyUnusedLocal
|
||||||
@@ -102,18 +102,18 @@ class UpdateMenu(BaseMenu):
|
|||||||
|
|
||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
self.options = {
|
self.options = {
|
||||||
"a": Option(self.update_all),
|
"a": Option(self.update_all, menu=False),
|
||||||
"1": Option(self.update_klipper),
|
"1": Option(self.update_klipper, menu=False),
|
||||||
"2": Option(self.update_moonraker),
|
"2": Option(self.update_moonraker, menu=False),
|
||||||
"3": Option(self.update_mainsail),
|
"3": Option(self.update_mainsail, menu=False),
|
||||||
"4": Option(self.update_fluidd),
|
"4": Option(self.update_fluidd, menu=False),
|
||||||
"5": Option(self.update_mainsail_config),
|
"5": Option(self.update_mainsail_config, menu=False),
|
||||||
"6": Option(self.update_fluidd_config),
|
"6": Option(self.update_fluidd_config, menu=False),
|
||||||
"7": Option(self.update_klipperscreen),
|
"7": Option(self.update_klipperscreen, menu=False),
|
||||||
"8": Option(self.update_mobileraker),
|
"8": Option(self.update_mobileraker, menu=False),
|
||||||
"9": Option(self.update_crowsnest),
|
"9": Option(self.update_crowsnest, menu=False),
|
||||||
"10": Option(self.update_octoeverywhere),
|
"10": Option(self.update_octoeverywhere, menu=False),
|
||||||
"11": Option(self.upgrade_system_packages),
|
"11": Option(self.upgrade_system_packages, menu=False),
|
||||||
}
|
}
|
||||||
|
|
||||||
def print_menu(self) -> None:
|
def print_menu(self) -> None:
|
||||||
@@ -281,6 +281,8 @@ class UpdateMenu(BaseMenu):
|
|||||||
DialogType.CUSTOM,
|
DialogType.CUSTOM,
|
||||||
["The following packages will be upgraded:", "\n\n", pkgs],
|
["The following packages will be upgraded:", "\n\n", pkgs],
|
||||||
custom_title="UPGRADABLE SYSTEM UPDATES",
|
custom_title="UPGRADABLE SYSTEM UPDATES",
|
||||||
|
padding_top=0,
|
||||||
|
padding_bottom=0,
|
||||||
)
|
)
|
||||||
if not get_confirm("Continue?"):
|
if not get_confirm("Continue?"):
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -8,9 +8,6 @@
|
|||||||
# ======================================================================= #
|
# ======================================================================= #
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from core.logger import DialogType, Logger
|
from core.logger import DialogType, Logger
|
||||||
from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import (
|
from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import (
|
||||||
NoOptionError,
|
NoOptionError,
|
||||||
@@ -25,21 +22,33 @@ DEFAULT_CFG = PROJECT_ROOT.joinpath("default.kiauh.cfg")
|
|||||||
CUSTOM_CFG = PROJECT_ROOT.joinpath("kiauh.cfg")
|
CUSTOM_CFG = PROJECT_ROOT.joinpath("kiauh.cfg")
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class AppSettings:
|
class AppSettings:
|
||||||
backup_before_update: bool | None = field(default=None)
|
def __init__(self) -> None:
|
||||||
|
self.backup_before_update = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
class KlipperSettings:
|
||||||
class RepoSettings:
|
def __init__(self) -> None:
|
||||||
repo_url: str | None = field(default=None)
|
self.repo_url = None
|
||||||
branch: str | None = field(default=None)
|
self.branch = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
class MoonrakerSettings:
|
||||||
class WebUiSettings:
|
def __init__(self) -> None:
|
||||||
port: str | None = field(default=None)
|
self.repo_url = None
|
||||||
unstable_releases: bool | None = field(default=None)
|
self.branch = None
|
||||||
|
|
||||||
|
|
||||||
|
class MainsailSettings:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.port = None
|
||||||
|
self.unstable_releases = None
|
||||||
|
|
||||||
|
|
||||||
|
class FluiddSettings:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.port = None
|
||||||
|
self.unstable_releases = None
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
# noinspection PyUnusedLocal
|
||||||
@@ -52,12 +61,6 @@ class KiauhSettings:
|
|||||||
cls._instance = super(KiauhSettings, cls).__new__(cls, *args, **kwargs)
|
cls._instance = super(KiauhSettings, cls).__new__(cls, *args, **kwargs)
|
||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return f"KiauhSettings(kiauh={self.kiauh}, klipper={self.klipper}, moonraker={self.moonraker}, mainsail={self.mainsail}, fluidd={self.fluidd})"
|
|
||||||
|
|
||||||
def __getitem__(self, item: str) -> Any:
|
|
||||||
return getattr(self, item)
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
if not hasattr(self, "__initialized"):
|
if not hasattr(self, "__initialized"):
|
||||||
self.__initialized = False
|
self.__initialized = False
|
||||||
@@ -66,10 +69,20 @@ class KiauhSettings:
|
|||||||
self.__initialized = True
|
self.__initialized = True
|
||||||
self.config = SimpleConfigParser()
|
self.config = SimpleConfigParser()
|
||||||
self.kiauh = AppSettings()
|
self.kiauh = AppSettings()
|
||||||
self.klipper = RepoSettings()
|
self.klipper = KlipperSettings()
|
||||||
self.moonraker = RepoSettings()
|
self.moonraker = MoonrakerSettings()
|
||||||
self.mainsail = WebUiSettings()
|
self.mainsail = MainsailSettings()
|
||||||
self.fluidd = WebUiSettings()
|
self.fluidd = FluiddSettings()
|
||||||
|
|
||||||
|
self.kiauh.backup_before_update = None
|
||||||
|
self.klipper.repo_url = None
|
||||||
|
self.klipper.branch = None
|
||||||
|
self.moonraker.repo_url = None
|
||||||
|
self.moonraker.branch = None
|
||||||
|
self.mainsail.port = None
|
||||||
|
self.mainsail.unstable_releases = None
|
||||||
|
self.fluidd.port = None
|
||||||
|
self.fluidd.unstable_releases = None
|
||||||
|
|
||||||
self._load_config()
|
self._load_config()
|
||||||
|
|
||||||
@@ -89,8 +102,22 @@ class KiauhSettings:
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
def set(self, section: str, option: str, value: str | int | bool) -> None:
|
||||||
|
"""
|
||||||
|
Set a value in the settings state by providing the section and option name as strings.
|
||||||
|
Prefer direct access to the properties, as it is usually safer!
|
||||||
|
:param section: The section name as string.
|
||||||
|
:param option: The option name as string.
|
||||||
|
:param value: The value to set as string, int or bool.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
section = getattr(self, section)
|
||||||
|
section.option = value # type: ignore
|
||||||
|
except AttributeError:
|
||||||
|
raise
|
||||||
|
|
||||||
def save(self) -> None:
|
def save(self) -> None:
|
||||||
self._set_config_options_state()
|
self._set_config_options()
|
||||||
self.config.write(CUSTOM_CFG)
|
self.config.write(CUSTOM_CFG)
|
||||||
self._load_config()
|
self._load_config()
|
||||||
|
|
||||||
@@ -102,7 +129,7 @@ class KiauhSettings:
|
|||||||
self.config.read(cfg)
|
self.config.read(cfg)
|
||||||
|
|
||||||
self._validate_cfg()
|
self._validate_cfg()
|
||||||
self._apply_settings_from_file()
|
self._read_settings()
|
||||||
|
|
||||||
def _validate_cfg(self) -> None:
|
def _validate_cfg(self) -> None:
|
||||||
try:
|
try:
|
||||||
@@ -144,7 +171,7 @@ class KiauhSettings:
|
|||||||
if v.isdigit() or v.lower() == "true" or v.lower() == "false":
|
if v.isdigit() or v.lower() == "true" or v.lower() == "false":
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
def _apply_settings_from_file(self) -> None:
|
def _read_settings(self) -> None:
|
||||||
self.kiauh.backup_before_update = self.config.getboolean(
|
self.kiauh.backup_before_update = self.config.getboolean(
|
||||||
"kiauh", "backup_before_update"
|
"kiauh", "backup_before_update"
|
||||||
)
|
)
|
||||||
@@ -161,7 +188,7 @@ class KiauhSettings:
|
|||||||
"fluidd", "unstable_releases"
|
"fluidd", "unstable_releases"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _set_config_options_state(self) -> None:
|
def _set_config_options(self) -> None:
|
||||||
self.config.set(
|
self.config.set(
|
||||||
"kiauh",
|
"kiauh",
|
||||||
"backup_before_update",
|
"backup_before_update",
|
||||||
|
|||||||
@@ -85,46 +85,10 @@ class DuplicateOptionError(Exception):
|
|||||||
class SimpleConfigParser:
|
class SimpleConfigParser:
|
||||||
"""A customized config parser targeted at handling Klipper style config files"""
|
"""A customized config parser targeted at handling Klipper style config files"""
|
||||||
|
|
||||||
# definition of section line:
|
_SECTION_RE = re.compile(r"\s*\[(\w+\s?.+)]\s*([#;].*)?$")
|
||||||
# - then line MUST start with an opening square bracket - it is the first section marker
|
_OPTION_RE = re.compile(r"^\s*(\w+)\s*[:=]\s*([^=:].*)\s*([#;].*)?$")
|
||||||
# - the section marker MUST be followed by at least one character - it is the section name
|
_MLOPTION_RE = re.compile(r"^\s*(\w+)\s*[:=]\s*([#;].*)?$")
|
||||||
# - the section name MUST be followed by a closing square bracket - it is the second section marker
|
|
||||||
# - the second section marker MAY be followed by any amount of whitespace characters
|
|
||||||
# - the second section marker MAY be followed by a # or ; - it is the comment marker
|
|
||||||
# - the inline comment MAY be of any length and character
|
|
||||||
_SECTION_RE = re.compile(r"\[(.+)]\s*([#;].*)?$")
|
|
||||||
|
|
||||||
# definition of option line:
|
|
||||||
# - the line MUST start with a word - it is the option name
|
|
||||||
# - the option name MUST be followed by a colon or an equal sign - it is the separator
|
|
||||||
# - the separator MUST be followed by a value
|
|
||||||
# - the separator MAY have any amount of leading or trailing whitespaces
|
|
||||||
# - the separator MUST NOT be directly followed by a colon or equal sign
|
|
||||||
# - the value MAY be of any length and character
|
|
||||||
# - the value MAY contain any amount of trailing whitespaces
|
|
||||||
# - the value MAY be followed by a # or ; - it is the comment marker
|
|
||||||
# - the inline comment MAY be of any length and character
|
|
||||||
_OPTION_RE = re.compile(r"^([^:=\s]+)\s?[:=]\s*([^=:].*)\s*([#;].*)?$")
|
|
||||||
|
|
||||||
# definition of multiline option line:
|
|
||||||
# - the line MUST start with a word - it is the option name
|
|
||||||
# - the option name MUST be followed by a colon or an equal sign - it is the separator
|
|
||||||
# - the separator MUST NOT be followed by a value
|
|
||||||
# - the separator MAY have any amount of leading or trailing whitespaces
|
|
||||||
# - the separator MUST NOT be directly followed by a colon or equal sign
|
|
||||||
# - the separator MAY be followed by a # or ; - it is the comment marker
|
|
||||||
# - the inline comment MAY be of any length and character
|
|
||||||
_MLOPTION_RE = re.compile(r"^([^:=\s]+)\s*[:=]\s*([#;].*)?$")
|
|
||||||
|
|
||||||
# definition of comment line:
|
|
||||||
# - the line MAY start with any amount of whitespace characters
|
|
||||||
# - the line MUST contain a # or ; - it is the comment marker
|
|
||||||
# - the comment marker MAY be followed by any amount of whitespace characters
|
|
||||||
# - the comment MAY be of any length and character
|
|
||||||
_COMMENT_RE = re.compile(r"^\s*([#;].*)?$")
|
_COMMENT_RE = re.compile(r"^\s*([#;].*)?$")
|
||||||
|
|
||||||
# definition of empty line:
|
|
||||||
# - the line MUST contain only whitespace characters
|
|
||||||
_EMPTY_LINE_RE = re.compile(r"^\s*$")
|
_EMPTY_LINE_RE = re.compile(r"^\s*$")
|
||||||
|
|
||||||
BOOLEAN_STATES = {
|
BOOLEAN_STATES = {
|
||||||
|
|||||||
@@ -21,8 +21,4 @@ testcases = [
|
|||||||
"serial",
|
"serial",
|
||||||
"/dev/serial/by-id/<your-mcu-id>",
|
"/dev/serial/by-id/<your-mcu-id>",
|
||||||
),
|
),
|
||||||
("parameter_temperature_(°C): 155", "parameter_temperature_(°C)", "155"),
|
|
||||||
("parameter_humidity_(%_RH): 45", "parameter_humidity_(%_RH)", "45"),
|
|
||||||
("parameter_spool_weight_(%): 10", "parameter_spool_weight_(%)", "10"),
|
|
||||||
("path: /dev/shm/drying_box.json", "path", "/dev/shm/drying_box.json"),
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ def parser():
|
|||||||
return SimpleConfigParser()
|
return SimpleConfigParser()
|
||||||
|
|
||||||
|
|
||||||
class TestSingleLineParsing:
|
class TestLineParsing:
|
||||||
@pytest.mark.parametrize("given, expected", [*case_parse_section])
|
@pytest.mark.parametrize("given, expected", [*case_parse_section])
|
||||||
def test_parse_section(self, parser, given, expected):
|
def test_parse_section(self, parser, given, expected):
|
||||||
parser._parse_section(given)
|
parser._parse_section(given)
|
||||||
@@ -14,14 +14,4 @@ testcases = [
|
|||||||
("", False),
|
("", False),
|
||||||
("# that's a comment", False),
|
("# that's a comment", False),
|
||||||
("; that's a comment", False),
|
("; that's a comment", False),
|
||||||
("parameter_humidity_(%_RH):", True),
|
|
||||||
("parameter_spool_weight_(%):", True),
|
|
||||||
("parameter_temperature_(°C):", True),
|
|
||||||
("parameter_humidity_(%_RH): 18.123", False),
|
|
||||||
("parameter_spool_weight_(%): 150", False),
|
|
||||||
("parameter_temperature_(°C): 30,5", False),
|
|
||||||
("trusted_clients:", True),
|
|
||||||
("trusted_clients: 192.168.1.0/24", False),
|
|
||||||
("cors_domains:", True),
|
|
||||||
("cors_domains: http://*.lan", False),
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -26,6 +26,5 @@ testcases = [
|
|||||||
("description: homing!", True),
|
("description: homing!", True),
|
||||||
("description: inline macro :-)", True),
|
("description: inline macro :-)", True),
|
||||||
("path: %GCODES_DIR%", True),
|
("path: %GCODES_DIR%", True),
|
||||||
("path: /dev/shm/drying_box.json", True),
|
|
||||||
("serial = /dev/serial/by-id/<your-mcu-id>", True),
|
("serial = /dev/serial/by-id/<your-mcu-id>", True),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ StatusMap: Dict[StatusCode, StatusText] = {
|
|||||||
@dataclass
|
@dataclass
|
||||||
class ComponentStatus:
|
class ComponentStatus:
|
||||||
status: StatusCode
|
status: StatusCode
|
||||||
owner: str | None = None
|
|
||||||
repo: str | None = None
|
repo: str | None = None
|
||||||
local: str | None = None
|
local: str | None = None
|
||||||
remote: str | None = None
|
remote: str | None = None
|
||||||
|
|||||||
@@ -38,7 +38,9 @@ class ExtensionsMenu(BaseMenu):
|
|||||||
|
|
||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
self.options = {
|
self.options = {
|
||||||
i: Option(self.extension_submenu, opt_data=self.extensions.get(i))
|
i: Option(
|
||||||
|
self.extension_submenu, menu=True, opt_data=self.extensions.get(i)
|
||||||
|
)
|
||||||
for i in self.extensions
|
for i in self.extensions
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,12 +119,12 @@ class ExtensionSubmenu(BaseMenu):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
self.options["1"] = Option(self.extension.install_extension)
|
self.options["1"] = Option(self.extension.install_extension, menu=False)
|
||||||
if self.extension.metadata.get("updates"):
|
if self.extension.metadata.get("updates"):
|
||||||
self.options["2"] = Option(self.extension.update_extension)
|
self.options["2"] = Option(self.extension.update_extension, menu=False)
|
||||||
self.options["3"] = Option(self.extension.remove_extension)
|
self.options["3"] = Option(self.extension.remove_extension, menu=False)
|
||||||
else:
|
else:
|
||||||
self.options["2"] = Option(self.extension.remove_extension)
|
self.options["2"] = Option(self.extension.remove_extension, menu=False)
|
||||||
|
|
||||||
def print_menu(self) -> None:
|
def print_menu(self) -> None:
|
||||||
header = f" [ {self.extension.metadata.get('display_name')} ] "
|
header = f" [ {self.extension.metadata.get('display_name')} ] "
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ import csv
|
|||||||
import shutil
|
import shutil
|
||||||
import textwrap
|
import textwrap
|
||||||
import urllib.request
|
import urllib.request
|
||||||
from dataclasses import dataclass
|
from typing import List, Type, TypedDict, Union
|
||||||
from typing import Any, Dict, List, Type, Union
|
|
||||||
|
|
||||||
from components.klipper.klipper import Klipper
|
from components.klipper.klipper import Klipper
|
||||||
from components.klipper.klipper_dialogs import (
|
from components.klipper.klipper_dialogs import (
|
||||||
@@ -32,8 +31,7 @@ from utils.input_utils import get_selection_input
|
|||||||
from utils.instance_utils import get_instances
|
from utils.instance_utils import get_instances
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
class ThemeData(TypedDict):
|
||||||
class ThemeData:
|
|
||||||
name: str
|
name: str
|
||||||
short_note: str
|
short_note: str
|
||||||
author: str
|
author: str
|
||||||
@@ -93,7 +91,7 @@ class MainsailThemeInstallMenu(BaseMenu):
|
|||||||
|
|
||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
self.options = {
|
self.options = {
|
||||||
f"{index}": Option(self.install_theme, opt_index=f"{index}")
|
f"{index}": Option(self.install_theme, False, opt_index=f"{index}")
|
||||||
for index in range(len(self.themes))
|
for index in range(len(self.themes))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,45 +102,36 @@ class MainsailThemeInstallMenu(BaseMenu):
|
|||||||
count = 62 - len(color) - len(RESET_FORMAT)
|
count = 62 - len(color) - len(RESET_FORMAT)
|
||||||
menu = textwrap.dedent(
|
menu = textwrap.dedent(
|
||||||
f"""
|
f"""
|
||||||
╔═══════════════════════════════════════════════════════╗
|
/=======================================================\\
|
||||||
║ {color}{header:~^{count}}{RESET_FORMAT} ║
|
| {color}{header:~^{count}}{RESET_FORMAT} |
|
||||||
╟───────────────────────────────────────────────────────╢
|
|-------------------------------------------------------|
|
||||||
║ {line1:<62} ║
|
| {line1:<62} |
|
||||||
║ https://docs.mainsail.xyz/theming/themes ║
|
| https://docs.mainsail.xyz/theming/themes |
|
||||||
╟───────────────────────────────────────────────────────╢
|
|-------------------------------------------------------|
|
||||||
"""
|
"""
|
||||||
)[1:]
|
)[1:]
|
||||||
for i, theme in enumerate(self.themes):
|
for i, theme in enumerate(self.themes):
|
||||||
j: str = f" {i}" if i < 10 else f"{i}"
|
i = f" {i}" if i < 10 else f"{i}"
|
||||||
row: str = f"{j}) [{theme.name}]"
|
row = f"{i}) [{theme.get('name')}]"
|
||||||
menu += f"║ {row:<53} ║\n"
|
menu += f"| {row:<53} |\n"
|
||||||
print(menu, end="")
|
print(menu, end="")
|
||||||
|
|
||||||
def load_themes(self) -> List[ThemeData]:
|
def load_themes(self) -> List[ThemeData]:
|
||||||
with urllib.request.urlopen(self.THEMES_URL) as response:
|
with urllib.request.urlopen(self.THEMES_URL) as response:
|
||||||
themes: List[ThemeData] = []
|
themes: List[ThemeData] = []
|
||||||
content: str = response.read().decode()
|
csv_data: str = response.read().decode().splitlines()
|
||||||
csv_data: List[str] = content.splitlines()
|
csv_reader = csv.DictReader(csv_data, delimiter=",")
|
||||||
fieldnames = ["name", "short_note", "author", "repo"]
|
|
||||||
csv_reader = csv.DictReader(csv_data, fieldnames=fieldnames, delimiter=",")
|
|
||||||
next(csv_reader) # skip the header of the csv file
|
|
||||||
for row in csv_reader:
|
for row in csv_reader:
|
||||||
row: Dict[str, str] # type: ignore
|
row: ThemeData = row
|
||||||
theme: ThemeData = ThemeData(**row)
|
themes.append(row)
|
||||||
themes.append(theme)
|
|
||||||
|
|
||||||
return themes
|
return themes
|
||||||
|
|
||||||
def install_theme(self, **kwargs: Any):
|
def install_theme(self, **kwargs):
|
||||||
opt_index: str | None = kwargs.get("opt_index", None)
|
index = int(kwargs.get("opt_index"))
|
||||||
|
|
||||||
if not opt_index:
|
|
||||||
raise ValueError("No option index provided")
|
|
||||||
|
|
||||||
index: int = int(opt_index)
|
|
||||||
theme_data: ThemeData = self.themes[index]
|
theme_data: ThemeData = self.themes[index]
|
||||||
theme_author: str = theme_data.author
|
theme_author: str = theme_data.get("author")
|
||||||
theme_repo: str = theme_data.repo
|
theme_repo: str = theme_data.get("repo")
|
||||||
theme_repo_url: str = f"https://github.com/{theme_author}/{theme_repo}"
|
theme_repo_url: str = f"https://github.com/{theme_author}/{theme_repo}"
|
||||||
|
|
||||||
print_instance_overview(
|
print_instance_overview(
|
||||||
@@ -160,9 +149,9 @@ class MainsailThemeInstallMenu(BaseMenu):
|
|||||||
for printer in printer_list:
|
for printer in printer_list:
|
||||||
git_clone_wrapper(theme_repo_url, printer.cfg_dir.joinpath(".theme"))
|
git_clone_wrapper(theme_repo_url, printer.cfg_dir.joinpath(".theme"))
|
||||||
|
|
||||||
if len(theme_data.short_note) > 1:
|
if len(theme_data.get("short_note", "")) > 1:
|
||||||
Logger.print_warn("Info from the creator:", prefix=False, start="\n")
|
Logger.print_warn("Info from the creator:", prefix=False, start="\n")
|
||||||
Logger.print_info(theme_data.short_note, prefix=False, end="\n\n")
|
Logger.print_info(theme_data.get("short_note"), prefix=False, end="\n\n")
|
||||||
|
|
||||||
|
|
||||||
def get_printer_selection(
|
def get_printer_selection(
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ class TelegramBotExtension(BaseExtension):
|
|||||||
"Moonraker Telegram Bot requires Moonraker to be installed. "
|
"Moonraker Telegram Bot requires Moonraker to be installed. "
|
||||||
"Please install Moonraker first!",
|
"Please install Moonraker first!",
|
||||||
],
|
],
|
||||||
|
padding_top=0,
|
||||||
|
padding_bottom=0,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -63,6 +65,8 @@ class TelegramBotExtension(BaseExtension):
|
|||||||
"\n\n",
|
"\n\n",
|
||||||
"The setup will apply the same names to Telegram Bot!",
|
"The setup will apply the same names to Telegram Bot!",
|
||||||
],
|
],
|
||||||
|
padding_top=0,
|
||||||
|
padding_bottom=0,
|
||||||
)
|
)
|
||||||
if not get_confirm(
|
if not get_confirm(
|
||||||
"Continue Moonraker Telegram Bot installation?",
|
"Continue Moonraker Telegram Bot installation?",
|
||||||
@@ -84,6 +88,8 @@ class TelegramBotExtension(BaseExtension):
|
|||||||
instance = MoonrakerTelegramBot(suffix=name)
|
instance = MoonrakerTelegramBot(suffix=name)
|
||||||
instance.create()
|
instance.create()
|
||||||
|
|
||||||
|
print(instance)
|
||||||
|
|
||||||
cmd_sysctl_service(instance.service_file_path.name, "enable")
|
cmd_sysctl_service(instance.service_file_path.name, "enable")
|
||||||
|
|
||||||
if create_example_cfg:
|
if create_example_cfg:
|
||||||
@@ -120,7 +126,6 @@ class TelegramBotExtension(BaseExtension):
|
|||||||
"following wiki page for further information:",
|
"following wiki page for further information:",
|
||||||
"https://github.com/nlef/moonraker-telegram-bot/wiki",
|
"https://github.com/nlef/moonraker-telegram-bot/wiki",
|
||||||
],
|
],
|
||||||
margin_bottom=1,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Logger.print_ok("Telegram Bot installation complete!")
|
Logger.print_ok("Telegram Bot installation complete!")
|
||||||
@@ -175,7 +180,7 @@ class TelegramBotExtension(BaseExtension):
|
|||||||
options=[
|
options=[
|
||||||
("type", "git_repo"),
|
("type", "git_repo"),
|
||||||
("path", str(TG_BOT_DIR)),
|
("path", str(TG_BOT_DIR)),
|
||||||
("origin", TG_BOT_REPO),
|
("orgin", TG_BOT_REPO),
|
||||||
("env", env_py),
|
("env", env_py),
|
||||||
("requirements", "scripts/requirements.txt"),
|
("requirements", "scripts/requirements.txt"),
|
||||||
("install_script", "scripts/install.sh"),
|
("install_script", "scripts/install.sh"),
|
||||||
|
|||||||
@@ -1,154 +0,0 @@
|
|||||||
# ======================================================================= #
|
|
||||||
# Copyright (C) 2020 - 2024 Dominik Willner <th33xitus@gmail.com> #
|
|
||||||
# #
|
|
||||||
# This file is part of KIAUH - Klipper Installation And Update Helper #
|
|
||||||
# https://github.com/dw-0/kiauh #
|
|
||||||
# #
|
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license #
|
|
||||||
# ======================================================================= #
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import shutil
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Literal
|
|
||||||
|
|
||||||
from components.klipper import (
|
|
||||||
KLIPPER_BACKUP_DIR,
|
|
||||||
KLIPPER_DIR,
|
|
||||||
KLIPPER_ENV_DIR,
|
|
||||||
KLIPPER_REQ_FILE,
|
|
||||||
)
|
|
||||||
from components.klipper.klipper import Klipper
|
|
||||||
from components.klipper.klipper_setup import install_klipper_packages
|
|
||||||
from components.moonraker import (
|
|
||||||
MOONRAKER_BACKUP_DIR,
|
|
||||||
MOONRAKER_DIR,
|
|
||||||
MOONRAKER_ENV_DIR,
|
|
||||||
MOONRAKER_REQ_FILE,
|
|
||||||
)
|
|
||||||
from components.moonraker.moonraker import Moonraker
|
|
||||||
from components.moonraker.moonraker_setup import install_moonraker_packages
|
|
||||||
from core.backup_manager.backup_manager import BackupManager, BackupManagerException
|
|
||||||
from core.instance_manager.instance_manager import InstanceManager
|
|
||||||
from core.logger import Logger
|
|
||||||
from core.settings.kiauh_settings import RepoSettings
|
|
||||||
from utils.git_utils import GitException, get_repo_name, git_clone_wrapper
|
|
||||||
from utils.instance_utils import get_instances
|
|
||||||
from utils.sys_utils import (
|
|
||||||
VenvCreationFailedException,
|
|
||||||
create_python_venv,
|
|
||||||
install_python_requirements,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class RepoSwitchFailedException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def run_switch_repo_routine(
|
|
||||||
name: Literal["klipper", "moonraker"], repo_settings: RepoSettings
|
|
||||||
) -> None:
|
|
||||||
repo_dir: Path = KLIPPER_DIR if name == "klipper" else MOONRAKER_DIR
|
|
||||||
env_dir: Path = KLIPPER_ENV_DIR if name == "klipper" else MOONRAKER_ENV_DIR
|
|
||||||
req_file = KLIPPER_REQ_FILE if name == "klipper" else MOONRAKER_REQ_FILE
|
|
||||||
backup_dir: Path = KLIPPER_BACKUP_DIR if name == "klipper" else MOONRAKER_BACKUP_DIR
|
|
||||||
_type = Klipper if name == "klipper" else Moonraker
|
|
||||||
|
|
||||||
# step 1: stop all instances
|
|
||||||
Logger.print_status(f"Stopping all {_type.__name__} instances ...")
|
|
||||||
instances = get_instances(_type)
|
|
||||||
InstanceManager.stop_all(instances)
|
|
||||||
|
|
||||||
repo_dir_backup_path: Path | None = None
|
|
||||||
env_dir_backup_path: Path | None = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
# step 2: backup old repo and env
|
|
||||||
org, repo = get_repo_name(repo_dir)
|
|
||||||
backup_dir = backup_dir.joinpath(org)
|
|
||||||
bm = BackupManager()
|
|
||||||
repo_dir_backup_path = bm.backup_directory(
|
|
||||||
repo_dir.name,
|
|
||||||
repo_dir,
|
|
||||||
backup_dir,
|
|
||||||
)
|
|
||||||
env_dir_backup_path = bm.backup_directory(
|
|
||||||
env_dir.name,
|
|
||||||
env_dir,
|
|
||||||
backup_dir,
|
|
||||||
)
|
|
||||||
|
|
||||||
# step 3: read repo url and branch from settings
|
|
||||||
repo_url = repo_settings.repo_url
|
|
||||||
branch = repo_settings.branch
|
|
||||||
|
|
||||||
if not (repo_url or branch):
|
|
||||||
error = f"Invalid repository URL ({repo_url}) or branch ({branch})!"
|
|
||||||
raise ValueError(error)
|
|
||||||
|
|
||||||
# step 4: clone new repo
|
|
||||||
git_clone_wrapper(repo_url, repo_dir, branch, force=True)
|
|
||||||
|
|
||||||
# step 5: install os dependencies
|
|
||||||
if name == "klipper":
|
|
||||||
install_klipper_packages()
|
|
||||||
elif name == "moonraker":
|
|
||||||
install_moonraker_packages()
|
|
||||||
|
|
||||||
# step 6: recreate python virtualenv
|
|
||||||
Logger.print_status(f"Recreating {_type.__name__} virtualenv ...")
|
|
||||||
if not create_python_venv(env_dir, force=True):
|
|
||||||
raise GitException(f"Failed to recreate virtualenv for {_type.__name__}")
|
|
||||||
else:
|
|
||||||
install_python_requirements(env_dir, req_file)
|
|
||||||
|
|
||||||
Logger.print_ok(f"Switched to {repo_url} at branch {branch}!")
|
|
||||||
|
|
||||||
except BackupManagerException as e:
|
|
||||||
Logger.print_error(f"Error during backup of repository: {e}")
|
|
||||||
raise RepoSwitchFailedException(e)
|
|
||||||
|
|
||||||
except (GitException, VenvCreationFailedException) as e:
|
|
||||||
# if something goes wrong during cloning or recreating the virtualenv,
|
|
||||||
# we restore the backup of the repo and env
|
|
||||||
Logger.print_error(f"Error during repository switch: {e}", start="\n")
|
|
||||||
Logger.print_status(f"Restoring last backup of {_type.__name__} ...")
|
|
||||||
_restore_repo_backup(
|
|
||||||
_type.__name__,
|
|
||||||
env_dir,
|
|
||||||
env_dir_backup_path,
|
|
||||||
repo_dir,
|
|
||||||
repo_dir_backup_path,
|
|
||||||
)
|
|
||||||
|
|
||||||
except RepoSwitchFailedException as e:
|
|
||||||
Logger.print_error(f"Something went wrong: {e}")
|
|
||||||
return
|
|
||||||
|
|
||||||
Logger.print_status(f"Restarting all {_type.__name__} instances ...")
|
|
||||||
InstanceManager.start_all(instances)
|
|
||||||
|
|
||||||
|
|
||||||
def _restore_repo_backup(
|
|
||||||
name: str,
|
|
||||||
env_dir: Path,
|
|
||||||
env_dir_backup_path: Path | None,
|
|
||||||
repo_dir: Path,
|
|
||||||
repo_dir_backup_path: Path | None,
|
|
||||||
) -> None:
|
|
||||||
# if repo_dir_backup_path is not None and env_dir_backup_path is not None:
|
|
||||||
if not repo_dir_backup_path or not env_dir_backup_path:
|
|
||||||
raise RepoSwitchFailedException(
|
|
||||||
f"Unable to restore backup of {name}! Path of backups directory is None!"
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if repo_dir.exists():
|
|
||||||
shutil.rmtree(repo_dir)
|
|
||||||
shutil.copytree(repo_dir_backup_path, repo_dir)
|
|
||||||
if env_dir.exists():
|
|
||||||
shutil.rmtree(env_dir)
|
|
||||||
shutil.copytree(env_dir_backup_path, env_dir)
|
|
||||||
Logger.print_warn(f"Restored backup of {name} successfully!")
|
|
||||||
except Exception as e:
|
|
||||||
raise RepoSwitchFailedException(f"Error restoring backup: {e}")
|
|
||||||
@@ -35,6 +35,8 @@ def change_system_hostname() -> None:
|
|||||||
"browser.",
|
"browser.",
|
||||||
],
|
],
|
||||||
custom_title="CHANGE SYSTEM HOSTNAME",
|
custom_title="CHANGE SYSTEM HOSTNAME",
|
||||||
|
padding_top=0,
|
||||||
|
padding_bottom=0,
|
||||||
)
|
)
|
||||||
if not get_confirm("Do you want to change the hostname?", default_choice=False):
|
if not get_confirm("Do you want to change the hostname?", default_choice=False):
|
||||||
return
|
return
|
||||||
@@ -48,6 +50,8 @@ def change_system_hostname() -> None:
|
|||||||
"● Any special characters",
|
"● Any special characters",
|
||||||
"● No leading or trailing '-'",
|
"● No leading or trailing '-'",
|
||||||
],
|
],
|
||||||
|
padding_top=0,
|
||||||
|
padding_bottom=0,
|
||||||
)
|
)
|
||||||
hostname = get_string_input(
|
hostname = get_string_input(
|
||||||
"Enter the new hostname",
|
"Enter the new hostname",
|
||||||
|
|||||||
@@ -22,12 +22,7 @@ from core.constants import (
|
|||||||
)
|
)
|
||||||
from core.logger import DialogType, Logger
|
from core.logger import DialogType, Logger
|
||||||
from core.types import ComponentStatus, StatusCode
|
from core.types import ComponentStatus, StatusCode
|
||||||
from utils.git_utils import (
|
from utils.git_utils import get_local_commit, get_remote_commit, get_repo_name
|
||||||
get_local_commit,
|
|
||||||
get_local_tags,
|
|
||||||
get_remote_commit,
|
|
||||||
get_repo_name,
|
|
||||||
)
|
|
||||||
from utils.instance_utils import get_instances
|
from utils.instance_utils import get_instances
|
||||||
from utils.sys_utils import (
|
from utils.sys_utils import (
|
||||||
check_package_install,
|
check_package_install,
|
||||||
@@ -36,14 +31,6 @@ from utils.sys_utils import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_kiauh_version() -> str:
|
|
||||||
"""
|
|
||||||
Helper method to get the current KIAUH version by reading the latest tag
|
|
||||||
:return: string of the latest tag
|
|
||||||
"""
|
|
||||||
return get_local_tags(Path(__file__).parent.parent)[-1]
|
|
||||||
|
|
||||||
|
|
||||||
def convert_camelcase_to_kebabcase(name: str) -> str:
|
def convert_camelcase_to_kebabcase(name: str) -> str:
|
||||||
return re.sub(r"(?<!^)(?=[A-Z])", "-", name).lower()
|
return re.sub(r"(?<!^)(?=[A-Z])", "-", name).lower()
|
||||||
|
|
||||||
@@ -124,12 +111,10 @@ def get_install_status(
|
|||||||
else:
|
else:
|
||||||
status = 1 # incomplete
|
status = 1 # incomplete
|
||||||
|
|
||||||
org, repo = get_repo_name(repo_dir)
|
|
||||||
return ComponentStatus(
|
return ComponentStatus(
|
||||||
status=status,
|
status=status,
|
||||||
instances=instances,
|
instances=instances,
|
||||||
owner=org,
|
repo=get_repo_name(repo_dir),
|
||||||
repo=repo,
|
|
||||||
local=get_local_commit(repo_dir),
|
local=get_local_commit(repo_dir),
|
||||||
remote=get_remote_commit(repo_dir),
|
remote=get_remote_commit(repo_dir),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -16,10 +16,6 @@ from utils.input_utils import get_confirm, get_number_input
|
|||||||
from utils.instance_utils import get_instances
|
from utils.instance_utils import get_instances
|
||||||
|
|
||||||
|
|
||||||
class GitException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def git_clone_wrapper(
|
def git_clone_wrapper(
|
||||||
repo: str, target_dir: Path, branch: str | None = None, force: bool = False
|
repo: str, target_dir: Path, branch: str | None = None, force: bool = False
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -47,10 +43,10 @@ def git_clone_wrapper(
|
|||||||
except CalledProcessError:
|
except CalledProcessError:
|
||||||
log = "An unexpected error occured during cloning of the repository."
|
log = "An unexpected error occured during cloning of the repository."
|
||||||
Logger.print_error(log)
|
Logger.print_error(log)
|
||||||
raise GitException(log)
|
return
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
Logger.print_error(f"Error removing existing repository: {e.strerror}")
|
Logger.print_error(f"Error removing existing repository: {e.strerror}")
|
||||||
raise GitException(f"Error removing existing repository: {e.strerror}")
|
return
|
||||||
|
|
||||||
|
|
||||||
def git_pull_wrapper(repo: str, target_dir: Path) -> None:
|
def git_pull_wrapper(repo: str, target_dir: Path) -> None:
|
||||||
@@ -70,58 +66,25 @@ def git_pull_wrapper(repo: str, target_dir: Path) -> None:
|
|||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def get_repo_name(repo: Path) -> tuple[str, str] | None:
|
def get_repo_name(repo: Path) -> str | None:
|
||||||
"""
|
"""
|
||||||
Helper method to extract the organisation and name of a repository |
|
Helper method to extract the organisation and name of a repository |
|
||||||
:param repo: repository to extract the values from
|
:param repo: repository to extract the values from
|
||||||
:return: String in form of "<orga>/<name>" or None
|
:return: String in form of "<orga>/<name>" or None
|
||||||
"""
|
"""
|
||||||
if not repo.exists() or not repo.joinpath(".git").exists():
|
if not repo.exists() or not repo.joinpath(".git").exists():
|
||||||
return "-", "-"
|
return "-"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cmd = ["git", "-C", repo.as_posix(), "config", "--get", "remote.origin.url"]
|
cmd = ["git", "-C", repo.as_posix(), "config", "--get", "remote.origin.url"]
|
||||||
result: str = check_output(cmd, stderr=DEVNULL).decode(encoding="utf-8")
|
result: str = check_output(cmd, stderr=DEVNULL).decode(encoding="utf-8")
|
||||||
substrings: List[str] = result.strip().split("/")[-2:]
|
substrings: List[str] = result.strip().split("/")[-2:]
|
||||||
return substrings[0], substrings[1]
|
return "/".join(substrings).replace(".git", "")
|
||||||
|
|
||||||
# return "/".join(substrings).replace(".git", "")
|
|
||||||
except CalledProcessError:
|
except CalledProcessError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_local_tags(repo_path: Path, _filter: str | None = None) -> List[str]:
|
def get_tags(repo_path: str) -> List[str]:
|
||||||
"""
|
|
||||||
Get all tags of a local Git repository
|
|
||||||
:param repo_path: Path to the local Git repository
|
|
||||||
:param _filter: Optional filter to filter the tags by
|
|
||||||
:return: List of tags
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
cmd = ["git", "tag", "-l"]
|
|
||||||
|
|
||||||
if _filter is not None:
|
|
||||||
cmd.append(f"'${_filter}'")
|
|
||||||
|
|
||||||
result: str = check_output(
|
|
||||||
cmd,
|
|
||||||
stderr=DEVNULL,
|
|
||||||
cwd=repo_path.as_posix(),
|
|
||||||
).decode(encoding="utf-8")
|
|
||||||
|
|
||||||
tags = result.split("\n")
|
|
||||||
return tags[:-1]
|
|
||||||
|
|
||||||
except CalledProcessError:
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
def get_remote_tags(repo_path: str) -> List[str]:
|
|
||||||
"""
|
|
||||||
Gets the tags of a GitHub repostiory
|
|
||||||
:param repo_path: path of the GitHub repository - e.g. `<owner>/<name>`
|
|
||||||
:return: List of tags
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
url = f"https://api.github.com/repos/{repo_path}/tags"
|
url = f"https://api.github.com/repos/{repo_path}/tags"
|
||||||
with urllib.request.urlopen(url) as r:
|
with urllib.request.urlopen(url) as r:
|
||||||
@@ -139,14 +102,14 @@ def get_remote_tags(repo_path: str) -> List[str]:
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def get_latest_remote_tag(repo_path: str) -> str:
|
def get_latest_tag(repo_path: str) -> str:
|
||||||
"""
|
"""
|
||||||
Gets the latest stable tag of a GitHub repostiory
|
Gets the latest stable tag of a GitHub repostiory
|
||||||
:param repo_path: path of the GitHub repository - e.g. `<owner>/<name>`
|
:param repo_path: path of the GitHub repository - e.g. `<owner>/<name>`
|
||||||
:return: tag or empty string
|
:return: tag or empty string
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if len(latest_tag := get_remote_tags(repo_path)) > 0:
|
if len(latest_tag := get_tags(repo_path)) > 0:
|
||||||
return latest_tag[0]
|
return latest_tag[0]
|
||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
@@ -161,10 +124,7 @@ def get_latest_unstable_tag(repo_path: str) -> str:
|
|||||||
:return: tag or empty string
|
:return: tag or empty string
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if (
|
if len(unstable_tags := [t for t in get_tags(repo_path) if "-" in t]) > 0:
|
||||||
len(unstable_tags := [t for t in get_remote_tags(repo_path) if "-" in t])
|
|
||||||
> 0
|
|
||||||
):
|
|
||||||
return unstable_tags[0]
|
return unstable_tags[0]
|
||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
@@ -173,34 +133,6 @@ def get_latest_unstable_tag(repo_path: str) -> str:
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def compare_semver_tags(tag1: str, tag2: str) -> bool:
|
|
||||||
"""
|
|
||||||
Compare two semver version strings.
|
|
||||||
Does not support comparing pre-release versions (e.g. 1.0.0-rc.1, 1.0.0-beta.1)
|
|
||||||
:param tag1: First version string
|
|
||||||
:param tag2: Second version string
|
|
||||||
:return: True if tag1 is greater than tag2, False otherwise
|
|
||||||
"""
|
|
||||||
if tag1 == tag2:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def parse_version(v):
|
|
||||||
return list(map(int, v[1:].split(".")))
|
|
||||||
|
|
||||||
tag1_parts = parse_version(tag1)
|
|
||||||
tag2_parts = parse_version(tag2)
|
|
||||||
|
|
||||||
max_len = max(len(tag1_parts), len(tag2_parts))
|
|
||||||
tag1_parts += [0] * (max_len - len(tag1_parts))
|
|
||||||
tag2_parts += [0] * (max_len - len(tag2_parts))
|
|
||||||
|
|
||||||
for part1, part2 in zip(tag1_parts, tag2_parts):
|
|
||||||
if part1 != part2:
|
|
||||||
return part1 > part2
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def get_local_commit(repo: Path) -> str | None:
|
def get_local_commit(repo: Path) -> str | None:
|
||||||
if not repo.exists() or not repo.joinpath(".git").exists():
|
if not repo.exists() or not repo.joinpath(".git").exists():
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ def get_selection_input(question: str, option_list: List | Dict, default=None) -
|
|||||||
else:
|
else:
|
||||||
raise ValueError("Invalid option_list type")
|
raise ValueError("Invalid option_list type")
|
||||||
|
|
||||||
Logger.print_error("Invalid option! Please select a valid option.", False)
|
Logger.print_error(INVALID_CHOICE)
|
||||||
|
|
||||||
|
|
||||||
def format_question(question: str, default=None) -> str:
|
def format_question(question: str, default=None) -> str:
|
||||||
|
|||||||
@@ -39,10 +39,6 @@ SysCtlServiceAction = Literal[
|
|||||||
SysCtlManageAction = Literal["daemon-reload", "reset-failed"]
|
SysCtlManageAction = Literal["daemon-reload", "reset-failed"]
|
||||||
|
|
||||||
|
|
||||||
class VenvCreationFailedException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def kill(opt_err_msg: str = "") -> None:
|
def kill(opt_err_msg: str = "") -> None:
|
||||||
"""
|
"""
|
||||||
Kills the application |
|
Kills the application |
|
||||||
@@ -91,12 +87,11 @@ def parse_packages_from_file(source_file: Path) -> List[str]:
|
|||||||
return packages
|
return packages
|
||||||
|
|
||||||
|
|
||||||
def create_python_venv(target: Path, force: bool = False) -> bool:
|
def create_python_venv(target: Path) -> bool:
|
||||||
"""
|
"""
|
||||||
Create a python 3 virtualenv at the provided target destination.
|
Create a python 3 virtualenv at the provided target destination.
|
||||||
Returns True if the virtualenv was created successfully.
|
Returns True if the virtualenv was created successfully.
|
||||||
Returns False if the virtualenv already exists, recreation was declined or creation failed.
|
Returns False if the virtualenv already exists, recreation was declined or creation failed.
|
||||||
:param force: Force recreation of the virtualenv
|
|
||||||
:param target: Path where to create the virtualenv at
|
:param target: Path where to create the virtualenv at
|
||||||
:return: bool
|
:return: bool
|
||||||
"""
|
"""
|
||||||
@@ -111,7 +106,7 @@ def create_python_venv(target: Path, force: bool = False) -> bool:
|
|||||||
Logger.print_error(f"Error setting up virtualenv:\n{e}")
|
Logger.print_error(f"Error setting up virtualenv:\n{e}")
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
if not force and not get_confirm(
|
if not get_confirm(
|
||||||
"Virtualenv already exists. Re-create?", default_choice=False
|
"Virtualenv already exists. Re-create?", default_choice=False
|
||||||
):
|
):
|
||||||
Logger.print_info("Skipping re-creation of virtualenv ...")
|
Logger.print_info("Skipping re-creation of virtualenv ...")
|
||||||
@@ -179,14 +174,14 @@ def install_python_requirements(target: Path, requirements: Path) -> None:
|
|||||||
|
|
||||||
if result.returncode != 0 or result.stderr:
|
if result.returncode != 0 or result.stderr:
|
||||||
Logger.print_error(f"{result.stderr}", False)
|
Logger.print_error(f"{result.stderr}", False)
|
||||||
raise VenvCreationFailedException("Installing Python requirements failed!")
|
Logger.print_error("Installing Python requirements failed!")
|
||||||
|
return
|
||||||
|
|
||||||
Logger.print_ok("Installing Python requirements successful!")
|
Logger.print_ok("Installing Python requirements successful!")
|
||||||
|
except CalledProcessError as e:
|
||||||
except Exception as e:
|
log = f"Error installing Python requirements:\n{e.output.decode()}"
|
||||||
log = f"Error installing Python requirements: {e}"
|
|
||||||
Logger.print_error(log)
|
Logger.print_error(log)
|
||||||
raise VenvCreationFailedException(log)
|
raise
|
||||||
|
|
||||||
|
|
||||||
def update_system_package_lists(silent: bool, rls_info_change=False) -> None:
|
def update_system_package_lists(silent: bool, rls_info_change=False) -> None:
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ function main_ui() {
|
|||||||
function get_kiauh_version() {
|
function get_kiauh_version() {
|
||||||
local version
|
local version
|
||||||
cd "${KIAUH_SRCDIR}"
|
cd "${KIAUH_SRCDIR}"
|
||||||
version="$(git tag -l 'v5*' | tail -1)"
|
version="$(git describe HEAD --always --tags | cut -d "-" -f 1,2)"
|
||||||
echo "${version}"
|
echo "${version}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user