refactor: full refactor of how webclient data is handled

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
This commit is contained in:
dw-0
2024-04-06 15:09:33 +02:00
parent 1484ebf445
commit 06801a47eb
20 changed files with 543 additions and 369 deletions

View File

@@ -30,7 +30,7 @@ from components.klipper.klipper_dialogs import (
) )
from components.moonraker.moonraker import Moonraker from components.moonraker.moonraker import Moonraker
from components.moonraker.moonraker_utils import moonraker_to_multi_conversion from components.moonraker.moonraker_utils import moonraker_to_multi_conversion
from components.webui_client import ClientData from components.webui_client.base_data import BaseWebClient
from components.webui_client.client_config.client_config_setup import ( from components.webui_client.client_config.client_config_setup import (
create_client_config_symlink, create_client_config_symlink,
) )
@@ -288,7 +288,7 @@ def get_highest_index(instance_list: List[Klipper]) -> int:
def create_example_printer_cfg( def create_example_printer_cfg(
instance: Klipper, clients: Optional[List[ClientData]] = None instance: Klipper, clients: Optional[List[BaseWebClient]] = None
) -> None: ) -> None:
Logger.print_status(f"Creating example printer.cfg in '{instance.cfg_dir}'") Logger.print_status(f"Creating example printer.cfg in '{instance.cfg_dir}'")
if instance.cfg_file.is_file(): if instance.cfg_file.is_file():
@@ -309,8 +309,8 @@ def create_example_printer_cfg(
# include existing client configs in the example config # include existing client configs in the example config
if clients is not None and len(clients) > 0: if clients is not None and len(clients) > 0:
for c in clients: for c in clients:
client_config = c.get("client_config") client_config = c.client_config
section = client_config.get("printer_cfg_section") section = client_config.config_section
cm.config.add_section(section=section) cm.config.add_section(section=section)
create_client_config_symlink(client_config, [instance]) create_client_config_symlink(client_config, [instance])

View File

@@ -11,11 +11,11 @@ import subprocess
import sys import sys
from pathlib import Path from pathlib import Path
from components.webui_client import MAINSAIL_DIR
from components.webui_client.client_utils import ( from components.webui_client.client_utils import (
enable_mainsail_remotemode, enable_mainsail_remotemode,
get_existing_clients, get_existing_clients,
) )
from components.webui_client.mainsail_data import MainsailData
from kiauh import KIAUH_CFG from kiauh import KIAUH_CFG
from components.klipper.klipper import Klipper from components.klipper.klipper import Klipper
from components.moonraker import ( from components.moonraker import (
@@ -113,7 +113,7 @@ def install_moonraker() -> None:
# if mainsail is installed, and we installed # if mainsail is installed, and we installed
# multiple moonraker instances, we enable mainsails remote mode # multiple moonraker instances, we enable mainsails remote mode
if MAINSAIL_DIR.exists() and len(mr_im.instances) > 1: if MainsailData().client_dir.exists() and len(mr_im.instances) > 1:
enable_mainsail_remotemode() enable_mainsail_remotemode()

View File

@@ -19,8 +19,9 @@ from components.moonraker import (
MOONRAKER_DB_BACKUP_DIR, MOONRAKER_DB_BACKUP_DIR,
) )
from components.moonraker.moonraker import Moonraker from components.moonraker.moonraker import Moonraker
from components.webui_client import MAINSAIL_DIR, ClientData from components.webui_client.base_data import BaseWebClient
from components.webui_client.client_utils import enable_mainsail_remotemode from components.webui_client.client_utils import enable_mainsail_remotemode
from components.webui_client.mainsail_data import MainsailData
from core.backup_manager.backup_manager import BackupManager from core.backup_manager.backup_manager import BackupManager
from core.config_manager.config_manager import ConfigManager from core.config_manager.config_manager import ConfigManager
from core.instance_manager.instance_manager import InstanceManager from core.instance_manager.instance_manager import InstanceManager
@@ -52,7 +53,7 @@ def get_moonraker_status() -> (
def create_example_moonraker_conf( def create_example_moonraker_conf(
instance: Moonraker, instance: Moonraker,
ports_map: Dict[str, int], ports_map: Dict[str, int],
clients: Optional[List[ClientData]] = None, clients: Optional[List[BaseWebClient]] = None,
) -> None: ) -> None:
Logger.print_status(f"Creating example moonraker.conf in '{instance.cfg_dir}'") Logger.print_status(f"Creating example moonraker.conf in '{instance.cfg_dir}'")
if instance.cfg_file.is_file(): if instance.cfg_file.is_file():
@@ -100,26 +101,26 @@ def create_example_moonraker_conf(
if clients is not None and len(clients) > 0: if clients is not None and len(clients) > 0:
for c in clients: for c in clients:
# client part # client part
c_section = f"update_manager {c.get('name')}" c_section = f"update_manager {c.name}"
c_options = [ c_options = [
("type", "web"), ("type", "web"),
("channel", "stable"), ("channel", "stable"),
("repo", c.get("mr_conf_repo")), ("repo", c.repo_path),
("path", c.get("mr_conf_path")), ("path", c.client_dir),
] ]
cm.config.add_section(section=c_section) cm.config.add_section(section=c_section)
for option in c_options: for option in c_options:
cm.config.set(c_section, option[0], option[1]) cm.config.set(c_section, option[0], option[1])
# client config part # client config part
c_config = c.get("client_config") c_config = c.client_config
if c_config.get("dir").exists(): if c_config.config_dir.exists():
c_config_section = f"update_manager {c_config.get('name')}" c_config_section = f"update_manager {c_config.name}"
c_config_options = [ c_config_options = [
("type", "git_repo"), ("type", "git_repo"),
("primary_branch", "master"), ("primary_branch", "master"),
("path", c_config.get("mr_conf_path")), ("path", c_config.config_dir),
("origin", c_config.get("mr_conf_origin")), ("origin", c_config.repo_url),
("managed_services", "klipper"), ("managed_services", "klipper"),
] ]
cm.config.add_section(section=c_config_section) cm.config.add_section(section=c_config_section)
@@ -177,7 +178,7 @@ def moonraker_to_multi_conversion(new_name: str) -> None:
im.start_instance() im.start_instance()
# if mainsail is installed, we enable mainsails remote mode # if mainsail is installed, we enable mainsails remote mode
if MAINSAIL_DIR.exists() and len(im.instances) > 1: if MainsailData().client_dir.exists() and len(im.instances) > 1:
enable_mainsail_remotemode() enable_mainsail_remotemode()

View File

@@ -1,75 +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 pathlib import Path
from typing import Literal, TypedDict
from core.backup_manager import BACKUP_ROOT_DIR
MODULE_PATH = Path(__file__).resolve().parent
###########
# MAINSAIL
###########
MAINSAIL_DIR = Path.home().joinpath("mainsail")
MAINSAIL_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("mainsail-backups")
MAINSAIL_CONFIG_DIR = Path.home().joinpath("mainsail-config")
MAINSAIL_CONFIG_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("mainsail-config-backups")
MAINSAIL_CONFIG_REPO_URL = "https://github.com/mainsail-crew/mainsail-config.git"
MAINSAIL_CONFIG_JSON = MAINSAIL_DIR.joinpath("config.json")
MAINSAIL_URL = (
"https://github.com/mainsail-crew/mainsail/releases/latest/download/mainsail.zip"
)
MAINSAIL_PRE_RLS_URL = (
"https://github.com/mainsail-crew/mainsail/releases/download/%TAG%/mainsail.zip"
)
MAINSAIL_TAGS_URL = "https://api.github.com/repos/mainsail-crew/mainsail/tags"
#########
# FLUIDD
#########
FLUIDD_DIR = Path.home().joinpath("fluidd")
FLUIDD_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("fluidd-backups")
FLUIDD_CONFIG_DIR = Path.home().joinpath("fluidd-config")
FLUIDD_CONFIG_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("fluidd-config-backups")
FLUIDD_CONFIG_REPO_URL = "https://github.com/fluidd-core/fluidd-config.git"
FLUIDD_URL = "https://github.com/fluidd-core/fluidd/releases/latest/download/fluidd.zip"
FLUIDD_PRE_RLS_URL = (
"https://github.com/fluidd-core/fluidd/releases/download/%TAG%/fluidd.zip"
)
FLUIDD_TAGS_URL = "https://api.github.com/repos/fluidd-core/fluidd/tags"
ClientName = Literal["mainsail", "fluidd"]
ClientConfigName = Literal["mainsail-config", "fluidd-config"]
class ClientData(TypedDict):
name: ClientName
display_name: str
dir: Path
backup_dir: Path
url: str
pre_release_url: str
tags_url: str
remote_mode: bool # required only for Mainsail
mr_conf_repo: str
mr_conf_path: str
client_config: "ClientConfigData"
class ClientConfigData(TypedDict):
name: ClientConfigName
display_name: str
cfg_filename: str
dir: Path
backup_dir: Path
url: str
printer_cfg_section: str
mr_conf_path: str
mr_conf_origin: str

View File

@@ -0,0 +1,117 @@
# ======================================================================= #
# 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
from abc import ABC, abstractmethod
from enum import Enum
from pathlib import Path
class WebClientType(Enum):
MAINSAIL: str = "mainsail"
FLUIDD: str = "fluidd"
class WebClientConfigType(Enum):
MAINSAIL: str = "mainsail-config"
FLUIDD: str = "fluidd-config"
class BaseWebClient(ABC):
"""Base class for webclient data"""
@property
@abstractmethod
def client(self) -> WebClientType:
raise NotImplementedError
@property
@abstractmethod
def name(self) -> str:
raise NotImplementedError
@property
@abstractmethod
def display_name(self) -> str:
raise NotImplementedError
@property
@abstractmethod
def client_dir(self) -> Path:
raise NotImplementedError
@property
@abstractmethod
def backup_dir(self) -> Path:
raise NotImplementedError
@property
@abstractmethod
def repo_path(self) -> str:
raise NotImplementedError
@property
@abstractmethod
def stable_url(self) -> str:
raise NotImplementedError
@property
@abstractmethod
def unstable_url(self) -> str:
raise NotImplementedError
@property
@abstractmethod
def client_config(self) -> BaseWebClientConfig:
raise NotImplementedError
class BaseWebClientConfig(ABC):
"""Base class for webclient config data"""
@property
@abstractmethod
def client_config(self) -> WebClientConfigType:
raise NotImplementedError
@property
@abstractmethod
def name(self) -> str:
raise NotImplementedError
@property
@abstractmethod
def display_name(self) -> str:
raise NotImplementedError
@property
@abstractmethod
def config_filename(self) -> str:
raise NotImplementedError
@property
@abstractmethod
def config_dir(self) -> Path:
raise NotImplementedError
@property
@abstractmethod
def backup_dir(self) -> Path:
raise NotImplementedError
@property
@abstractmethod
def repo_url(self) -> str:
raise NotImplementedError
@property
@abstractmethod
def config_section(self) -> str:
raise NotImplementedError

View File

@@ -14,26 +14,26 @@ from typing import List
from components.klipper.klipper import Klipper from components.klipper.klipper import Klipper
from components.moonraker.moonraker import Moonraker from components.moonraker.moonraker import Moonraker
from components.webui_client import ClientConfigData from components.webui_client.base_data import BaseWebClientConfig
from core.instance_manager.instance_manager import InstanceManager from core.instance_manager.instance_manager import InstanceManager
from utils.filesystem_utils import remove_file, remove_config_section from utils.filesystem_utils import remove_file, remove_config_section
from utils.logger import Logger from utils.logger import Logger
def run_client_config_removal( def run_client_config_removal(
client_config: ClientConfigData, client_config: BaseWebClientConfig,
kl_instances: List[Klipper], kl_instances: List[Klipper],
mr_instances: List[Moonraker], mr_instances: List[Moonraker],
) -> None: ) -> None:
remove_client_config_dir(client_config) remove_client_config_dir(client_config)
remove_client_config_symlink(client_config) remove_client_config_symlink(client_config)
remove_config_section(f"update_manager {client_config.get('name')}", mr_instances) remove_config_section(f"update_manager {client_config.name}", mr_instances)
remove_config_section(client_config.get("printer_cfg_section"), kl_instances) remove_config_section(client_config.config_section, kl_instances)
def remove_client_config_dir(client_config: ClientConfigData) -> None: def remove_client_config_dir(client_config: BaseWebClientConfig) -> None:
Logger.print_status(f"Removing {client_config.get('name')} ...") Logger.print_status(f"Removing {client_config.name} ...")
client_config_dir = client_config.get("dir") client_config_dir = client_config.config_dir
if not client_config_dir.exists(): if not client_config_dir.exists():
Logger.print_info(f"'{client_config_dir}' does not exist. Skipping ...") Logger.print_info(f"'{client_config_dir}' does not exist. Skipping ...")
return return
@@ -44,12 +44,12 @@ def remove_client_config_dir(client_config: ClientConfigData) -> None:
Logger.print_error(f"Unable to delete '{client_config_dir}':\n{e}") Logger.print_error(f"Unable to delete '{client_config_dir}':\n{e}")
def remove_client_config_symlink(client_config: ClientConfigData) -> None: def remove_client_config_symlink(client_config: BaseWebClientConfig) -> None:
im = InstanceManager(Klipper) im = InstanceManager(Klipper)
instances: List[Klipper] = im.instances instances: List[Klipper] = im.instances
for instance in instances: for instance in instances:
Logger.print_status(f"Removing symlink from '{instance.cfg_dir}' ...") Logger.print_status(f"Removing symlink from '{instance.cfg_dir}' ...")
symlink = instance.cfg_dir.joinpath(client_config.get("cfg_filename")) symlink = instance.cfg_dir.joinpath(client_config.config_filename)
if not symlink.is_symlink(): if not symlink.is_symlink():
Logger.print_info(f"'{symlink}' does not exist. Skipping ...") Logger.print_info(f"'{symlink}' does not exist. Skipping ...")
continue continue

View File

@@ -12,15 +12,14 @@ import subprocess
from pathlib import Path from pathlib import Path
from typing import List from typing import List
from components.webui_client.base_data import BaseWebClient, BaseWebClientConfig
from kiauh import KIAUH_CFG from kiauh import KIAUH_CFG
from components.klipper.klipper import Klipper from components.klipper.klipper import Klipper
from components.moonraker.moonraker import Moonraker from components.moonraker.moonraker import Moonraker
from components.webui_client import ClientConfigData, ClientName, ClientData
from components.webui_client.client_dialogs import ( from components.webui_client.client_dialogs import (
print_client_already_installed_dialog, print_client_already_installed_dialog,
) )
from components.webui_client.client_utils import ( from components.webui_client.client_utils import (
load_client_data,
backup_client_config_data, backup_client_config_data,
config_for_other_client_exist, config_for_other_client_exist,
) )
@@ -38,19 +37,18 @@ from utils.input_utils import get_confirm
from utils.logger import Logger from utils.logger import Logger
def install_client_config(client_name: ClientName) -> None: def install_client_config(client_data: BaseWebClient) -> None:
client: ClientData = load_client_data(client_name) client_config: BaseWebClientConfig = client_data.client_config
client_config: ClientConfigData = client.get("client_config") display_name = client_config.display_name
d_name = client_config.get("display_name")
if config_for_other_client_exist(client_name): if config_for_other_client_exist(client_data.client):
Logger.print_info("Another Client-Config is already installed! Skipped ...") Logger.print_info("Another Client-Config is already installed! Skipped ...")
return return
if client_config.get("dir").exists(): if client_config.config_dir.exists():
print_client_already_installed_dialog(d_name) print_client_already_installed_dialog(display_name)
if get_confirm(f"Re-install {d_name}?", allow_go_back=True): if get_confirm(f"Re-install {display_name}?", allow_go_back=True):
shutil.rmtree(client_config.get("dir")) shutil.rmtree(client_config.config_dir)
else: else:
return return
@@ -66,69 +64,74 @@ def install_client_config(client_name: ClientName) -> None:
backup_printer_config_dir() backup_printer_config_dir()
add_config_section( add_config_section(
section=f"update_manager {client_config.get('name')}", section=f"update_manager {client_config.name}",
instances=mr_instances, instances=mr_instances,
options=[ options=[
("type", "git_repo"), ("type", "git_repo"),
("primary_branch", "master"), ("primary_branch", "master"),
("path", client_config.get("mr_conf_path")), ("path", str(client_config.config_dir)),
("origin", client_config.get("mr_conf_origin")), ("origin", str(client_config.repo_url)),
("managed_services", "klipper"), ("managed_services", "klipper"),
], ],
) )
add_config_section_at_top( add_config_section_at_top(client_config.config_section, kl_instances)
client_config.get("printer_cfg_section"), kl_instances
)
kl_im.restart_all_instance() kl_im.restart_all_instance()
except Exception as e: except Exception as e:
Logger.print_error(f"{d_name} installation failed!\n{e}") Logger.print_error(f"{display_name} installation failed!\n{e}")
return return
Logger.print_ok(f"{d_name} installation complete!", start="\n") Logger.print_ok(f"{display_name} installation complete!", start="\n")
def download_client_config(client_config: ClientConfigData) -> None: def download_client_config(client_config: BaseWebClientConfig) -> None:
try: try:
Logger.print_status(f"Downloading {client_config.get('display_name')} ...") Logger.print_status(f"Downloading {client_config.display_name} ...")
rm = RepoManager( rm = RepoManager(
client_config.get("url"), target_dir=str(client_config.get("dir")) client_config.repo_url,
target_dir=str(client_config.config_dir),
) )
rm.clone_repo() rm.clone_repo()
except Exception: except Exception:
Logger.print_error(f"Downloading {client_config.get('display_name')} failed!") Logger.print_error(f"Downloading {client_config.display_name} failed!")
raise raise
def update_client_config(client: ClientData) -> None: def update_client_config(client: BaseWebClient) -> None:
client_config: ClientConfigData = client.get("client_config") client_config: BaseWebClientConfig = client.client_config
Logger.print_status(f"Updating {client_config.get('display_name')} ...") Logger.print_status(f"Updating {client_config.display_name} ...")
if not client_config.config_dir.exists():
Logger.print_info(
f"Unable to update {client_config.display_name}. Directory does not exist! Skipping ..."
)
return
cm = ConfigManager(cfg_file=KIAUH_CFG) cm = ConfigManager(cfg_file=KIAUH_CFG)
if cm.get_value("kiauh", "backup_before_update"): if cm.get_value("kiauh", "backup_before_update"):
backup_client_config_data(client) backup_client_config_data(client)
repo_manager = RepoManager( repo_manager = RepoManager(
repo=client_config.get("url"), repo=client_config.repo_url,
branch="master", branch="master",
target_dir=str(client_config.get("dir")), target_dir=str(client_config.config_dir),
) )
repo_manager.pull_repo() repo_manager.pull_repo()
Logger.print_ok(f"Successfully updated {client_config.get('display_name')}.") Logger.print_ok(f"Successfully updated {client_config.display_name}.")
Logger.print_warn("Remember to restart Klipper to reload the configurations!") Logger.print_info("Restart Klipper to reload the configuration!")
def create_client_config_symlink( def create_client_config_symlink(
client_config: ClientConfigData, klipper_instances: List[Klipper] = None client_config: BaseWebClientConfig, klipper_instances: List[Klipper] = None
) -> None: ) -> None:
if klipper_instances is None: if klipper_instances is None:
kl_im = InstanceManager(Klipper) kl_im = InstanceManager(Klipper)
klipper_instances = kl_im.instances klipper_instances = kl_im.instances
Logger.print_status(f"Create symlink for {client_config.get('cfg_filename')} ...") Logger.print_status(f"Create symlink for {client_config.config_filename} ...")
source = Path(client_config.get("dir"), client_config.get("cfg_filename")) source = Path(client_config.config_dir, client_config.config_filename)
for instance in klipper_instances: for instance in klipper_instances:
target = instance.cfg_dir target = instance.cfg_dir
Logger.print_status(f"Linking {source} to {target}") Logger.print_status(f"Linking {source} to {target}")

View File

@@ -10,7 +10,7 @@
import textwrap import textwrap
from typing import List from typing import List
from components.webui_client import ClientData from components.webui_client.base_data import BaseWebClient
from core.menus.base_menu import print_back_footer from core.menus.base_menu import print_back_footer
from utils.constants import RESET_FORMAT, COLOR_YELLOW, COLOR_CYAN from utils.constants import RESET_FORMAT, COLOR_YELLOW, COLOR_CYAN
@@ -84,9 +84,9 @@ def print_client_port_select_dialog(name: str, port: str, ports_in_use: List[str
print(dialog, end="") print(dialog, end="")
def print_install_client_config_dialog(client: ClientData): def print_install_client_config_dialog(client: BaseWebClient):
name = client.get("display_name") name = client.display_name
url = client.get("client_config").get("url").replace(".git", "") url = client.client_config.repo_url.replace(".git", "")
line1 = f"have {name} fully functional and working." line1 = f"have {name} fully functional and working."
line2 = f"The recommended macros for {name} can be seen here:" line2 = f"The recommended macros for {name} can be seen here:"
dialog = textwrap.dedent( dialog = textwrap.dedent(

View File

@@ -13,7 +13,10 @@ from typing import List
from components.klipper.klipper import Klipper from components.klipper.klipper import Klipper
from components.moonraker.moonraker import Moonraker from components.moonraker.moonraker import Moonraker
from components.webui_client import ClientData from components.webui_client.base_data import (
BaseWebClient,
WebClientType,
)
from components.webui_client.client_config.client_config_remove import ( from components.webui_client.client_config.client_config_remove import (
run_client_config_removal, run_client_config_removal,
) )
@@ -29,7 +32,7 @@ from utils.logger import Logger
def run_client_removal( def run_client_removal(
client: ClientData, client: BaseWebClient,
rm_client: bool, rm_client: bool,
rm_client_config: bool, rm_client_config: bool,
backup_ms_config_json: bool, backup_ms_config_json: bool,
@@ -39,11 +42,11 @@ def run_client_removal(
kl_im = InstanceManager(Klipper) kl_im = InstanceManager(Klipper)
kl_instances: List[Klipper] = kl_im.instances kl_instances: List[Klipper] = kl_im.instances
if backup_ms_config_json and client.get("name") == "mainsail": if backup_ms_config_json and client.client == WebClientType.MAINSAIL:
backup_mainsail_config_json() backup_mainsail_config_json()
if rm_client: if rm_client:
client_name = client.get("name") client_name = client.name
remove_client_dir(client) remove_client_dir(client)
remove_nginx_config(client_name) remove_nginx_config(client_name)
remove_nginx_logs(client_name) remove_nginx_logs(client_name)
@@ -53,16 +56,16 @@ def run_client_removal(
if rm_client_config: if rm_client_config:
run_client_config_removal( run_client_config_removal(
client.get("client_config"), client.client_config,
kl_instances, kl_instances,
mr_instances, mr_instances,
) )
def remove_client_dir(client: ClientData) -> None: def remove_client_dir(client: BaseWebClient) -> None:
Logger.print_status(f"Removing {client.get('display_name')} ...") Logger.print_status(f"Removing {client.display_name} ...")
client_dir = client.get("dir") client_dir = client.client_dir
if not client.get("dir").exists(): if not client.client_dir.exists():
Logger.print_info(f"'{client_dir}' does not exist. Skipping ...") Logger.print_info(f"'{client_dir}' does not exist. Skipping ...")
return return

View File

@@ -11,12 +11,13 @@ from pathlib import Path
from typing import List from typing import List
from components.klipper.klipper import Klipper from components.klipper.klipper import Klipper
from components.webui_client import (
ClientName,
ClientData,
)
from components.moonraker.moonraker import Moonraker from components.moonraker.moonraker import Moonraker
from components.webui_client.base_data import (
WebClientType,
BaseWebClient,
BaseWebClientConfig,
)
from components.webui_client.client_config.client_config_setup import ( from components.webui_client.client_config.client_config_setup import (
install_client_config, install_client_config,
) )
@@ -30,7 +31,6 @@ from components.webui_client.client_utils import (
restore_mainsail_config_json, restore_mainsail_config_json,
enable_mainsail_remotemode, enable_mainsail_remotemode,
symlink_webui_nginx_log, symlink_webui_nginx_log,
load_client_data,
config_for_other_client_exist, config_for_other_client_exist,
) )
from core.config_manager.config_manager import ConfigManager from core.config_manager.config_manager import ConfigManager
@@ -60,17 +60,13 @@ from utils.system_utils import (
) )
def install_client(client_name: ClientName) -> None: def install_client(client: BaseWebClient) -> None:
client: ClientData = load_client_data(client_name)
d_name = client.get("display_name")
if client is None: if client is None:
Logger.print_error("Missing parameter client_name!") raise ValueError("Missing parameter client_data!")
return
if client.get("dir").exists(): if client.client_dir.exists():
Logger.print_info( Logger.print_info(
f"{client.get('display_name')} seems to be already installed! Skipped ..." f"{client.display_name} seems to be already installed! Skipped ..."
) )
return return
@@ -81,31 +77,35 @@ def install_client(client_name: ClientName) -> None:
if not mr_instances: if not mr_instances:
print_moonraker_not_found_dialog() print_moonraker_not_found_dialog()
if not get_confirm( if not get_confirm(
f"Continue {d_name} installation?", f"Continue {client.display_name} installation?",
allow_go_back=True, allow_go_back=True,
): ):
return return
# if moonraker is not installed or multiple instances # if moonraker is not installed or multiple instances
# are installed we enable mainsails remote mode # are installed we enable mainsails remote mode
if client.get("remote_mode") and not mr_instances or len(mr_instances) > 1: if (
client.client == WebClientType.MAINSAIL
and not mr_instances
or len(mr_instances) > 1
):
enable_remotemode = True enable_remotemode = True
kl_im = InstanceManager(Klipper) kl_im = InstanceManager(Klipper)
kl_instances = kl_im.instances kl_instances = kl_im.instances
install_client_cfg = False install_client_cfg = False
client_config = client.get("client_config") client_config: BaseWebClientConfig = client.client_config
if ( if (
kl_instances kl_instances
and not client_config.get("dir").exists() and not client_config.config_dir.exists()
and not config_for_other_client_exist(client_to_ignore=client.get("name")) and not config_for_other_client_exist(client_to_ignore=client.client)
): ):
print_install_client_config_dialog(client) print_install_client_config_dialog(client)
question = f"Download the recommended {client_config.get('display_name')}?" question = f"Download the recommended {client_config.display_name}?"
install_client_cfg = get_confirm(question, allow_go_back=False) install_client_cfg = get_confirm(question, allow_go_back=False)
cm = ConfigManager(cfg_file=KIAUH_CFG) cm = ConfigManager(cfg_file=KIAUH_CFG)
default_port = cm.get_value(client.get("name"), "port") default_port = cm.get_value(client.name, "port")
client_port = default_port if default_port and default_port.isdigit() else "80" client_port = default_port if default_port and default_port.isdigit() else "80"
ports_in_use = read_ports_from_nginx_configs() ports_in_use = read_ports_from_nginx_configs()
@@ -113,10 +113,10 @@ def install_client(client_name: ClientName) -> None:
valid_port = is_valid_port(client_port, ports_in_use) valid_port = is_valid_port(client_port, ports_in_use)
while not valid_port: while not valid_port:
next_port = get_next_free_port(ports_in_use) next_port = get_next_free_port(ports_in_use)
print_client_port_select_dialog(d_name, next_port, ports_in_use) print_client_port_select_dialog(client.display_name, next_port, ports_in_use)
client_port = str( client_port = str(
get_number_input( get_number_input(
f"Configure {d_name} for port", f"Configure {client.display_name} for port",
min_count=int(next_port), min_count=int(next_port),
default=next_port, default=next_port,
) )
@@ -127,22 +127,22 @@ def install_client(client_name: ClientName) -> None:
try: try:
download_client(client) download_client(client)
if enable_remotemode and client.get("name") == "mainsail": if enable_remotemode and client.client == WebClientType.MAINSAIL:
enable_mainsail_remotemode() enable_mainsail_remotemode()
if mr_instances: if mr_instances:
add_config_section( add_config_section(
section=f"update_manager {client.get('name')}", section=f"update_manager {client.name}",
instances=mr_instances, instances=mr_instances,
options=[ options=[
("type", "web"), ("type", "web"),
("channel", "stable"), ("channel", "stable"),
("repo", client.get("mr_conf_repo")), ("repo", str(client.repo_path)),
("path", client.get("mr_conf_path")), ("path", str(client.client_dir)),
], ],
) )
mr_im.restart_all_instance() mr_im.restart_all_instance()
if install_client_cfg and kl_instances: if install_client_cfg and kl_instances:
install_client_config(client.get("name")) install_client_config(client)
copy_upstream_nginx_cfg() copy_upstream_nginx_cfg()
copy_common_vars_nginx_cfg() copy_common_vars_nginx_cfg()
@@ -152,24 +152,24 @@ def install_client(client_name: ClientName) -> None:
control_systemd_service("nginx", "restart") control_systemd_service("nginx", "restart")
except Exception as e: except Exception as e:
Logger.print_error(f"{d_name} installation failed!\n{e}") Logger.print_error(f"{client.display_name} installation failed!\n{e}")
return return
log = f"Open {d_name} now on: http://{get_ipv4_addr()}:{client_port}" log = f"Open {client.display_name} now on: http://{get_ipv4_addr()}:{client_port}"
Logger.print_ok(f"{d_name} installation complete!", start="\n") Logger.print_ok(f"{client.display_name} installation complete!", start="\n")
Logger.print_ok(log, prefix=False, end="\n\n") Logger.print_ok(log, prefix=False, end="\n\n")
def download_client(client: ClientData) -> None: def download_client(client: BaseWebClient) -> None:
zipfile = f"{client.get('name').lower()}.zip" zipfile = f"{client.name.lower()}.zip"
target = Path().home().joinpath(zipfile) target = Path().home().joinpath(zipfile)
try: try:
Logger.print_status(f"Downloading {zipfile} ...") Logger.print_status(f"Downloading {zipfile} ...")
download_file(client.get("url"), target, True) download_file(client.stable_url, target, True)
Logger.print_ok("Download complete!") Logger.print_ok("Download complete!")
Logger.print_status(f"Extracting {zipfile} ...") Logger.print_status(f"Extracting {zipfile} ...")
unzip(target, client.get("dir")) unzip(target, client.client_dir)
target.unlink(missing_ok=True) target.unlink(missing_ok=True)
Logger.print_ok("OK!") Logger.print_ok("OK!")
@@ -178,29 +178,35 @@ def download_client(client: ClientData) -> None:
raise raise
def update_client(client: ClientData) -> None: def update_client(client: BaseWebClient) -> None:
Logger.print_status(f"Updating {client.get('display_name')} ...") Logger.print_status(f"Updating {client.display_name} ...")
if client.get("name") == "mainsail": if not client.client_dir.exists():
Logger.print_info(
f"Unable to update {client.display_name}. Directory does not exist! Skipping ..."
)
return
if client.client == WebClientType.MAINSAIL:
backup_mainsail_config_json(is_temp=True) backup_mainsail_config_json(is_temp=True)
download_client(client) download_client(client)
if client.get("name") == "mainsail": if client.client == WebClientType.MAINSAIL:
restore_mainsail_config_json() restore_mainsail_config_json()
def create_client_nginx_cfg(client: ClientData, port: int) -> None: def create_client_nginx_cfg(client: BaseWebClient, port: int) -> None:
d_name = client.get("display_name") display_name = client.display_name
root_dir = client.get("dir") root_dir = client.client_dir
source = NGINX_SITES_AVAILABLE.joinpath(client.get("name")) source = NGINX_SITES_AVAILABLE.joinpath(client.name)
target = NGINX_SITES_ENABLED.joinpath(client.get("name")) target = NGINX_SITES_ENABLED.joinpath(client.name)
try: try:
Logger.print_status(f"Creating NGINX config for {d_name} ...") Logger.print_status(f"Creating NGINX config for {display_name} ...")
remove_file(Path("/etc/nginx/sites-enabled/default"), True) remove_file(Path("/etc/nginx/sites-enabled/default"), True)
create_nginx_cfg(client.get("name"), port, root_dir) create_nginx_cfg(client.name, port, root_dir)
create_symlink(source, target, True) create_symlink(source, target, True)
set_nginx_permissions() set_nginx_permissions()
Logger.print_ok(f"NGINX config for {d_name} successfully created.") Logger.print_ok(f"NGINX config for {display_name} successfully created.")
except Exception: except Exception:
Logger.print_error(f"Creating NGINX config for {d_name} failed!") Logger.print_error(f"Creating NGINX config for {display_name} failed!")
raise raise

View File

@@ -9,117 +9,43 @@
import json import json
import shutil import shutil
from json import JSONDecodeError
from pathlib import Path from pathlib import Path
from typing import List, Optional, Dict, Literal, Union, get_args from typing import List, Dict, Literal, Union, get_args
import urllib.request
from components.klipper.klipper import Klipper from components.klipper.klipper import Klipper
from components.webui_client import ( from components.webui_client.base_data import (
MAINSAIL_CONFIG_JSON, WebClientType,
MAINSAIL_DIR, BaseWebClient,
MAINSAIL_BACKUP_DIR, BaseWebClientConfig,
FLUIDD_PRE_RLS_URL,
FLUIDD_BACKUP_DIR,
FLUIDD_URL,
FLUIDD_DIR,
ClientData,
FLUIDD_CONFIG_REPO_URL,
FLUIDD_CONFIG_DIR,
ClientConfigData,
MAINSAIL_PRE_RLS_URL,
MAINSAIL_URL,
MAINSAIL_CONFIG_REPO_URL,
MAINSAIL_CONFIG_DIR,
ClientName,
MAINSAIL_TAGS_URL,
FLUIDD_TAGS_URL,
FLUIDD_CONFIG_BACKUP_DIR,
MAINSAIL_CONFIG_BACKUP_DIR,
) )
from components.webui_client.mainsail_data import MainsailData
from core.backup_manager.backup_manager import BackupManager from core.backup_manager.backup_manager import BackupManager
from core.repo_manager.repo_manager import RepoManager from core.repo_manager.repo_manager import RepoManager
from utils import NGINX_SITES_AVAILABLE, NGINX_CONFD from utils import NGINX_SITES_AVAILABLE, NGINX_CONFD
from utils.common import get_install_status_webui from utils.common import get_install_status_webui
from utils.constants import COLOR_CYAN, RESET_FORMAT, COLOR_YELLOW from utils.constants import COLOR_CYAN, RESET_FORMAT, COLOR_YELLOW
from utils.git_utils import get_latest_tag
from utils.logger import Logger from utils.logger import Logger
def load_client_data(client_name: ClientName) -> Optional[ClientData]: def get_client_status(client: BaseWebClient) -> str:
client_data = None
if client_name == "mainsail":
client_config_data = ClientConfigData(
name="mainsail-config",
display_name="Mainsail-Config",
cfg_filename="mainsail.cfg",
dir=MAINSAIL_CONFIG_DIR,
backup_dir=MAINSAIL_CONFIG_BACKUP_DIR,
url=MAINSAIL_CONFIG_REPO_URL,
printer_cfg_section="include mainsail.cfg",
mr_conf_path="~/mainsail-config",
mr_conf_origin=MAINSAIL_CONFIG_REPO_URL,
)
client_data = ClientData(
name=client_name,
display_name=client_name.capitalize(),
dir=MAINSAIL_DIR,
backup_dir=MAINSAIL_BACKUP_DIR,
url=MAINSAIL_URL,
pre_release_url=MAINSAIL_PRE_RLS_URL,
tags_url=MAINSAIL_TAGS_URL,
remote_mode=True,
mr_conf_repo="mainsail-crew/mainsail",
mr_conf_path="~/mainsail",
client_config=client_config_data,
)
elif client_name == "fluidd":
client_config_data = ClientConfigData(
name="fluidd-config",
display_name="Fluidd-Config",
cfg_filename="fluidd.cfg",
dir=FLUIDD_CONFIG_DIR,
backup_dir=FLUIDD_CONFIG_BACKUP_DIR,
url=FLUIDD_CONFIG_REPO_URL,
printer_cfg_section="include fluidd.cfg",
mr_conf_path="~/fluidd-config",
mr_conf_origin=FLUIDD_CONFIG_REPO_URL,
)
client_data = ClientData(
name=client_name,
display_name=client_name.capitalize(),
dir=FLUIDD_DIR,
backup_dir=FLUIDD_BACKUP_DIR,
url=FLUIDD_URL,
pre_release_url=FLUIDD_PRE_RLS_URL,
tags_url=FLUIDD_TAGS_URL,
remote_mode=False,
mr_conf_repo="fluidd-core/fluidd",
mr_conf_path="~/fluidd",
client_config=client_config_data,
)
return client_data
def get_client_status(client: ClientData) -> str:
return get_install_status_webui( return get_install_status_webui(
client.get("dir"), client.client_dir,
NGINX_SITES_AVAILABLE.joinpath(client.get("name")), NGINX_SITES_AVAILABLE.joinpath(client.name),
NGINX_CONFD.joinpath("upstreams.conf"), NGINX_CONFD.joinpath("upstreams.conf"),
NGINX_CONFD.joinpath("common_vars.conf"), NGINX_CONFD.joinpath("common_vars.conf"),
) )
def get_client_config_status( def get_client_config_status(
client: ClientData, client: BaseWebClient,
) -> Dict[ ) -> Dict[
Literal["repo", "local", "remote"], Literal["repo", "local", "remote"],
Union[str, int], Union[str, int],
]: ]:
client_config = client.get("client_config") client_config = client.client_config
client_config = client_config.get("dir") client_config = client_config.config_dir
return { return {
"repo": RepoManager.get_repo_name(client_config), "repo": RepoManager.get_repo_name(client_config),
@@ -128,44 +54,47 @@ def get_client_config_status(
} }
def get_current_client_config(clients: List[ClientData]) -> str: def get_current_client_config(clients: List[BaseWebClient]) -> str:
installed = [] installed = []
for client in clients: for client in clients:
client_config = client.get("client_config") client_config = client.client_config
if client_config.get("dir").exists(): if client_config.config_dir.exists():
installed.append(client) installed.append(client)
if len(installed) > 1: if len(installed) > 1:
return f"{COLOR_YELLOW}Conflict!{RESET_FORMAT}" return f"{COLOR_YELLOW}Conflict!{RESET_FORMAT}"
elif len(installed) == 1: elif len(installed) == 1:
cfg = installed[0].get("client_config") cfg = installed[0].client_config
return f"{COLOR_CYAN}{cfg.get('display_name')}{RESET_FORMAT}" return f"{COLOR_CYAN}{cfg.display_name}{RESET_FORMAT}"
return f"{COLOR_CYAN}-{RESET_FORMAT}" return f"{COLOR_CYAN}-{RESET_FORMAT}"
def backup_mainsail_config_json(is_temp=False) -> None: def backup_mainsail_config_json(is_temp=False) -> None:
Logger.print_status(f"Backup '{MAINSAIL_CONFIG_JSON}' ...") c_json = MainsailData().client_dir.joinpath("config.json")
Logger.print_status(f"Backup '{c_json}' ...")
bm = BackupManager() bm = BackupManager()
if is_temp: if is_temp:
fn = Path.home().joinpath("config.json.kiauh.bak") fn = Path.home().joinpath("config.json.kiauh.bak")
bm.backup_file(MAINSAIL_CONFIG_JSON, custom_filename=fn) bm.backup_file(c_json, custom_filename=fn)
else: else:
bm.backup_file(MAINSAIL_CONFIG_JSON) bm.backup_file(c_json)
def restore_mainsail_config_json() -> None: def restore_mainsail_config_json() -> None:
try: try:
Logger.print_status(f"Restore '{MAINSAIL_CONFIG_JSON}' ...") c_json = MainsailData().client_dir.joinpath("config.json")
Logger.print_status(f"Restore '{c_json}' ...")
source = Path.home().joinpath("config.json.kiauh.bak") source = Path.home().joinpath("config.json.kiauh.bak")
shutil.copy(source, MAINSAIL_CONFIG_JSON) shutil.copy(source, c_json)
except OSError: except OSError:
Logger.print_info("Unable to restore config.json. Skipped ...") Logger.print_info("Unable to restore config.json. Skipped ...")
def enable_mainsail_remotemode() -> None: def enable_mainsail_remotemode() -> None:
Logger.print_status("Enable Mainsails remote mode ...") Logger.print_status("Enable Mainsails remote mode ...")
with open(MAINSAIL_CONFIG_JSON, "r") as f: c_json = MainsailData().client_dir.joinpath("config.json")
with open(c_json, "r") as f:
config_data = json.load(f) config_data = json.load(f)
if config_data["instancesDB"] == "browser": if config_data["instancesDB"] == "browser":
@@ -175,7 +104,7 @@ def enable_mainsail_remotemode() -> None:
Logger.print_status("Setting instance storage location to 'browser' ...") Logger.print_status("Setting instance storage location to 'browser' ...")
config_data["instancesDB"] = "browser" config_data["instancesDB"] = "browser"
with open(MAINSAIL_CONFIG_JSON, "w") as f: with open(c_json, "w") as f:
json.dump(config_data, f, indent=4) json.dump(config_data, f, indent=4)
Logger.print_ok("Mainsails remote mode enabled!") Logger.print_ok("Mainsails remote mode enabled!")
@@ -195,8 +124,8 @@ def symlink_webui_nginx_log(klipper_instances: List[Klipper]) -> None:
desti_error.symlink_to(error_log) desti_error.symlink_to(error_log)
def get_local_client_version(client: ClientData) -> str: def get_local_client_version(client: BaseWebClient) -> str:
relinfo_file = client.get("dir").joinpath("release_info.json") relinfo_file = client.client_dir.joinpath("release_info.json")
if not relinfo_file.is_file(): if not relinfo_file.is_file():
return "-" return "-"
@@ -204,19 +133,19 @@ def get_local_client_version(client: ClientData) -> str:
return json.load(f)["version"] return json.load(f)["version"]
def get_remote_client_version(client: ClientData) -> str: def get_remote_client_version(client: BaseWebClient) -> str:
try: try:
with urllib.request.urlopen(client.get("tags_url")) as response: if (tag := get_latest_tag(client.repo_path)) != "":
data = json.loads(response.read()) return tag
return data[0]["name"] return "ERROR"
except (JSONDecodeError, TypeError): except Exception:
return "ERROR" return "ERROR"
def backup_client_data(client: ClientData) -> None: def backup_client_data(client: BaseWebClient) -> None:
name = client.get("name") name = client.name
src = client.get("dir") src = client.client_dir
dest = client.get("backup_dir") dest = client.backup_dir
with open(src.joinpath(".version"), "r") as v: with open(src.joinpath(".version"), "r") as v:
version = v.readlines()[0] version = v.readlines()[0]
@@ -224,43 +153,42 @@ def backup_client_data(client: ClientData) -> None:
bm = BackupManager() bm = BackupManager()
bm.backup_directory(f"{name}-{version}", src, dest) bm.backup_directory(f"{name}-{version}", src, dest)
if name == "mainsail": if name == "mainsail":
bm.backup_file(MAINSAIL_CONFIG_JSON, dest) c_json = MainsailData().client_dir.joinpath("config.json")
bm.backup_file(c_json, dest)
bm.backup_file(NGINX_SITES_AVAILABLE.joinpath(name), dest) bm.backup_file(NGINX_SITES_AVAILABLE.joinpath(name), dest)
def backup_client_config_data(client: ClientData) -> None: def backup_client_config_data(client: BaseWebClient) -> None:
client_config = client.get("client_config") client_config = client.client_config
name = client_config.get("name") name = client_config.name
source = client_config.get("dir") source = client_config.config_dir
target = client_config.get("backup_dir") target = client_config.backup_dir
bm = BackupManager() bm = BackupManager()
bm.backup_directory(name, source, target) bm.backup_directory(name, source, target)
def get_existing_clients() -> List[ClientData]: def get_existing_clients() -> List[BaseWebClient]:
clients = list(get_args(ClientName)) clients = list(get_args(WebClientType))
installed_clients: List[ClientData] = [] installed_clients: List[BaseWebClient] = []
for c in clients: for client in clients:
c_data: ClientData = load_client_data(c) if client.client_dir.exists():
if c_data.get("dir").exists(): installed_clients.append(client)
installed_clients.append(c_data)
return installed_clients return installed_clients
def get_existing_client_config() -> List[ClientData]: def get_existing_client_config() -> List[BaseWebClient]:
clients = list(get_args(ClientName)) clients = list(get_args(WebClientType))
installed_client_configs: List[ClientData] = [] installed_client_configs: List[BaseWebClient] = []
for c in clients: for client in clients:
c_data: ClientData = load_client_data(c) c_config_data: BaseWebClientConfig = client.client_config
c_config_data: ClientConfigData = c_data.get("client_config") if c_config_data.config_dir.exists():
if c_config_data.get("dir").exists(): installed_client_configs.append(client)
installed_client_configs.append(c_data)
return installed_client_configs return installed_client_configs
def config_for_other_client_exist(client_to_ignore: ClientName) -> bool: def config_for_other_client_exist(client_to_ignore: WebClientType) -> bool:
""" """
Check if any other client configs are present on the system. Check if any other client configs are present on the system.
It is usually not harmful, but chances are they can conflict each other. It is usually not harmful, but chances are they can conflict each other.
@@ -269,7 +197,7 @@ def config_for_other_client_exist(client_to_ignore: ClientName) -> bool:
:return: True, if other client configs were found, else False :return: True, if other client configs were found, else False
""" """
clients = set([c["name"] for c in get_existing_client_config()]) clients = set([c.name for c in get_existing_client_config()])
clients = clients - {client_to_ignore} clients = clients - {client_to_ignore.value}
return True if len(clients) > 0 else False return True if len(clients) > 0 else False

View File

@@ -0,0 +1,65 @@
# ======================================================================= #
# 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
from dataclasses import dataclass
from pathlib import Path
from components.webui_client.base_data import (
BaseWebClientConfig,
WebClientConfigType,
WebClientType,
BaseWebClient,
)
from core.backup_manager import BACKUP_ROOT_DIR
from utils.git_utils import get_latest_unstable_tag
@dataclass(frozen=True)
class FluiddConfigWeb(BaseWebClientConfig):
client_config: WebClientConfigType = WebClientConfigType.FLUIDD
name: str = client_config.value
display_name: str = name.title()
config_dir: Path = Path.home().joinpath("fluidd-config")
config_filename: str = "fluidd.cfg"
config_section: str = f"include {config_filename}"
backup_dir: Path = BACKUP_ROOT_DIR.joinpath("fluidd-config-backups")
repo_url: str = "https://github.com/fluidd-core/fluidd-config.git"
@dataclass(frozen=True)
class FluiddData(BaseWebClient):
BASE_DL_URL = "https://github.com/fluidd-core/fluidd/releases"
client: WebClientType = WebClientType.FLUIDD
name: str = client.value
display_name: str = name.capitalize()
client_dir: Path = Path.home().joinpath("fluidd")
backup_dir: Path = BACKUP_ROOT_DIR.joinpath("fluidd-backups")
repo_path: str = "fluidd-core/fluidd"
@property
def stable_url(self) -> str:
return f"{self.BASE_DL_URL}/latest/download/fluidd.zip"
@property
def unstable_url(self) -> str:
try:
unstable_tag = get_latest_unstable_tag(self.repo_path)
if unstable_tag != "":
return f"{self.BASE_DL_URL}/download/{unstable_tag}/fluidd.zip"
else:
raise Exception
except Exception:
return self.stable_url
@property
def client_config(self) -> BaseWebClientConfig:
return FluiddConfigWeb()

View File

@@ -0,0 +1,65 @@
# ======================================================================= #
# 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
from dataclasses import dataclass
from pathlib import Path
from components.webui_client.base_data import (
BaseWebClientConfig,
WebClientConfigType,
WebClientType,
BaseWebClient,
)
from core.backup_manager import BACKUP_ROOT_DIR
from utils.git_utils import get_latest_unstable_tag
@dataclass(frozen=True)
class MainsailConfigWeb(BaseWebClientConfig):
client_config: WebClientConfigType = WebClientConfigType.MAINSAIL
name: str = client_config.value
display_name: str = name.title()
config_dir: Path = Path.home().joinpath("mainsail-config")
config_filename: str = "mainsail.cfg"
config_section: str = f"include {config_filename}"
backup_dir: Path = BACKUP_ROOT_DIR.joinpath("mainsail-config-backups")
repo_url: str = "https://github.com/mainsail-crew/mainsail-config.git"
@dataclass(frozen=True)
class MainsailData(BaseWebClient):
BASE_DL_URL: str = "https://github.com/mainsail-crew/mainsail/releases"
client: WebClientType = WebClientType.MAINSAIL
name: str = WebClientType.MAINSAIL.value
display_name: str = name.capitalize()
client_dir: Path = Path.home().joinpath("mainsail")
backup_dir: Path = BACKUP_ROOT_DIR.joinpath("mainsail-backups")
repo_path: str = "mainsail-crew/mainsail"
@property
def stable_url(self) -> str:
return f"{self.BASE_DL_URL}/latest/download/mainsail.zip"
@property
def unstable_url(self) -> str:
try:
unstable_tag = get_latest_unstable_tag(self.repo_path)
if unstable_tag != "":
return f"{self.BASE_DL_URL}/download/{unstable_tag}/mainsail.zip"
else:
raise Exception
except Exception:
return self.stable_url
@property
def client_config(self) -> BaseWebClientConfig:
return MainsailConfigWeb()

View File

@@ -10,14 +10,15 @@
import textwrap import textwrap
from typing import Callable, Dict from typing import Callable, Dict
from components.webui_client import client_remove, ClientData from components.webui_client import client_remove
from components.webui_client.base_data import BaseWebClient, WebClientType
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
class ClientRemoveMenu(BaseMenu): class ClientRemoveMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu, client: ClientData): def __init__(self, previous_menu: BaseMenu, client: BaseWebClient):
super().__init__() super().__init__()
self.previous_menu = previous_menu self.previous_menu = previous_menu
self.options = self.get_options(client) self.options = self.get_options(client)
@@ -27,22 +28,22 @@ class ClientRemoveMenu(BaseMenu):
self.rm_client_config = False self.rm_client_config = False
self.backup_mainsail_config_json = False self.backup_mainsail_config_json = False
def get_options(self, client: ClientData) -> Dict[str, Callable]: def get_options(self, client: BaseWebClient) -> Dict[str, Callable]:
options = { options = {
"0": self.toggle_all, "0": self.toggle_all,
"1": self.toggle_rm_client, "1": self.toggle_rm_client,
"2": self.toggle_rm_client_config, "2": self.toggle_rm_client_config,
"c": self.run_removal_process, "c": self.run_removal_process,
} }
if client.get("name") == "mainsail": if client.client == WebClientType.MAINSAIL:
options["3"] = self.toggle_backup_mainsail_config_json options["3"] = self.toggle_backup_mainsail_config_json
return options return options
def print_menu(self) -> None: def print_menu(self) -> None:
client_name = self.client.get("display_name") client_name = self.client.display_name
client_config = self.client.get("client_config") client_config = self.client.client_config
client_config_name = client_config.get("display_name") client_config_name = client_config.display_name
header = f" [ Remove {client_name} ] " header = f" [ Remove {client_name} ] "
color = COLOR_RED color = COLOR_RED
@@ -66,7 +67,7 @@ class ClientRemoveMenu(BaseMenu):
""" """
)[1:] )[1:]
if self.client.get("name") == "mainsail": if self.client.client == WebClientType.MAINSAIL:
o3 = checked if self.backup_mainsail_config_json else unchecked o3 = checked if self.backup_mainsail_config_json else unchecked
menu += textwrap.dedent( menu += textwrap.dedent(
f""" f"""

View File

@@ -16,9 +16,10 @@ from components.moonraker.moonraker_utils import (
) )
from components.webui_client.client_utils import ( from components.webui_client.client_utils import (
backup_client_data, backup_client_data,
load_client_data,
backup_client_config_data, backup_client_config_data,
) )
from components.webui_client.fluidd_data import FluiddData
from components.webui_client.mainsail_data import MainsailData
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
from utils.common import backup_printer_config_dir from utils.common import backup_printer_config_dir
from utils.constants import COLOR_CYAN, RESET_FORMAT, COLOR_YELLOW from utils.constants import COLOR_CYAN, RESET_FORMAT, COLOR_YELLOW
@@ -81,16 +82,16 @@ class BackupMenu(BaseMenu):
backup_moonraker_db_dir() backup_moonraker_db_dir()
def backup_mainsail(self, **kwargs): def backup_mainsail(self, **kwargs):
backup_client_data(load_client_data("mainsail")) backup_client_data(MainsailData().get())
def backup_fluidd(self, **kwargs): def backup_fluidd(self, **kwargs):
backup_client_data(load_client_data("fluidd")) backup_client_data(FluiddData().get())
def backup_mainsail_config(self, **kwargs): def backup_mainsail_config(self, **kwargs):
backup_client_config_data(load_client_data("mainsail")) backup_client_config_data(MainsailData().get())
def backup_fluidd_config(self, **kwargs): def backup_fluidd_config(self, **kwargs):
backup_client_config_data(load_client_data("fluidd")) backup_client_config_data(FluiddData().get())
def backup_klipperscreen(self, **kwargs): def backup_klipperscreen(self, **kwargs):
pass pass

View File

@@ -13,6 +13,8 @@ from components.klipper import klipper_setup
from components.moonraker import moonraker_setup from components.moonraker import moonraker_setup
from components.webui_client import client_setup from components.webui_client import client_setup
from components.webui_client.client_config import client_config_setup from components.webui_client.client_config import client_config_setup
from components.webui_client.fluidd_data import FluiddData
from components.webui_client.mainsail_data import MainsailData
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
from utils.constants import COLOR_GREEN, RESET_FORMAT from utils.constants import COLOR_GREEN, RESET_FORMAT
@@ -69,13 +71,13 @@ class InstallMenu(BaseMenu):
moonraker_setup.install_moonraker() moonraker_setup.install_moonraker()
def install_mainsail(self, **kwargs): def install_mainsail(self, **kwargs):
client_setup.install_client(client_name="mainsail") client_setup.install_client(MainsailData())
def install_mainsail_config(self, **kwargs): def install_mainsail_config(self, **kwargs):
client_config_setup.install_client_config(client_name="mainsail") client_config_setup.install_client_config(MainsailData())
def install_fluidd(self, **kwargs): def install_fluidd(self, **kwargs):
client_setup.install_client(client_name="fluidd") client_setup.install_client(FluiddData())
def install_fluidd_config(self, **kwargs): def install_fluidd_config(self, **kwargs):
client_config_setup.install_client_config(client_name="fluidd") client_config_setup.install_client_config(FluiddData())

View File

@@ -14,9 +14,10 @@ from components.log_uploads.menus.log_upload_menu import LogUploadMenu
from components.moonraker.moonraker_utils import get_moonraker_status from components.moonraker.moonraker_utils import get_moonraker_status
from components.webui_client.client_utils import ( from components.webui_client.client_utils import (
get_client_status, get_client_status,
load_client_data,
get_current_client_config, get_current_client_config,
) )
from components.webui_client.fluidd_data import FluiddData
from components.webui_client.mainsail_data import MainsailData
from core.menus import FooterType from core.menus import FooterType
from core.menus.advanced_menu import AdvancedMenu from core.menus.advanced_menu import AdvancedMenu
from core.menus.backup_menu import BackupMenu from core.menus.backup_menu import BackupMenu
@@ -92,15 +93,11 @@ class MainMenu(BaseMenu):
self.mr_status = self.format_status_by_code(mr_code, mr_status, mr_instances) self.mr_status = self.format_status_by_code(mr_code, mr_status, mr_instances)
self.mr_repo = f"{COLOR_CYAN}{moonraker_status.get('repo')}{RESET_FORMAT}" self.mr_repo = f"{COLOR_CYAN}{moonraker_status.get('repo')}{RESET_FORMAT}"
# mainsail # mainsail
mainsail_client_data = load_client_data("mainsail") self.ms_status = get_client_status(MainsailData())
self.ms_status = get_client_status(mainsail_client_data)
# fluidd # fluidd
fluidd_client_data = load_client_data("fluidd") self.fl_status = get_client_status(FluiddData())
self.fl_status = get_client_status(fluidd_client_data)
# client-config # client-config
self.cc_status = get_current_client_config( self.cc_status = get_current_client_config([MainsailData(), FluiddData()])
[mainsail_client_data, fluidd_client_data]
)
def format_status_by_code(self, code: int, status: str, count: str) -> str: def format_status_by_code(self, code: int, status: str, count: str) -> str:
if code == 1: if code == 1:

View File

@@ -13,7 +13,8 @@ from components.klipper.menus.klipper_remove_menu import KlipperRemoveMenu
from components.moonraker.menus.moonraker_remove_menu import ( from components.moonraker.menus.moonraker_remove_menu import (
MoonrakerRemoveMenu, MoonrakerRemoveMenu,
) )
from components.webui_client.client_utils import load_client_data from components.webui_client.fluidd_data import FluiddData
from components.webui_client.mainsail_data import MainsailData
from components.webui_client.menus.client_remove_menu import ClientRemoveMenu from components.webui_client.menus.client_remove_menu import ClientRemoveMenu
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
from utils.constants import COLOR_RED, RESET_FORMAT from utils.constants import COLOR_RED, RESET_FORMAT
@@ -76,7 +77,7 @@ class RemoveMenu(BaseMenu):
MoonrakerRemoveMenu(previous_menu=self).run() MoonrakerRemoveMenu(previous_menu=self).run()
def remove_mainsail(self, **kwargs): def remove_mainsail(self, **kwargs):
ClientRemoveMenu(previous_menu=self, client=load_client_data("mainsail")).run() ClientRemoveMenu(previous_menu=self, client=MainsailData()).run()
def remove_fluidd(self, **kwargs): def remove_fluidd(self, **kwargs):
ClientRemoveMenu(previous_menu=self, client=load_client_data("fluidd")).run() ClientRemoveMenu(previous_menu=self, client=FluiddData()).run()

View File

@@ -22,9 +22,10 @@ from components.webui_client.client_setup import update_client
from components.webui_client.client_utils import ( from components.webui_client.client_utils import (
get_local_client_version, get_local_client_version,
get_remote_client_version, get_remote_client_version,
load_client_data,
get_client_config_status, get_client_config_status,
) )
from components.webui_client.fluidd_data import FluiddData
from components.webui_client.mainsail_data import MainsailData
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
from utils.constants import ( from utils.constants import (
COLOR_GREEN, COLOR_GREEN,
@@ -69,6 +70,9 @@ class UpdateMenu(BaseMenu):
self.fc_local = f"{COLOR_WHITE}{RESET_FORMAT}" self.fc_local = f"{COLOR_WHITE}{RESET_FORMAT}"
self.fc_remote = f"{COLOR_WHITE}{RESET_FORMAT}" self.fc_remote = f"{COLOR_WHITE}{RESET_FORMAT}"
self.mainsail_cient = MainsailData()
self.fluidd_client = FluiddData()
def print_menu(self): def print_menu(self):
self.fetch_update_status() self.fetch_update_status()
@@ -114,16 +118,16 @@ class UpdateMenu(BaseMenu):
update_moonraker() update_moonraker()
def update_mainsail(self, **kwargs): def update_mainsail(self, **kwargs):
update_client(load_client_data("mainsail")) update_client(self.mainsail_cient)
def update_mainsail_config(self, **kwargs): def update_mainsail_config(self, **kwargs):
update_client_config(load_client_data("mainsail")) update_client_config(self.mainsail_cient)
def update_fluidd(self, **kwargs): def update_fluidd(self, **kwargs):
update_client(load_client_data("fluidd")) update_client(self.fluidd_client)
def update_fluidd_config(self, **kwargs): def update_fluidd_config(self, **kwargs):
update_client_config(load_client_data("fluidd")) update_client_config(self.fluidd_client)
def update_klipperscreen(self, **kwargs): ... def update_klipperscreen(self, **kwargs): ...
@@ -153,25 +157,23 @@ class UpdateMenu(BaseMenu):
self.mr_local = f"{COLOR_YELLOW}{self.mr_local}{RESET_FORMAT}" self.mr_local = f"{COLOR_YELLOW}{self.mr_local}{RESET_FORMAT}"
self.mr_remote = f"{COLOR_GREEN}{self.mr_remote}{RESET_FORMAT}" self.mr_remote = f"{COLOR_GREEN}{self.mr_remote}{RESET_FORMAT}"
# mainsail # mainsail
mainsail_client_data = load_client_data("mainsail") self.ms_local = get_local_client_version(self.mainsail_cient)
self.ms_local = get_local_client_version(mainsail_client_data) self.ms_remote = get_remote_client_version(self.mainsail_cient)
self.ms_remote = get_remote_client_version(mainsail_client_data)
if self.ms_local == self.ms_remote: if self.ms_local == self.ms_remote:
self.ms_local = f"{COLOR_GREEN}{self.ms_local}{RESET_FORMAT}" self.ms_local = f"{COLOR_GREEN}{self.ms_local}{RESET_FORMAT}"
else: else:
self.ms_local = f"{COLOR_YELLOW}{self.ms_local}{RESET_FORMAT}" self.ms_local = f"{COLOR_YELLOW}{self.ms_local}{RESET_FORMAT}"
self.ms_remote = f"{COLOR_GREEN if self.ms_remote != 'ERROR' else COLOR_RED}{self.ms_remote}{RESET_FORMAT}" self.ms_remote = f"{COLOR_GREEN if self.ms_remote != 'ERROR' else COLOR_RED}{self.ms_remote}{RESET_FORMAT}"
# fluidd # fluidd
fluidd_client_data = load_client_data("fluidd") self.fl_local = get_local_client_version(self.fluidd_client)
self.fl_local = get_local_client_version(fluidd_client_data) self.fl_remote = get_remote_client_version(self.fluidd_client)
self.fl_remote = get_remote_client_version(fluidd_client_data)
if self.fl_local == self.fl_remote: if self.fl_local == self.fl_remote:
self.fl_local = f"{COLOR_GREEN}{self.fl_local}{RESET_FORMAT}" self.fl_local = f"{COLOR_GREEN}{self.fl_local}{RESET_FORMAT}"
else: else:
self.fl_local = f"{COLOR_YELLOW}{self.fl_local}{RESET_FORMAT}" self.fl_local = f"{COLOR_YELLOW}{self.fl_local}{RESET_FORMAT}"
self.fl_remote = f"{COLOR_GREEN if self.fl_remote != 'ERROR' else COLOR_RED}{self.fl_remote}{RESET_FORMAT}" self.fl_remote = f"{COLOR_GREEN if self.fl_remote != 'ERROR' else COLOR_RED}{self.fl_remote}{RESET_FORMAT}"
# mainsail-config # mainsail-config
mc_status = get_client_config_status(load_client_data("mainsail")) mc_status = get_client_config_status(self.mainsail_cient)
self.mc_local = mc_status.get("local") self.mc_local = mc_status.get("local")
self.mc_remote = mc_status.get("remote") self.mc_remote = mc_status.get("remote")
if self.mc_local == self.mc_remote: if self.mc_local == self.mc_remote:
@@ -180,7 +182,7 @@ class UpdateMenu(BaseMenu):
self.mc_local = f"{COLOR_YELLOW}{self.mc_local}{RESET_FORMAT}" self.mc_local = f"{COLOR_YELLOW}{self.mc_local}{RESET_FORMAT}"
self.mc_remote = f"{COLOR_GREEN}{self.mc_remote}{RESET_FORMAT}" self.mc_remote = f"{COLOR_GREEN}{self.mc_remote}{RESET_FORMAT}"
# fluidd-config # fluidd-config
fc_status = get_client_config_status(load_client_data("fluidd")) fc_status = get_client_config_status(self.fluidd_client)
self.fc_local = fc_status.get("local") self.fc_local = fc_status.get("local")
self.fc_remote = fc_status.get("remote") self.fc_remote = fc_status.get("remote")
if self.fc_local == self.mc_remote: if self.fc_local == self.mc_remote:

57
kiauh/utils/git_utils.py Normal file
View File

@@ -0,0 +1,57 @@
import json
import urllib.request
from http.client import HTTPResponse
from json import JSONDecodeError
from typing import List
from utils.logger import Logger
def get_tags(repo_path: str) -> List[str]:
try:
url = f"https://api.github.com/repos/{repo_path}/tags"
with urllib.request.urlopen(url) as r:
response: HTTPResponse = r
if response.getcode() != 200:
Logger.print_error(
f"Error retrieving tags: HTTP status code {response.getcode()}"
)
return []
data = json.loads(response.read())
return [item["name"] for item in data]
except (JSONDecodeError, TypeError) as e:
Logger.print_error(f"Error while processing the response: {e}")
raise
def get_latest_tag(repo_path: str) -> str:
"""
Gets the latest stable tag of a GitHub repostiory
:param repo_path: path of the GitHub repository - e.g. `<owner>/<name>`
:return: tag or empty string
"""
try:
if len(latest_tag := get_tags(repo_path)) > 0:
return latest_tag[0]
else:
return ""
except Exception:
Logger.print_error("Error while getting the latest tag")
raise
def get_latest_unstable_tag(repo_path: str) -> str:
"""
Gets the latest unstable (alpha, beta, rc) tag of a GitHub repository
:param repo_path: path of the GitHub repository - e.g. `<owner>/<name>`
:return: tag or empty string
"""
try:
if len(unstable_tags := [t for t in get_tags(repo_path) if "-" in t]) > 0:
return unstable_tags[0]
else:
return ""
except Exception:
Logger.print_error("Error while getting the latest unstable tag")
raise