diff --git a/logger.py b/logger.py new file mode 100644 index 0000000..9ace1fc --- /dev/null +++ b/logger.py @@ -0,0 +1,12 @@ +class Logger: + def __init__(self): + self._log_func = print # Default to print if not initialized + + def init_logger(self, log_func): + self._log_func = log_func + + def log(self, message): + self._log_func(message) + +# Global instance +app_logger = Logger() diff --git a/lx-icons/128/log.png b/lx-icons/128/log.png index 06de63c..7266073 100644 Binary files a/lx-icons/128/log.png and b/lx-icons/128/log.png differ diff --git a/lx-icons/32/log.png b/lx-icons/32/log.png index ebc7be1..02be837 100644 Binary files a/lx-icons/32/log.png and b/lx-icons/32/log.png differ diff --git a/lx-icons/48/log.png b/lx-icons/48/log.png index 971a013..b03f2d3 100644 Binary files a/lx-icons/48/log.png and b/lx-icons/48/log.png differ diff --git a/lx-icons/64/log.png b/lx-icons/64/log.png index e1eb8dc..f1f00e3 100644 Binary files a/lx-icons/64/log.png and b/lx-icons/64/log.png differ diff --git a/ssl_encrypt.py b/ssl_encrypt.py index fd581bf..23393b0 100755 --- a/ssl_encrypt.py +++ b/ssl_encrypt.py @@ -2,27 +2,27 @@ """ This Script encrypt Wireguardfiles for Wirepy users for more Security """ import argparse -import logging + from pathlib import Path import pwd import shutil from subprocess import CompletedProcess, run from shared_libs.wp_app_config import AppConfig -from shared_libs.common_tools import LogConfig + parser = argparse.ArgumentParser() parser.add_argument("--user", required=True, help="Username of the target file system") args = parser.parse_args() -LogConfig.logger(f"/home/{args.user}/.local/share/lxlogs/wirepy.log") + try: # Retrieve UID and GID user_info = pwd.getpwnam(args.user) uid = user_info.pw_uid # User ID (e.g., 1000) gid = user_info.pw_gid # Group ID (e.g., 1000) except KeyError: - logging.error(f"User '{args.user}' not found.") + exit(1) keyfile: Path = Path(f"/home/{args.user}/.config/wire_py/pbwgk.pem") @@ -50,13 +50,7 @@ if not keyfile.is_file(): # Output from Openssl Error if process.stderr: - if "writing RSA key" in process.stderr: - logging.info(f"{process.stderr}") - else: - logging.error(f"{process.stderr}") - - if process.returncode == 0: - logging.info("Public key generated successfully.") + pass shutil.chown(keyfile, uid, gid) @@ -86,4 +80,4 @@ if AppConfig.TEMP_DIR.exists() and any(AppConfig.TEMP_DIR.iterdir()): # Output from Openssl Error if process.stderr: - logging.error(process.stderr) + pass \ No newline at end of file diff --git a/start_wg.py b/start_wg.py index 6491666..56e73df 100755 --- a/start_wg.py +++ b/start_wg.py @@ -2,13 +2,13 @@ """ This script belongs to wirepy and is for the auto start of the tunnel """ -import logging + from subprocess import CompletedProcess, run from shared_libs.wp_app_config import AppConfig -from shared_libs.common_tools import ConfigManager, LogConfig +from shared_libs.common_tools import ConfigManager ConfigManager.init(AppConfig.SETTINGS_FILE) -LogConfig.logger(ConfigManager.get("logfile")) + if ConfigManager.get("autostart") != "off": process: CompletedProcess[str] = run( ["nmcli", "connection", "up", ConfigManager.get("autostart")], @@ -18,7 +18,7 @@ if ConfigManager.get("autostart") != "off": ) # Output from start_wg error if process.stderr: - logging.error(process.stderr) + else: pass diff --git a/tunnel.py b/tunnel.py index 6cd66f2..4bd2fca 100644 --- a/tunnel.py +++ b/tunnel.py @@ -1,5 +1,5 @@ #!/usr/bin/python3 -import logging +from logger import app_logger import getpass import zipfile from datetime import datetime @@ -25,6 +25,7 @@ class Tunnel: if filepath is not None: filepath = Path(filepath) + truncated_stem = None try: content = filepath.read_text() @@ -70,17 +71,17 @@ class Tunnel: content = secrets.token_bytes(len(content)) except StopIteration: - pass + return None, None elif directory is not None: if not directory.exists() or not directory.is_dir(): - logging.error( + app_logger.log( "Temp directory does not exist or is not a directory.") return None # Get a list of all files in the directory - files = [file for file in AppConfig.TEMP_DIR.iterdir() + files = [file for file in directory.iterdir() if file.is_file()] # Search for the string in the files @@ -146,12 +147,12 @@ class Tunnel: ) if process.stderr and "error" in process.stderr.lower(): - logging.error(f"Error output on nmcli: {process.stderr}") + app_logger.log(f"Error output on nmcli: {process.stderr}") except StopIteration: active = None except Exception as e: - logging.error(f"Error on nmcli: {e}") + app_logger.log(f"Error on nmcli: {e}") active = None return active if active is not None else "" @@ -189,7 +190,7 @@ class Tunnel: ) else: - logging.error( + app_logger.log( "There was a mistake at creating the Zip file. File is empty." ) MessageDialog( @@ -199,18 +200,18 @@ class Tunnel: return False return True except PermissionError: - logging.error( + app_logger.log( f"Permission denied when creating archive in {wg_tar}" ) return False except zipfile.BadZipFile as e: - logging.error(f"Invalid ZIP file: {e}") + app_logger.log(f"Invalid ZIP file: {e}") return False except TypeError: pass except Exception as e: - logging.error(f"Export failed: {str(e)}") + app_logger.log(f"Export failed: {str(e)}") MessageDialog( "error", Msg.STR["exp_try"], title=Msg.STR["exp_err"]) return False diff --git a/ui/__init__.py b/ui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ui/controls.py b/ui/controls.py new file mode 100644 index 0000000..44cd69a --- /dev/null +++ b/ui/controls.py @@ -0,0 +1,87 @@ +import tkinter as tk +from tkinter import ttk + +from shared_libs.common_tools import Tooltip +from shared_libs.wp_app_config import Msg +from tunnel import Tunnel + + +class Controls(ttk.Frame): + def __init__(self, container, image_manager, tooltip_state, import_callback, delete_callback, start_stop_callback, **kwargs): + super().__init__(container, **kwargs) + self.image_manager = image_manager + self.tooltip_state = tooltip_state + + self.columnconfigure(0, weight=1) + self.rowconfigure(0, weight=1) + self.rowconfigure(1, weight=1) + self.rowconfigure(2, weight=1) + self.rowconfigure(3, weight=1) + + self.btn_stst = ttk.Button( + self, + image=self.image_manager.get_icon("vpn_start_large"), + command=start_stop_callback, + padding=0, + ) + self.btn_stst.grid(column=0, row=0, sticky="ew") + Tooltip(self.btn_stst, Msg.TTIP["start_tl"], + state_var=self.tooltip_state) + + self.btn_i = ttk.Button( + self, + image=self.image_manager.get_icon("import_large"), + command=import_callback, + padding=0, + ) + self.btn_i.grid(column=0, row=1, sticky="ew") + Tooltip(self.btn_i, Msg.TTIP["import_tl"], + state_var=self.tooltip_state) + + self.btn_tr = ttk.Button( + self, + image=self.image_manager.get_icon("trash_large"), + command=delete_callback, + padding=0, + ) + self.btn_tr.grid(column=0, row=2, sticky="ew") + Tooltip(self.btn_tr, Msg.TTIP["trash_tl"], + state_var=self.tooltip_state) + + self.btn_exp = ttk.Button( + self, + image=self.image_manager.get_icon("export_large"), + command=lambda: Tunnel.export(), + padding=0, + ) + self.btn_exp.grid(column=0, row=3, sticky="ew") + Tooltip(self.btn_exp, Msg.TTIP["export_tl"], + state_var=self.tooltip_state) + + def set_start_stop_button_state(self, is_active, list_box_size): + if is_active: + self.btn_stst.config( + image=self.image_manager.get_icon("vpn_stop_large")) + Tooltip(self.btn_stst, + Msg.TTIP["stop_tl"], state_var=self.tooltip_state) + else: + self.btn_stst.config( + image=self.image_manager.get_icon("vpn_start_large")) + if list_box_size == 0: + Tooltip(self.btn_stst, + Msg.TTIP["empty_list"], state_var=self.tooltip_state) + else: + Tooltip(self.btn_stst, + Msg.TTIP["start_tl"], state_var=self.tooltip_state) + + def update_tooltips(self, list_box_size): + if list_box_size == 0: + Tooltip(self.btn_tr, + Msg.TTIP["trash_tl_info"], state_var=self.tooltip_state) + Tooltip(self.btn_exp, + Msg.TTIP["export_tl_info"], state_var=self.tooltip_state) + else: + Tooltip(self.btn_tr, Msg.TTIP["trash_tl"], + state_var=self.tooltip_state) + Tooltip(self.btn_exp, + Msg.TTIP["export_tl"], state_var=self.tooltip_state) diff --git a/ui/header.py b/ui/header.py new file mode 100644 index 0000000..4cf4d3f --- /dev/null +++ b/ui/header.py @@ -0,0 +1,58 @@ +import tkinter as tk +from shared_libs.wp_app_config import AppConfig, Msg + + +class Header(tk.Frame): + def __init__(self, container, image_manager, **kwargs): + super().__init__(container, bg="#2c3e50", **kwargs) + + self.image_manager = image_manager + + wg_icon_header_frame = tk.Frame(self, bg="#2c3e50") + wg_icon_header_frame.grid(column=0, row=0, rowspan=2, sticky="w") + + wg_icon_header_label = tk.Label( + wg_icon_header_frame, + image=self.image_manager.get_icon("vpn_small"), + bg="#2c3e50", + ) + wg_icon_header_label.grid(column=0, row=0, sticky="e", ipadx=10) + + self.header_label = tk.Label( + self, + text=Msg.STR["lx_tools"], + font=("Helvetica", 12, "bold"), + fg="#ffffff", + bg="#2c3e50", + ) + self.header_label.grid( + column=1, + row=0, + sticky="w", + padx=(5, 20), + pady=(15, 5), + ipady=4, + ) + + self.version_label = tk.Label( + self, + text=f"{AppConfig.VERSION} • {Msg.STR['header_left_bottom']}", + font=("Helvetica", 9), + fg="#bdc3c7", + bg="#2c3e50", + ) + self.version_label.grid( + column=1, row=1, sticky="w", padx=(5, 20), pady=(0, 10)) + + info_label = tk.Label( + self, + text=Msg.STR["header_right_top"], + font=("Helvetica", 10), + fg="#ecf0f1", + bg="#2c3e50", + ) + info_label.grid(column=2, row=0, sticky="ne", + padx=(10, 10), pady=(10, 0)) + + self.columnconfigure(1, weight=1, pad=2) + self.rowconfigure(0, weight=1) diff --git a/ui/log_window.py b/ui/log_window.py new file mode 100644 index 0000000..e6533f4 --- /dev/null +++ b/ui/log_window.py @@ -0,0 +1,47 @@ +import tkinter as tk +from tkinter import ttk +from datetime import datetime + +class LogWindow(tk.Toplevel): + def __init__(self, parent): + super().__init__(parent) + self.title("Log") + self.geometry("600x400") + self.protocol("WM_DELETE_WINDOW", self.withdraw) # Hide on close + + log_container = tk.Frame(self) + log_container.pack(fill="both", expand=True, padx=10, pady=10) + + self.log_text = tk.Text( + log_container, + wrap=tk.WORD, + font=("Consolas", 9), + bg="#1e1e1e", + fg="#d4d4d4", + insertbackground="white", + selectbackground="#264f78", + ) + + log_scrollbar = ttk.Scrollbar( + log_container, orient="vertical", command=self.log_text.yview + ) + self.log_text.configure(yscrollcommand=log_scrollbar.set) + + self.log_text.pack(side="left", fill="both", expand=True) + log_scrollbar.pack(side="right", fill="y") + + self.withdraw() # Start hidden + + def log_message(self, message): + """Add message to log""" + timestamp = datetime.now().strftime("%H:%M:%S") + log_entry = f"[{timestamp}] {message}\n" + self.log_text.insert(tk.END, log_entry) + self.log_text.see(tk.END) + self.log_text.update() + + def toggle(self): + if self.winfo_viewable(): + self.withdraw() + else: + self.deiconify() diff --git a/ui/main_frame.py b/ui/main_frame.py new file mode 100644 index 0000000..fd23846 --- /dev/null +++ b/ui/main_frame.py @@ -0,0 +1,272 @@ + +import getpass +import shutil +import tkinter as tk +from functools import partial +from pathlib import Path +from subprocess import CompletedProcess, run +from tkinter import ttk +from shared_libs.custom_file_dialog import CustomFileDialog + +from shared_libs.common_tools import ( + LxTools, + CryptoUtil, + ConfigManager, + ThemeManager, +) +from shared_libs.message import MessageDialog +from shared_libs.wp_app_config import AppConfig, Msg +from tunnel import Tunnel +from .header import Header +from .menu_bar import MenuBar +from .controls import Controls +from .tunnel_list import TunnelList +from .tunnel_details import TunnelDetails +from .status_panel import StatusPanel + + +from logger import app_logger + + +class MainFrame(ttk.Frame): + def __init__(self, container, image_manager, toggle_log_window, **kwargs): + super().__init__(container, **kwargs) + self.image_manager = image_manager + + self.columnconfigure(1, weight=1) + self.columnconfigure(2, weight=18) + self.rowconfigure(2, weight=1) + + self.tooltip_state = tk.BooleanVar() + state = ConfigManager.get("tooltips") + self.tooltip_state.set(str(state) == "True") + + self.active_tunnel_name = Tunnel.get_active() + self.tunnels = Tunnel.parse_files_to_dictionary( + directory=AppConfig.TEMP_DIR) + if self.tunnels is None: + self.tunnels = {} + LxTools.clean_files(AppConfig.TEMP_DIR, file=None) + AppConfig.ensure_directories() + + # Create components + self.header = Header(self, self.image_manager) + self.menu_bar = MenuBar(self, self.image_manager, + self.tooltip_state, self.on_theme_toggle, + toggle_log_window) + self.controls = Controls( + self, self.image_manager, self.tooltip_state, self.import_sl, self.delete, self.wg_switch) + self.tunnel_list = TunnelList(self, self.on_tunnel_select) + self.tunnel_details = TunnelDetails(self) + self.status_panel = StatusPanel( + self, self.tooltip_state, self.tl_rename, self.box_set) + + # Layout components + self.header.grid(column=0, columnspan=3, row=0, sticky="nsew") + self.menu_bar.grid(column=0, columnspan=3, row=1, sticky="we") + self.controls.grid(column=0, row=2, sticky="nsew", padx=(15, 0)) + self.tunnel_list.grid(column=1, row=2, sticky="nsew") + self.tunnel_details.grid(column=2, row=2, sticky="nsew") + self.status_panel.grid(column=0, row=3, columnspan=3, sticky="nsew") + + self.tunnel_list.populate(self.tunnels.keys()) + self.update_ui_state() + + def on_theme_toggle(self): + current_theme = ConfigManager.get("theme") + new_theme = "dark" if current_theme == "light" else "light" + ThemeManager.change_theme(self, new_theme, new_theme) + self.header.header_label.config(fg="#ffffff") + self.tunnel_details.color_label() + self.menu_bar.update_theme_label() + self.menu_bar.settings.entryconfigure( + 2, label=self.menu_bar.theme_label.get()) + + def update_ui_state(self): + self.active_tunnel_name = Tunnel.get_active() + self.controls.set_start_stop_button_state( + bool(self.active_tunnel_name), self.tunnel_list.get_size()) + self.controls.update_tooltips(self.tunnel_list.get_size()) + self.tunnel_details.update_details( + self.active_tunnel_name, self.tunnels) + self.status_panel.enable_controls( + self.tunnel_list.get_size(), bool(self.tunnel_list.get_selected())) + self.status_panel.update_autoconnect_display() + + def on_tunnel_select(self, event=None): + self.status_panel.enable_controls(self.tunnel_list.get_size(), True) + + def wg_switch(self): + try: + if not self.active_tunnel_name: + selected_tunnel = self.tunnel_list.get_selected() + if not selected_tunnel: + MessageDialog( + "info", Msg.STR["sel_list"], title=Msg.STR["sel_tl"]) + return + self.handle_connection_state("start", selected_tunnel) + else: + self.handle_connection_state("stop") + except IndexError: + MessageDialog("info", Msg.STR["tl_first"], title=Msg.STR["sel_tl"]) + + def handle_connection_state(self, action: str, tunnel_name: str = None): + cmd = [] + if action == "stop": + if self.active_tunnel_name: + cmd = ["nmcli", "connection", "down", self.active_tunnel_name] + elif action == "start" and tunnel_name: + cmd = ["nmcli", "connection", "up", tunnel_name] + + if cmd: + process: CompletedProcess[str] = run( + cmd, capture_output=True, text=True, check=False) + if process.stderr: + app_logger.log(f"{process.stderr} Code: {process.returncode}") + + self.update_ui_state() + + def import_sl(self): + try: + dialog = CustomFileDialog( + self, + initial_dir=f"{Path.home()}", + title="Select Wireguard config File", + filetypes=[("WG config files", "*.conf")], + dialog_mode="open" + ) + self.wait_window(dialog) + filepath = dialog.get_selected_file() + if not filepath: + return + + data_import, key_name = Tunnel.parse_files_to_dictionary( + filepath=filepath) + + if CryptoUtil.find_key(f"{data_import[key_name]['PrivateKey']}="): + MessageDialog( + "error", Msg.STR["tl_exist"], title=Msg.STR["imp_err"]) + return + + if not CryptoUtil.is_valid_base64(f"{data_import[key_name]['PrivateKey']}="): + MessageDialog( + "error", Msg.STR["invalid_base64"], title=Msg.STR["imp_err"]) + return + + filepath = Path(filepath) + truncated_name = ( + filepath.name[-17:] if len(filepath.name) > 17 else filepath.name) + import_file = shutil.copy2( + filepath, AppConfig.TEMP_DIR / truncated_name) + import_file = Path(import_file) + + if self.active_tunnel_name: + self.handle_connection_state("stop") + + process: CompletedProcess[str] = run( + ["nmcli", "connection", "import", "type", + "wireguard", "file", import_file], + capture_output=True, text=True, check=False) + if process.stderr: + app_logger.log(f"{process.stderr} Code: {process.returncode}") + + CryptoUtil.encrypt(getpass.getuser()) + CryptoUtil.decrypt(getpass.getuser()) # Decrypt all files again + self.active_tunnel_name = Tunnel.get_active() + self.tunnels = Tunnel.parse_files_to_dictionary( + directory=AppConfig.TEMP_DIR) # Read from TEMP_DIR + if self.tunnels is None: + self.tunnels = {} + self.tunnel_list.populate(self.tunnels.keys()) + self.tunnel_list.set_selection(0) + + run(["nmcli", "con", "mod", self.active_tunnel_name, + "connection.autoconnect", "no"], check=False) + self.update_ui_state() + LxTools.clean_files(AppConfig.TEMP_DIR, file=None) + + except (UnboundLocalError, TypeError, FileNotFoundError): + MessageDialog( + "error", Msg.STR["no_valid_file"], title=Msg.STR["imp_err"]) + except Exception as e: + app_logger.log(f"Import failed: {e}") + + def delete(self): + try: + select_tl = self.tunnel_list.get_selected() + if not select_tl: + MessageDialog( + "info", Msg.STR["sel_list"], title=Msg.STR["sel_tl"]) + return + + run(["nmcli", "connection", "delete", select_tl], check=False) + Path.unlink(f"{AppConfig.CONFIG_DIR}/{select_tl}.dat") + + if select_tl == ConfigManager.get("autostart"): + ConfigManager.set("autostart", "off") + + self.tunnel_list.delete_selected() + del self.tunnels[select_tl] + self.update_ui_state() + + except IndexError: + MessageDialog("info", Msg.STR["tl_first"], title=Msg.STR["sel_tl"]) + + def box_set(self): + try: + select_tl = self.tunnel_list.get_selected() + if self.status_panel.selected_option.get() == 1 and select_tl: + ConfigManager.set("autostart", select_tl) + else: + ConfigManager.set("autostart", "off") + except IndexError: + self.status_panel.selected_option.set(0) + self.update_ui_state() + + def tl_rename(self): + special_characters = ["\\", "/", "{", "}", " "] + new_name = self.status_panel.get_rename_value() + + if len(new_name) > 12: + MessageDialog("info", Msg.STR["sign_len"], + title=Msg.STR["ren_err"]) + return + if len(new_name) == 0: + MessageDialog( + "info", Msg.STR["zero_signs"], title=Msg.STR["ren_err"]) + return + if any(ch in special_characters for ch in new_name): + MessageDialog( + "info", Msg.STR["false_signs"], title=Msg.STR["ren_err"]) + return + if new_name in self.tunnels: + MessageDialog( + "info", Msg.STR["is_in_use"], title=Msg.STR["ren_err"]) + return + + try: + old_name = self.tunnel_list.get_selected() + if not old_name: + MessageDialog( + "info", Msg.STR["sel_list"], title=Msg.STR["ren_err"]) + return + + run(["nmcli", "connection", "modify", old_name, + "connection.id", new_name], check=False) + + source = Path(f"{AppConfig.CONFIG_DIR}/{old_name}.dat") + destination = AppConfig.CONFIG_DIR / f"{new_name}.dat" + source.replace(destination) + + self.tunnels[new_name] = self.tunnels.pop(old_name) + if old_name == ConfigManager.get("autostart"): + ConfigManager.set("autostart", new_name) + + self.tunnel_list.delete_selected() + self.tunnel_list.populate(self.tunnels.keys()) + self.status_panel.clear_rename_entry() + self.update_ui_state() + + except IndexError: + MessageDialog("info", Msg.STR["sel_list"], + title=Msg.STR["ren_err"]) diff --git a/ui/menu_bar.py b/ui/menu_bar.py new file mode 100644 index 0000000..94bfd48 --- /dev/null +++ b/ui/menu_bar.py @@ -0,0 +1,179 @@ + +import os +import subprocess +import tkinter as tk +import webbrowser +from functools import partial +from pathlib import Path +from tkinter import ttk + +from logger import app_logger + +from shared_libs.common_tools import ConfigManager, Tooltip +from shared_libs.gitea import GiteaUpdate +from shared_libs.message import MessageDialog +from shared_libs.wp_app_config import AppConfig, Msg + + +class MenuBar(ttk.Frame): + def __init__(self, container, image_manager, tooltip_state, on_theme_toggle, toggle_log_window, **kwargs): + super().__init__(container, **kwargs) + self.image_manager = image_manager + self.tooltip_state = tooltip_state + self.on_theme_toggle = on_theme_toggle + + self.options_btn = ttk.Menubutton(self, text=Msg.STR["options"]) + self.options_btn.grid(column=0, row=0) + + Tooltip(self.options_btn, Msg.TTIP["settings"], state_var=self.tooltip_state) + + self.set_update = tk.IntVar() + self.settings = tk.Menu(self, relief="flat") + self.options_btn.configure(menu=self.settings, style="Toolbutton") + self.settings.add_checkbutton( + label=Msg.STR["disable_updates"], + command=lambda: self.update_setting(self.set_update.get()), + variable=self.set_update, + ) + self.update_label = tk.StringVar() + self.updates_lb = ttk.Label(self, textvariable=self.update_label) + self.updates_lb.grid(column=2, row=0) + self.updates_lb.grid_remove() + self.update_tooltip = tk.StringVar() + self.update_foreground = tk.StringVar(value="red") + self.update_label.trace_add("write", self.update_label_display) + self.update_foreground.trace_add("write", self.update_label_display) + res = GiteaUpdate.api_down( + AppConfig.UPDATE_URL, AppConfig.VERSION, ConfigManager.get("updates") + ) + self.update_ui_for_update(res) + + # Tooltip Menu + self.tooltip_label = tk.StringVar() + self.tooltip_update_label() + self.settings.add_command( + label=self.tooltip_label.get(), command=self.tooltips_toggle + ) + # Label show dark or light + self.theme_label = tk.StringVar() + self.update_theme_label() + self.settings.add_command( + label=self.theme_label.get(), command=self.on_theme_toggle + ) + + # About BTN Menu / Label + self.about_btn = ttk.Button( + self, text=Msg.STR["about"], style="Toolbutton", command=self.about + ) + self.about_btn.grid(column=1, row=0) + + self.columnconfigure(10, weight=1) # Add a column with weight to push button to the right + + self.log_btn = ttk.Button( + self, + image=self.image_manager.get_icon("log_small"), + style="Toolbutton", + command=toggle_log_window, + ) + self.log_btn.grid(column=11, row=0, sticky='e') + Tooltip(self.log_btn, "Show Log", state_var=self.tooltip_state) + + def update_label_display(self, *args): + self.updates_lb.configure(foreground=self.update_foreground.get()) + if self.update_label.get(): + self.updates_lb.grid(column=4, columnspan=3, row=0, padx=10) + else: + self.updates_lb.grid_remove() + + def updater(self): + tmp_dir = Path("/tmp/lxtools") + Path.mkdir(tmp_dir, exist_ok=True) + os.chdir(tmp_dir) + result = subprocess.run(["/usr/local/bin/lxtools_installer"], check=False) + if result.returncode != 0: + MessageDialog("error", result.stderr) + + def update_ui_for_update(self, res): + if hasattr(self, "update_btn"): + self.update_btn.grid_forget() + delattr(self, "update_btn") + + if res == "False": + self.set_update.set(value=1) + self.update_label.set(Msg.STR["update_search_off"]) + self.update_tooltip.set(Msg.TTIP["updates_disabled"]) + self.update_foreground.set("") + Tooltip(self.updates_lb, self.update_tooltip.get(), state_var=self.tooltip_state) + elif res == "No Internet Connection!": + self.update_label.set(Msg.STR["no_server_connection"]) + self.update_foreground.set("red") + Tooltip(self.updates_lb, Msg.TTIP["no_server_conn_tt"], state_var=self.tooltip_state) + elif res == "No Updates": + self.update_label.set(Msg.STR["no_updates"]) + self.update_tooltip.set(Msg.TTIP["up_to_date"]) + self.update_foreground.set("") + Tooltip(self.updates_lb, self.update_tooltip.get(), state_var=self.tooltip_state) + else: + self.set_update.set(value=0) + self.update_label.set("") + self.update_btn = ttk.Button( + self, + image=self.image_manager.get_icon("settings"), + style="Toolbutton", + command=self.updater, + ) + self.update_btn.grid(column=5, row=0, padx=0) + Tooltip(self.update_btn, Msg.TTIP["install_new_version"], state_var=self.tooltip_state) + + @staticmethod + def about() -> None: + MessageDialog( + "info", + Msg.STR["about_msg"], + buttons=["OK", Msg.STR["goto_git"]], + title=Msg.STR["info"], + commands=[ + None, + partial(webbrowser.open, "https://git.ilunix.de/punix/Wire-Py"), + ], + icon="/usr/share/icons/lx-icons/64/wg_vpn.png", + wraplength=420, + ) + + def update_setting(self, update_res) -> None: + if update_res == 1: + ConfigManager.set("updates", "off") + self.update_ui_for_update("False") + else: + ConfigManager.set("updates", "on") + try: + res = GiteaUpdate.api_down(AppConfig.UPDATE_URL, AppConfig.VERSION, "on") + if hasattr(self, "update_btn"): + self.update_btn.grid_forget() + if hasattr(self, "updates_lb"): + self.updates_lb.grid_forget() + self.update_ui_for_update(res) + except Exception as e: + app_logger.log(f"Error checking for updates: {e}") + self.update_ui_for_update("No Internet Connection!") + + def tooltip_update_label(self) -> None: + if self.tooltip_state.get(): + self.tooltip_label.set(Msg.STR["disable_tooltips"]) + else: + self.tooltip_label.set(Msg.STR["enable_tooltips"]) + + def tooltips_toggle(self): + new_bool_state = not self.tooltip_state.get() + ConfigManager.set("tooltips", str(new_bool_state)) + self.tooltip_state.set(new_bool_state) + self.tooltip_update_label() + self.settings.entryconfigure(1, label=self.tooltip_label.get()) + + def update_theme_label(self) -> None: + current_theme = ConfigManager.get("theme") + if current_theme == "light": + self.theme_label.set(Msg.STR["dark"]) + else: + self.theme_label.set(Msg.STR["light"]) + diff --git a/ui/status_panel.py b/ui/status_panel.py new file mode 100644 index 0000000..d8692a8 --- /dev/null +++ b/ui/status_panel.py @@ -0,0 +1,100 @@ +import tkinter as tk +from tkinter import ttk + +from shared_libs.common_tools import Tooltip, ConfigManager +from shared_libs.wp_app_config import Msg + + +class StatusPanel(ttk.Frame): + def __init__(self, container, tooltip_state, rename_callback, autoconnect_callback, **kwargs): + super().__init__(container, **kwargs) + + self.columnconfigure(1, weight=1) + + self.tooltip_state = tooltip_state + + # Autoconnect Frame + autoconnect_frame = ttk.Frame(self) + autoconnect_frame.grid(column=0, row=0, sticky="w") + + self.selected_option = tk.IntVar() + self.wg_autostart = ttk.Checkbutton( + autoconnect_frame, + text=Msg.STR["autoconnect_on"], + variable=self.selected_option, + command=autoconnect_callback, + ) + self.wg_autostart.grid(column=0, row=0, pady=5, + padx=(10, 0), sticky="ew") + Tooltip(self.wg_autostart, + Msg.TTIP["autostart_info"], state_var=self.tooltip_state) + + self.autoconnect_var = tk.StringVar() + self.autoconnect_label = ttk.Label( + autoconnect_frame, + textvariable=self.autoconnect_var, + foreground="#0071ff", + width=18, + ) + self.autoconnect_label.config(font=("Ubuntu", 11)) + self.autoconnect_label.grid(column=1, row=0, sticky="ew", pady=5) + + # Rename Frame + rename_frame = ttk.Frame(self) + rename_frame.grid(column=1, row=0, sticky="e", padx=10) + + self.lb_rename = ttk.Entry(rename_frame) + self.lb_rename.grid(column=0, row=0, padx=8, pady=10, sticky="ne") + self.lb_rename.config(width=15) + self.lb_rename.insert(0, Msg.STR["max_chars"]) + self.lb_rename.config(state="disable") + Tooltip(self.lb_rename, + Msg.TTIP["rename_tl_info"], state_var=self.tooltip_state) + + self.btn_rename = ttk.Button( + rename_frame, + text=Msg.STR["rename"], + state="disable", + command=rename_callback, + width=15, + ) + self.btn_rename.grid(column=1, row=0, pady=10, sticky="nsew") + + self.update_autoconnect_display() + + def update_autoconnect_display(self): + autostart_tunnel = ConfigManager.get("autostart") + if autostart_tunnel and autostart_tunnel != "off": + self.selected_option.set(1) + self.autoconnect_var.set(autostart_tunnel) + else: + self.selected_option.set(0) + self.autoconnect_var.set(Msg.STR["no_autoconnect"]) + + def get_rename_value(self): + return self.lb_rename.get() + + def clear_rename_entry(self): + self.lb_rename.delete(0, tk.END) + + def enable_controls(self, list_box_size, is_selection): + if list_box_size > 0: + self.wg_autostart.config(state="normal") + Tooltip(self.wg_autostart, + Msg.TTIP["autostart"], state_var=self.tooltip_state) + if is_selection: + self.lb_rename.config(state="normal") + self.btn_rename.config(state="normal") + self.lb_rename.delete(0, tk.END) + Tooltip(self.lb_rename, + Msg.TTIP["rename_tl"], state_var=self.tooltip_state) + else: + self.wg_autostart.config(state="disabled") + self.lb_rename.config(state="disabled") + self.btn_rename.config(state="disabled") + self.lb_rename.delete(0, tk.END) + self.lb_rename.insert(0, Msg.STR["max_chars"]) + Tooltip(self.wg_autostart, + Msg.TTIP["autostart_info"], state_var=self.tooltip_state) + Tooltip(self.lb_rename, + Msg.TTIP["rename_tl_info"], state_var=self.tooltip_state) diff --git a/ui/tunnel_details.py b/ui/tunnel_details.py new file mode 100644 index 0000000..e4380dc --- /dev/null +++ b/ui/tunnel_details.py @@ -0,0 +1,73 @@ +import tkinter as tk +from tkinter import ttk + +from shared_libs.common_tools import ConfigManager +from shared_libs.wp_app_config import Msg + + +class TunnelDetails(ttk.Frame): + def __init__(self, container, **kwargs): + super().__init__(container, **kwargs) + + self.columnconfigure(0, weight=1) + self.rowconfigure(0, weight=1) + self.rowconfigure(1, weight=1) + self.rowconfigure(2, weight=1) + + self.active_frame = ttk.LabelFrame(self, text=Msg.STR["active_tunnel"]) + self.active_frame.grid(column=0, row=0, sticky="nsew", padx=10, pady=5) + self.active_frame.columnconfigure(0, weight=1) + self.active_frame.rowconfigure(0, weight=1) + + self.interface_frame = ttk.LabelFrame(self, text=Msg.STR["interface"]) + self.interface_frame.grid(column=0, row=1, sticky="nsew", padx=10, pady=5) + self.interface_frame.columnconfigure(0, weight=1) + self.interface_frame.rowconfigure(0, weight=1) + self.interface_frame.rowconfigure(1, weight=1) + + self.peer_frame = ttk.LabelFrame(self, text=Msg.STR["peer"]) + self.peer_frame.grid(column=0, row=2, sticky="nsew", padx=10, pady=5) + self.peer_frame.columnconfigure(0, weight=1) + self.peer_frame.rowconfigure(0, weight=1) + + self.str_var = tk.StringVar() + self.add_var = tk.StringVar() + self.dns_var = tk.StringVar() + self.enp_var = tk.StringVar() + + self.lb_tunnel = None + self.color_label() + + self.address_label = ttk.Label(self.interface_frame, textvariable=self.add_var, foreground="#0071ff") + self.address_label.grid(column=0, row=0, sticky="nsew", padx=10, pady=(0, 20)) + self.address_label.config(font=("Ubuntu", 9)) + + self.dns_label = ttk.Label(self.interface_frame, textvariable=self.dns_var, foreground="#0071ff") + self.dns_label.grid(column=0, row=1, sticky="nsew", padx=10, pady=(0, 20)) + self.dns_label.config(font=("Ubuntu", 9)) + + self.endpoint_label = ttk.Label(self.peer_frame, textvariable=self.enp_var, foreground="#0071ff") + self.endpoint_label.grid(column=0, row=0, sticky="nsew", padx=10, pady=(0, 30)) + self.endpoint_label.config(font=("Ubuntu", 9)) + + def color_label(self): + if self.lb_tunnel: + self.lb_tunnel.destroy() + + foreground = "yellow" if ConfigManager.get("theme") != "light" else "green" + self.lb_tunnel = ttk.Label(self.active_frame, textvariable=self.str_var, foreground=foreground) + self.lb_tunnel.config(font=("Ubuntu", 11, "bold")) + self.lb_tunnel.grid(column=0, row=0, padx=10, pady=(0, 10), sticky="nsew") + + def update_details(self, active_tunnel_name, tunnel_data): + self.str_var.set(active_tunnel_name) + if active_tunnel_name and tunnel_data and active_tunnel_name in tunnel_data: + values = tunnel_data[active_tunnel_name] + self.add_var.set(f"Address: {values.get('Address', '')}") + self.dns_var.set(f" DNS: {values.get('DNS', '')}") + self.enp_var.set(f"Endpoint: {values.get('Endpoint', '')}") + else: + self.add_var.set("") + self.dns_var.set("") + self.enp_var.set("") + self.color_label() diff --git a/ui/tunnel_list.py b/ui/tunnel_list.py new file mode 100644 index 0000000..94838e1 --- /dev/null +++ b/ui/tunnel_list.py @@ -0,0 +1,57 @@ +import tkinter as tk +from tkinter import ttk + +from shared_libs.wp_app_config import Msg + + +class TunnelList(ttk.Frame): + def __init__(self, container, selection_callback, **kwargs): + super().__init__(container, **kwargs) + + self.list_frame = ttk.LabelFrame(self, text=Msg.STR["tunnels"]) + self.list_frame.grid( + column=0, row=0, sticky="nsew", padx=(10, 0), pady=(0, 8)) + + self.list_box = tk.Listbox(self.list_frame, selectmode="single") + self.list_box.config( + relief="flat", + font=("Ubuntu", 12, "bold"), + ) + self.list_box.grid(column=0, row=0, sticky="nsew") + self.list_box.event_add("<>", "") + self.list_box.bind("<>", selection_callback) + + self.scrollbar = ttk.Scrollbar( + self.list_frame, orient="vertical", command=self.list_box.yview + ) + self.scrollbar.grid(column=1, row=0, sticky="ns") + self.list_box.configure(yscrollcommand=self.scrollbar.set) + + self.columnconfigure(0, weight=1) + self.rowconfigure(0, weight=1) + self.list_frame.columnconfigure(0, weight=1) + self.list_frame.rowconfigure(0, weight=1) + + def populate(self, tunnels): + self.list_box.delete(0, tk.END) + for tunnel in tunnels: + self.list_box.insert("end", tunnel) + self.list_box.update() + + def get_selected(self): + selection_indices = self.list_box.curselection() + if not selection_indices: + return None + return self.list_box.get(selection_indices[0]) + + def get_size(self): + return self.list_box.size() + + def set_selection(self, index): + self.list_box.selection_clear(0, tk.END) + self.list_box.selection_set(index) + + def delete_selected(self): + selection_indices = self.list_box.curselection() + if selection_indices: + self.list_box.delete(selection_indices[0]) diff --git a/wirepy.py b/wirepy.py index 49a1f25..588bfb0 100755 --- a/wirepy.py +++ b/wirepy.py @@ -2,32 +2,22 @@ """ this script is a simple GUI for managing Wireguard Tunnels """ -import logging import getpass -import shutil import sys -import os -import subprocess import tkinter as tk -import webbrowser -from functools import partial -from pathlib import Path -from subprocess import CompletedProcess, run -from tkinter import TclError, filedialog, ttk -from tunnel import Tunnel -from shared_libs.message import MessageDialog -from shared_libs.gitea import GiteaUpdate +from tkinter import TclError + from shared_libs.common_tools import ( LxTools, CryptoUtil, - LogConfig, ConfigManager, ThemeManager, - Tooltip, IconManager, ) - -from shared_libs.wp_app_config import AppConfig, Msg +from shared_libs.wp_app_config import AppConfig +from ui.main_frame import MainFrame +from ui.log_window import LogWindow +from logger import app_logger class Wirepy(tk.Tk): @@ -70,11 +60,16 @@ class Wirepy(tk.Tk): icon = self.image_manager.get_icon("vpn_small") if icon: self.iconphoto(True, icon) - except: + except Exception: pass + self.log_window = LogWindow(self) + app_logger.init_logger(self.log_window.log_message) + # Add the widgets - FrameWidgets(self).grid() + main_frame = MainFrame(self, image_manager=self.image_manager, + toggle_log_window=self.toggle_log_window) + main_frame.grid(sticky="nsew") self.columnconfigure(0, weight=1) self.rowconfigure(0, weight=1) @@ -84,1123 +79,8 @@ class Wirepy(tk.Tk): # Now show the window after it has been positioned self.after(10, self.deiconify) - -class FrameWidgets(ttk.Frame): - """ - ttk frame class for better structure - """ - - def __init__(self, container, **kwargs): - super().__init__(container, **kwargs) - - self.lb_tunnel = None - self.btn_stst = None - self.endpoint = None - self.dns = None - self.address = None - self.auto_con = None - self.image_manager = IconManager() - self.columnconfigure(0, weight=1) - self.rowconfigure(0, weight=1) - self.columnconfigure(1, weight=1) - self.rowconfigure(1, weight=1) - self.columnconfigure(2, weight=18) - self.rowconfigure(2, weight=1) - self.rowconfigure(3, weight=1) - - # StringVar-Variables initialization - self.tooltip_state = tk.BooleanVar() - # Get value from configuration - state = ConfigManager.get("tooltips") - # NOTE: ConfigManager.get("tooltips") can return either a boolean value or a string, - # depending on whether the value was loaded from the file (bool) or the default value is used (string). - # The expression 'lines[5].strip() == "True"' in ConfigManager.load() converts the string to a boolean. - # Convert to boolean and set - if isinstance(state, bool): - # If it's already a boolean, use directly - self.tooltip_state.set(state) - else: - # If it's a string or something else - self.tooltip_state.set(str(state) == "True") - - self.tooltip_label = ( - tk.StringVar() - ) # StringVar-Variable for tooltip label for view Disabled/Enabled - self.tooltip_update_label() - self.update_label = tk.StringVar() # StringVar-Variable for update label - self.update_tooltip = ( - tk.StringVar() - ) # StringVar-Variable for update tooltip please not remove! - self.update_foreground = tk.StringVar(value="red") - - # Frame for Menu - self.menu_frame = ttk.Frame(self) - self.menu_frame.grid(column=0, columnspan=3, row=1, sticky="we") - - self.options_btn = ttk.Menubutton( - self.menu_frame, text=Msg.STR["options"]) - self.options_btn.grid(column=0, row=0) - - Tooltip(self.options_btn, - Msg.TTIP["settings"], state_var=self.tooltip_state) - - self.set_update = tk.IntVar() - self.settings = tk.Menu(self, relief="flat") - self.options_btn.configure(menu=self.settings, style="Toolbutton") - self.settings.add_checkbutton( - label=Msg.STR["disable_updates"], - command=lambda: self.update_setting(self.set_update.get()), - variable=self.set_update, - ) - self.updates_lb = ttk.Label( - self.menu_frame, textvariable=self.update_label) - self.updates_lb.grid(column=2, row=0) - self.updates_lb.grid_remove() - self.update_label.trace_add("write", self.update_label_display) - self.update_foreground.trace_add("write", self.update_label_display) - res = GiteaUpdate.api_down( - AppConfig.UPDATE_URL, AppConfig.VERSION, ConfigManager.get( - "updates") - ) - self.update_ui_for_update(res) - - # Tooltip Menu - self.settings.add_command( - label=self.tooltip_label.get(), command=self.tooltips_toggle - ) - # Label show dark or light - self.theme_label = tk.StringVar() - self.update_theme_label() - self.settings.add_command( - label=self.theme_label.get(), command=self.on_theme_toggle - ) - # Logviewer Menu - self.settings.add_command( - label="Log Viewer", - command=lambda: run(["logviewer", "--modul=wp_app_config"]), - ) - # About BTN Menu / Label - self.about_btn = ttk.Button( - self.menu_frame, text=Msg.STR["about"], style="Toolbutton", command=self.about - ) - self.about_btn.grid(column=1, row=0) - - self.a = Tunnel.get_active() - - # Header Frame - # Festlegen der Farbe - self.header_frame = tk.Frame(self, bg="#2c3e50") - self.wg_icon_header_frame = tk.Frame(self.header_frame, bg="#2c3e50") - self.header_label = tk.Label( - self.header_frame, - text=Msg.STR["lx_tools"], - font=("Helvetica", 12, "bold"), - fg="#ffffff", - bg="#2c3e50", - ) - - self.version_label = tk.Label( - self.header_frame, - text=f"{AppConfig.VERSION} • {Msg.STR['header_left_bottom']}", - font=("Helvetica", 9), - fg="#bdc3c7", - bg="#2c3e50", - ) - self.info_label = tk.Label( - self.header_frame, - text=Msg.STR["header_right_top"], - font=("Helvetica", 10), - fg="#ecf0f1", - bg="#2c3e50", - ) - self.header_frame.grid(column=0, columnspan=3, row=0, sticky="nsew") - self.wg_icon_header_frame.grid(column=0, row=0, rowspan=2, sticky="w") - - self.wg_icon_header_label = tk.Label( - self.wg_icon_header_frame, - image=self.image_manager.get_icon("vpn_small"), - bg="#2c3e50", - ) - self.wg_icon_header_label.grid(column=0, row=0, sticky="e", ipadx=10) - - self.header_label.grid( - column=1, - row=0, - sticky="w", - padx=(5, 20), - pady=(15, 5), - ipady=4, - ) - self.version_label.grid( - column=1, row=1, sticky="w", padx=(5, 20), pady=(0, 10)) - self.info_label.grid(column=2, row=0, sticky="ne", - padx=(10, 10), pady=(10, 0)) - self.header_frame.columnconfigure(1, weight=1, pad=2) - self.header_frame.rowconfigure(0, weight=1) - - # Frame for Control Buttons (Start, Stop, Import, Trash, Export) - self.control_buttons_frame = ttk.Frame(self) - self.control_buttons_frame.grid( - column=0, row=2, sticky="w", padx=(15, 0)) - self.control_buttons_frame.columnconfigure(0, weight=1) - self.control_buttons_frame.rowconfigure(2, weight=1) - - # Frame for Listbox and Scrollbar - self.list_container_frame = ttk.Frame(self) - self.list_container_frame.grid(column=1, row=2, sticky="nsew") - self.list_container_frame.columnconfigure(1, weight=1) - self.list_container_frame.rowconfigure(2, weight=1) - self.list_frame = ttk.LabelFrame( - self.list_container_frame, text=Msg.STR["tunnels"]) - self.list_frame.grid(column=0, row=0, sticky="nsew", padx=10, ipady=20) - # Listbox with Scrollbar - self.list_box = tk.Listbox(self.list_frame, selectmode="single") - self.list_box.config( - relief="flat", - font=("Ubuntu", 12, "bold"), - ) - self.list_box.grid(column=0, row=0, sticky="nsew") - self.list_box.event_add("<>", "") - self.list_box.bind("<>", self.enable_check_box) - self.scrollbar = ttk.Scrollbar( - self.list_frame, orient="vertical", command=self.list_box.yview - ) - self.scrollbar.grid(column=1, row=0, sticky="ns") - self.list_box.configure(yscrollcommand=self.scrollbar.set) - self.scrollbar.columnconfigure(1, weight=1) - self.scrollbar.rowconfigure(0, weight=1) - - # Frame for Active Tunnel, Interface and Peer - # Right Side Frame - self.right_side_frame = ttk.Frame(self) - self.right_side_frame.grid(column=2, row=2, sticky="nsew") - self.right_side_frame.columnconfigure(2, weight=1) - self.right_side_frame.rowconfigure(2, weight=1) - - # Show active Label - self.select_tunnel = None - self.active_frame = ttk.LabelFrame( - self.right_side_frame, text=Msg.STR["active_tunnel"] - ) - - self.active_frame.grid( - column=0, row=0, sticky="nsew", padx=10, pady=5, columnspan=3 - ) - self.active_frame.columnconfigure(0, weight=1) - self.active_frame.rowconfigure(0, weight=1) - # Interface Label Frame - self.interface_frame = ttk.LabelFrame( - self.right_side_frame, text=Msg.STR["interface"] - ) - self.interface_frame.grid( - column=0, row=1, sticky="nsew", padx=10, pady=5, columnspan=3 - ) - self.interface_frame.columnconfigure(0, weight=1) - self.interface_frame.rowconfigure(1, weight=1) - - # Peer Label Frame - self.peer_frame = ttk.LabelFrame( - self.right_side_frame, text=Msg.STR["peer"]) - self.peer_frame.grid( - column=0, row=2, sticky="nsew", padx=10, pady=5, columnspan=3 - ) - self.peer_frame.columnconfigure(0, weight=1) - self.peer_frame.rowconfigure(2, weight=1) - - # Auto Start Label Frame - self.autoconnect_frame = ttk.Frame(self) - self.autoconnect_frame.grid(column=0, row=3, columnspan=2, sticky="w") - - # Rename Frame - self.rename_frame = ttk.Frame(self) - self.rename_frame.grid(column=2, padx=10, row=3, sticky="nsew") - self.rename_frame.columnconfigure(0, weight=2) - self.rename_frame.columnconfigure(1, weight=1) - - # Label to Show active Tunnel - self.str_var = tk.StringVar(value=self.a) - self.color_label() - - # Interface Label - self.interface = ttk.Label(self.interface_frame) - self.interface.grid(column=0, row=4, sticky="we") - self.interface.config(font=("Ubuntu", 9)) - - # Peer Label - self.peer = ttk.Label(self.peer_frame) - self.peer.config(font=("Ubuntu", 9)) - self.peer.grid(column=0, row=5, sticky="we") - - # Tunnel List - self.tl = Tunnel.parse_files_to_dictionary( - directory=AppConfig.TEMP_DIR) - LxTools.clean_files(AppConfig.TEMP_DIR, file=None) - AppConfig.ensure_directories() - - for tunnels, values in self.tl.items(): - self.list_box.insert("end", tunnels) - self.list_box.update() - - # Button Vpn - if self.a != "": - self.stop() - self.handle_tunnel_data(self.a, self.tl) - self.show_data() - else: - self.start() - - # Address Label - self.add = tk.StringVar() - self.DNS = tk.StringVar() - self.enp = tk.StringVar() - self.reset_fields() - self.show_data() - - # Button Import - self.btn_i = ttk.Button( - self.control_buttons_frame, - image=self.image_manager.get_icon("import_large"), - command=self.import_sl, - padding=0, - ) - self.btn_i.grid(column=0, row=1, pady=8) - - Tooltip(self.btn_i, Msg.TTIP["import_tl"], - state_var=self.tooltip_state) - - # Button Trash - self.btn_tr = ttk.Button( - self.control_buttons_frame, - image=self.image_manager.get_icon("trash_large"), - command=self.delete, - padding=0, - ) - self.btn_tr.grid(column=0, row=2, pady=8) - - if self.list_box.size() == 0: - Tooltip(self.btn_tr, - Msg.TTIP["trash_tl_info"], state_var=self.tooltip_state) - else: - Tooltip(self.btn_tr, Msg.TTIP["trash_tl"], - state_var=self.tooltip_state) - - # Button Export - self.btn_exp = ttk.Button( - self.control_buttons_frame, - image=self.image_manager.get_icon("export_large"), - command=lambda: Tunnel.export(), - padding=0, - ) - - self.btn_exp.grid(column=0, row=3, pady=8) - self.btn_exp.columnconfigure(0, weight=1) - self.btn_exp.rowconfigure(3, weight=1) - - if self.list_box.size() == 0: - Tooltip(self.btn_exp, - Msg.TTIP["export_tl_info"], state_var=self.tooltip_state) - else: - Tooltip(self.btn_exp, - Msg.TTIP["export_tl"], state_var=self.tooltip_state) - - # Label Entry - self.lb_rename = ttk.Entry(self.rename_frame) - self.lb_rename.grid(column=0, row=0, padx=8, pady=10, sticky="ne") - self.lb_rename.config(width=15) - - self.lb_rename.insert(0, Msg.STR["max_chars"]) - self.lb_rename.config(state="disable") - - if self.list_box.size() != 0: - Tooltip( - self.lb_rename, - Msg.TTIP["rename_tl"], - state_var=self.tooltip_state) - else: - Tooltip( - self.lb_rename, - Msg.TTIP["rename_tl_info"], - state_var=self.tooltip_state) - - # Button Rename - self.btn_rename = ttk.Button( - self.rename_frame, - text=Msg.STR["rename"], - state="disable", - command=self.tl_rename, - width=15, - ) - self.btn_rename.grid(column=1, row=0, pady=10, sticky="nsew") - - # Check Buttons - self.selected_option = tk.IntVar() - self.autoconnect_var = tk.StringVar() - self.autoconnect_var.set(f"{self.auto_con}") - - # Frame for Labels, Entry and Button - self.autoconnect = ttk.Label( - self.autoconnect_frame, - textvariable=self.autoconnect_var - ) - self.autoconnect.config(font=("Ubuntu", 11)) - self.autoconnect.grid(column=1, row=0, pady=10, sticky="nsew") - self.autoconnect.columnconfigure(1, weight=1) - self.autoconnect.rowconfigure(0, weight=1) - self.wg_autostart = ttk.Checkbutton( - self.autoconnect_frame, - text=Msg.STR["autoconnect_on"], - variable=self.selected_option, - command=self.box_set, - ) - self.wg_autostart.grid(column=0, row=0, pady=10, - padx=(10, 0), sticky="ew") - - if self.list_box.size() >= 1 and len(self.list_box.curselection()) >= 1: - Tooltip( - self.wg_autostart, - Msg.TTIP["autostart"], - state_var=self.tooltip_state) - - if self.list_box.size() == 0: - Tooltip( - self.wg_autostart, - Msg.TTIP["autostart_info"], - state_var=self.tooltip_state) - - else: - - Tooltip( - self.wg_autostart, - Msg.TTIP["autostart"], - state_var=self.tooltip_state) - - self.on_off() - - # Method that is called when the variable changes - def update_label_display(self, *args): - # Set the foreground color - self.updates_lb.configure(foreground=self.update_foreground.get()) - - # Show or hide the label based on whether it contains text - if self.update_label.get(): - # Make sure the label is in the correct position every time it's shown - self.updates_lb.grid(column=4, columnspan=3, row=0, padx=10) - else: - self.updates_lb.grid_remove() - - def updater(self): - """Start the lxtools_installer""" - tmp_dir = Path("/tmp/lxtools") - Path.mkdir(tmp_dir, exist_ok=True) - os.chdir(tmp_dir) - result = subprocess.run( - ["/usr/local/bin/lxtools_installer"], check=False) - if result.returncode != 0: - MessageDialog("error", result.stderr) - - # Update the labels based on the result - def update_ui_for_update(self, res): - """Update UI elements based on an update check result""" - # First, remove the update button if it exists to avoid conflicts - if hasattr(self, "update_btn"): - self.update_btn.grid_forget() - delattr(self, "update_btn") - - if res == "False": - self.set_update.set(value=1) - self.update_label.set(Msg.STR["update_search_off"]) - self.update_tooltip.set(Msg.TTIP["updates_disabled"]) - # Clear the foreground color as requested - self.update_foreground.set("") - # Set the tooltip for the label - Tooltip(self.updates_lb, self.update_tooltip.get(), - state_var=self.tooltip_state) - - elif res == "No Internet Connection!": - self.update_label.set(Msg.STR["no_server_connection"]) - self.update_foreground.set("red") - # Set the tooltip for "No Server Connection" - Tooltip( - self.updates_lb, - Msg.TTIP["no_server_conn_tt"], - state_var=self.tooltip_state, - ) - - elif res == "No Updates": - self.update_label.set(Msg.STR["no_updates"]) - self.update_tooltip.set( - Msg.TTIP["up_to_date"]) - self.update_foreground.set("") - # Set the tooltip for the label - Tooltip(self.updates_lb, self.update_tooltip.get(), - state_var=self.tooltip_state) - - else: - self.set_update.set(value=0) - - # Clear the label text since we'll show the button instead - self.update_label.set("") - - # Create the update button - self.update_btn = ttk.Button( - self.menu_frame, - image=self.image_manager.get_icon("settings"), - style="Toolbutton", - command=self.updater, - ) - self.update_btn.grid(column=5, row=0, padx=0) - Tooltip( - self.update_btn, - Msg.TTIP["install_new_version"], - state_var=self.tooltip_state - ) - - @staticmethod - def about() -> None: - """ - a tk.Toplevel window - """ - MessageDialog( - "info", - Msg.STR["about_msg"], - buttons=["OK", Msg.STR["goto_git"]], - title=Msg.STR["info"], - commands=[ - None, - partial(webbrowser.open, "https://git.ilunix.de/punix/Wire-Py"), - ], - icon="/usr/share/icons/lx-icons/64/wg_vpn.png", - wraplength=420, - ) - - def update_setting(self, update_res) -> None: - """write off or on in file - Args: - update_res (int): argument that is passed contains 0 or 1 - """ - if update_res == 1: - # Disable updates - ConfigManager.set("updates", "off") - # When updates are disabled, we know the result should be "False" - self.update_ui_for_update("False") - else: - # Enable updates - ConfigManager.set("updates", "on") - # When enabling updates, we need to actually check for updates - try: - # Force a fresh check by passing "on" as the update setting - res = GiteaUpdate.api_down( - AppConfig.UPDATE_URL, AppConfig.VERSION, "on" - ) - - # Make sure the UI is updated regardless of the previous state - if hasattr(self, "update_btn"): - self.update_btn.grid_forget() - if hasattr(self, "updates_lb"): - self.updates_lb.grid_forget() - - # Now update the UI with the fresh result - self.update_ui_for_update(res) - except Exception as e: - logging.error( - f"Error checking for updates: {e}") - # Fallback to a default message if there's an error - self.update_ui_for_update("No Internet Connection!") - - def tooltip_update_label(self) -> None: - """Updates the tooltip menu label based on the current tooltip status""" - # Set the menu text based on the current status - if self.tooltip_state.get(): - # If tooltips are enabled, the menu option should be to disable them - self.tooltip_label.set(Msg.STR["disable_tooltips"]) - else: - # If tooltips are disabled, the menu option should be to enable them - self.tooltip_label.set(Msg.STR["enable_tooltips"]) - - def tooltips_toggle(self): - """ - Toggles the visibility of tooltips (on/off) and updates - the corresponding menu label. Inverts the current tooltip state - (`self.tooltip_state`), saves the new value in the configuration, - and applies the change immediately. Updates the menu entry's label to - reflect the new tooltip status (e.g., "Tooltips: On" or "Tooltips: Off"). - """ - # Toggle the boolean state - new_bool_state = not self.tooltip_state.get() - # Save the converted value in the configuration - ConfigManager.set("tooltips", str(new_bool_state)) - # Update the tooltip_state variable for immediate effect - self.tooltip_state.set(new_bool_state) - - # Update the menu label - self.tooltip_update_label() - - # Update the menu entry - find the correct index - # This assumes it's the third item (index 2) in your menu - self.settings.entryconfigure(1, label=self.tooltip_label.get()) - - def update_theme_label(self) -> None: - """Update the theme label based on the current theme""" - current_theme = ConfigManager.get("theme") - if current_theme == "light": - self.theme_label.set(Msg.STR["dark"]) - else: - self.theme_label.set(Msg.STR["light"]) - - def on_theme_toggle(self) -> None: - """Toggle between light and dark theme""" - current_theme = ConfigManager.get("theme") - new_theme = "dark" if current_theme == "light" else "light" - ThemeManager.change_theme(self, new_theme, new_theme) - self.color_label() - self.header_label.config(fg="#ffffff") - self.update_theme_label() # Update the theme label - - # Update Menulfield - self.settings.entryconfigure(2, label=self.theme_label.get()) - - def start(self) -> None: - """ - Start Button - """ - self.btn_stst = ttk.Button( - self.control_buttons_frame, - image=self.image_manager.get_icon("vpn_start_large"), - command=lambda: self.wg_switch("start"), - padding=0, - ) - self.btn_stst.grid(column=0, row=0, pady=8) - self.btn_stst.columnconfigure(0, weight=1) - self.btn_stst.rowconfigure(0, weight=1) - - if self.list_box.size() == 0: - Tooltip(self.btn_stst, - Msg.TTIP["empty_list"], state_var=self.tooltip_state) - else: - Tooltip(self.btn_stst, - Msg.TTIP["start_tl"], state_var=self.tooltip_state) - - def color_label(self) -> None: - """ - View activ Tunnel in the color green or yellow - """ - if ConfigManager.get("theme") == "light": - - self.lb_tunnel = ttk.Label( - self.active_frame, textvariable=self.str_var, foreground="green" - ) - - else: - self.lb_tunnel = ttk.Label( - self.active_frame, textvariable=self.str_var, foreground="yellow" - ) - - self.lb_tunnel.config(font=("Ubuntu", 11, "bold")) - self.lb_tunnel.grid(column=0, row=0, padx=10, pady=(0, 10), sticky="n") - self.lb_tunnel.columnconfigure(0, weight=1) - self.lb_tunnel.rowconfigure(0, weight=1) - - def stop(self) -> None: - """ - Stop Button - """ - self.btn_stst = ttk.Button( - self.control_buttons_frame, - image=self.image_manager.get_icon("vpn_stop_large"), - command=lambda: self.wg_switch("stop"), - padding=0, - ) - self.btn_stst.grid(column=0, row=0, pady=8) - self.btn_stst.columnconfigure(0, weight=1) - self.btn_stst.rowconfigure(0, weight=1) - - Tooltip(self.btn_stst, Msg.TTIP["stop_tl"], - state_var=self.tooltip_state) - - def reset_fields(self) -> None: - """ - reset data from labels - """ - fields = [self.add, self.DNS, self.enp] - for field in fields: - field.set("") - - def import_sl(self) -> None: - """validity check of wireguard config files""" - - AppConfig.ensure_directories() - try: - filepath = filedialog.askopenfilename( - initialdir=f"{Path.home()}", - title="Select Wireguard config File", - filetypes=[("WG config files", "*.conf")], - ) - data_import, key_name = Tunnel.parse_files_to_dictionary( - filepath=filepath) - - if CryptoUtil.find_key(f"{data_import[key_name]['PrivateKey']}="): - MessageDialog( - "error", Msg.STR["tl_exist"], title=Msg.STR["imp_err"]) - - elif not CryptoUtil.is_valid_base64( - f"{data_import[key_name]['PrivateKey']}=" - ): # 2. Second check: Is it valid Base64? - MessageDialog( - "error", - Msg.STR["invalid_base64"], - title=Msg.STR["imp_err"], - ) - else: - filepath = Path(filepath) - # Shorten the tunnel name to the maximum allowed length if it exceeds 12 characters. - original_name = filepath.name - truncated_name = ( - original_name[-17:] if len( - original_name) > 17 else original_name - ) - import_file = shutil.copy2( - filepath, AppConfig.TEMP_DIR / truncated_name - ) - import_file = Path(import_file) - - del data_import[key_name]["PrivateKey"] - self.tl.update(data_import) - - if self.a != "": - process: CompletedProcess[str] = run( - ["nmcli", "connection", "down", self.a], - capture_output=True, - text=True, - check=False, - ) - - if process.stderr: - logging.error( - f"{process.stderr}: Code {process.returncode}") - - self.reset_fields() - - process: CompletedProcess[str] = run( - [ - "nmcli", - "connection", - "import", - "type", - "wireguard", - "file", - import_file, - ], - capture_output=True, - text=True, - check=False, - ) - - if process.stderr: - logging.error( - f"{process.stderr} Code: {process.returncode}") - - CryptoUtil.encrypt(getpass.getuser()) - LxTools.clean_files(AppConfig.TEMP_DIR, file=None) - AppConfig.ensure_directories() - self.str_var.set("") - self.a = Tunnel.get_active() - self.list_box.insert(0, self.a) - self.wg_autostart.configure(state="normal") - self.list_box.selection_clear(0, tk.END) - self.list_box.update() - self.list_box.selection_set(0) - - Tooltip( - self.wg_autostart, - Msg.TTIP["autostart"], - state_var=self.tooltip_state) - Tooltip(self.btn_tr, - Msg.TTIP["trash_tl"], state_var=self.tooltip_state) - Tooltip(self.btn_exp, - Msg.TTIP["export_tl"], state_var=self.tooltip_state) - Tooltip(self.btn_rename, - Msg.TTIP["rename_tl"], state_var=self.tooltip_state) - - self.lb_rename.insert(0, "Max. 12 characters!") - self.str_var = tk.StringVar() - self.str_var.set(self.a) - self.color_label() - self.stop() - self.handle_tunnel_data(self.a, self.tl) - self.show_data() - process: CompletedProcess[str] = run( - ["nmcli", "con", "mod", self.a, "connection.autoconnect", "no"], - capture_output=True, - text=True, - check=False, - ) - - if process.stderr: - logging.error(process.stderr) - - if process.returncode == 0: - print( - f">> {import_file.stem} << autostart is disabled by default") - - except UnboundLocalError: - MessageDialog( - "error", Msg.STR["no_valid_file"], title=Msg.STR["imp_err"]) - except (IsADirectoryError, TypeError, FileNotFoundError): - print("File import: abort by user...") - except EOFError as e: - print(e) - - def delete(self) -> None: - """ - delete Wireguard Tunnel - """ - try: - self.select_tunnel = self.list_box.curselection() - select_tl = self.list_box.get(self.select_tunnel[0]) - - process: CompletedProcess[str] = run( - ["nmcli", "connection", "delete", select_tl], - capture_output=True, - text=True, - check=False, - ) - - if process.stderr: - logging.error( - f"{process.stderr} Code: {process.returncode}") - - self.list_box.delete(self.select_tunnel[0]) - Path.unlink(f"{AppConfig.CONFIG_DIR}/{select_tl}.dat") - - if select_tl == ConfigManager.get("autostart"): - ConfigManager.set("autostart", "off") - self.selected_option.set(0) - self.autoconnect_var.set(Msg.STR["no_autoconnect"]) - - self.wg_autostart.configure(state="disabled") - - # for disabling checkbox when Listbox empty - if self.list_box.size() == 0: - self.wg_autostart.configure(state="disabled") - self.lb_rename.configure(state="disabled") - Tooltip( - self.wg_autostart, - Msg.TTIP["autostart_info"], - state_var=self.tooltip_state) - - Tooltip(self.btn_exp, - Msg.TTIP["export_tl_info"], state_var=self.tooltip_state) - Tooltip(self.btn_stst, - Msg.TTIP["empty_list"], state_var=self.tooltip_state) - Tooltip(self.lb_rename, - Msg.TTIP["rename_tl_info"], state_var=self.tooltip_state) - self.lb_rename.insert(0, Msg.STR["max_chars"]) - - if self.a != "" and self.a == select_tl: - self.str_var.set(value="") - self.start() - self.list_box.update() - self.reset_fields() - - except IndexError: - - if self.list_box.size() != 0: - - MessageDialog( - "info", Msg.STR["sel_list"], title=Msg.STR["sel_tl"]) - - else: - - MessageDialog( - "info", Msg.STR["tl_first"], title=Msg.STR["sel_tl"]) - - def enable_check_box(self, _) -> None: - """ - checkbox for enable autostart Tunnel - """ - AppConfig.get_autostart_content() - if self.list_box.size() != 0: - self.wg_autostart.configure(state="normal") - self.lb_rename.config(state="normal") - self.lb_rename.delete(0, tk.END) - self.btn_rename.config(state="normal") - - def on_off(self) -> None: - """ - Here it is checked whether the path to the file is there, if not, it is created. - Set (on), the selected tunnel is displayed in the label. - At (off) the label is first emptied then filled with No Autoconnect - """ - - if ConfigManager.get("autostart") != "off": - self.selected_option.set(1) - self.autoconnect_var.set("") - self.auto_con = ConfigManager.get("autostart") - AppConfig.get_autostart_content() - - else: - self.selected_option.set(0) - self.auto_con = Msg.STR["no_autoconnect"] - self.autoconnect_var.set("") - self.autoconnect_var = tk.StringVar() - self.autoconnect_var.set(self.auto_con) - - self.autoconnect = ttk.Label( - self.autoconnect_frame, - textvariable=self.autoconnect_var, - foreground="#0071ff", - width=18, - ) - self.autoconnect.config(font=("Ubuntu", 11)) - self.autoconnect.grid(column=1, row=0, sticky="ew", pady=19) - self.autoconnect.rowconfigure(0, weight=1) - - def box_set(self) -> None: - """ - Configures the autostart for a selected tunnel. - - This method is called when the user changes the autostart checkbox. - It saves the selected tunnel in the configuration file so that it - will be automatically connected at system startup. - - If the checkbox is deactivated, 'off' is written to the configuration file - to disable the autostart. - """ - try: - select_tunnel = self.list_box.curselection() - select_tl = self.list_box.get(select_tunnel[0]) - - if self.selected_option.get() == 0: - ConfigManager.set("autostart", "off") - - if self.list_box.size() == 0: - self.wg_autostart.configure(state="disabled") - - if self.selected_option.get() >= 1: - ConfigManager.set("autostart", select_tl) - - except IndexError: - self.selected_option.set(1) - - self.on_off() - - def tl_rename(self) -> None: - """ - Method to rename a tunnel. Validates input for length, - special characters, and duplicate names, - performs the renaming via `nmcli` if valid, updates - the configuration file in the directory, - and adjusts UI elements such as listboxes and labels. - """ - special_characters = ["\\", "/", "{", "}", " "] - - if len(self.lb_rename.get()) > 12: - - MessageDialog("info", Msg.STR["sign_len"], - title=Msg.STR["ren_err"]) - - elif len(self.lb_rename.get()) == 0: - - MessageDialog( - "info", Msg.STR["zero_signs"], title=Msg.STR["ren_err"]) - - elif any(ch in special_characters for ch in self.lb_rename.get()): - - MessageDialog( - "info", Msg.STR["false_signs"], title=Msg.STR["ren_err"]) - - elif self.lb_rename.get() in [ - file.stem for file in AppConfig.CONFIG_DIR.glob("*.dat") - ]: - - MessageDialog( - "info", Msg.STR["is_in_use"], title=Msg.STR["ren_err"]) - - else: - - try: - self.select_tunnel = self.list_box.curselection() - select_tl = self.list_box.get(self.select_tunnel[0]) - - # nmcli connection modify old connection.id iphone - process: CompletedProcess[str] = run( - [ - "nmcli", - "connection", - "modify", - select_tl, - "connection.id", - self.lb_rename.get(), - ], - capture_output=True, - text=True, - check=False, - ) - if process.stderr: - logging.error( - f"{process.stderr} Code: {process.returncode}") - - source = Path(f"{AppConfig.CONFIG_DIR}/{select_tl}.dat") - destination = AppConfig.CONFIG_DIR / \ - f"{self.lb_rename.get()}.dat" - source.replace(destination) - self.tl[self.lb_rename.get()] = self.tl.pop(select_tl) - if select_tl == ConfigManager.get("autostart"): - ConfigManager.set("autostart", self.lb_rename.get()) - self.autoconnect_var.set(value=self.lb_rename.get()) - self.list_box.delete(self.select_tunnel[0]) - self.list_box.insert("end", self.lb_rename.get()) - self.list_box.update() - self.lb_rename.delete(0, tk.END) - self.update_connection_display() - - except IndexError: - - MessageDialog( - "info", Msg.STR["sel_list"], title=Msg.STR["ren_err"]) - - except EOFError as e: - logging.error(e) - - def handle_tunnel_data(self, active=None, data=None) -> None: - """Processes tunnel data from an active connection and updates - UI elements like labels with information about address, DNS, and endpoint. - """ - tunnel = active - values = data[tunnel] - # Address Label - self.add = tk.StringVar() - self.add.set(f"Address: {values['Address']}") - self.DNS = tk.StringVar() - self.DNS.set(f" DNS: {values['DNS']}") - self.enp = tk.StringVar() - self.enp.set(f"Endpoint: {values['Endpoint']}") - - def show_data(self) -> None: - """ - Displays network-related data (address, DNS, endpoint) - in the UI using ttk.Label widgets. - Creates three labels for address, DNS, and endpoint with - specific styling (color, font), positioning them in a - grid layout (`lb_frame` and `peer_frame`). - Each label is linked to a corresponding text variable - (`self.add`, `self.DNS`, `self.enp`) for dynamic data updates. - """ - # Address Label - self.address = ttk.Label( - self.interface_frame, textvariable=self.add, foreground="#0071ff" - ) - self.address.grid(column=0, row=5, sticky="w", padx=10, pady=(0, 20)) - self.address.config(font=("Ubuntu", 9)) - - # DNS Label - self.dns = ttk.Label( - self.interface_frame, textvariable=self.DNS, foreground="#0071ff" - ) - self.dns.grid(column=0, row=7, sticky="w", padx=10, pady=(0, 20)) - self.dns.config(font=("Ubuntu", 9)) - - # Endpoint Label - self.endpoint = ttk.Label( - self.peer_frame, textvariable=self.enp, foreground="#0071ff" - ) - self.endpoint.grid(column=0, row=8, sticky="w", padx=10, pady=(0, 30)) - self.endpoint.config(font=("Ubuntu", 9)) - - def wg_switch(self, event=None) -> None: - """ - Manages switching between active and inactiveVPN connections. - If no tunnel is selected (`self.a == ""`), it starts a new connection - with the selected tunnel from the listbox (`list_box`). - Otherwise, it stops the current connection and updates - tunnel data using `handle_tunnel_data`. - Handles errors like `IndexError` by displaying appropriate - messages if no items are selected or the listbox is empty. - """ - try: - if self.a == "": - self.select_tunnel = self.list_box.curselection() - select_tl = self.list_box.get(self.select_tunnel[0]) - self.handle_connection_state("start", select_tl) - - else: - - self.handle_tunnel_data(self.a, self.tl) - self.handle_connection_state("stop") - - except IndexError: - - if self.list_box.size() != 0: - - MessageDialog( - "info", Msg.STR["sel_list"], title=Msg.STR["sel_tl"]) - - else: - - MessageDialog( - "info", Msg.STR["tl_first"], title=Msg.STR["sel_tl"]) - - def handle_connection_state(self, action: str, tunnel_name: str = None) -> None: - """ - central management for connection states - - Args: - action (str): "start", "stop" or "toggle" - tunnel_name (str, optional): name of a tunnel for a start-option. defaults to None. - """ - if action == "stop": - if self.a: - process: CompletedProcess[str] = run( - ["nmcli", "connection", "down", self.a], - capture_output=True, - text=True, - check=False, - ) - - if process.stderr: - logging.error( - f"{process.stderr} Code: {process.returncode}") - - self.update_connection_display() - self.reset_fields() - self.start() - - elif action == "start": - if tunnel_name or self.a: - target_tunnel = tunnel_name or self.a - process: CompletedProcess[str] = run( - ["nmcli", "connection", "up", target_tunnel], - capture_output=True, - text=True, - check=False, - ) - - if process.stderr: - logging.error( - f"{process.stderr} Code: {process.returncode}") - - self.update_connection_display() - self.handle_tunnel_data(self.a, self.tl) - self.show_data() - self.color_label() - self.stop() - - elif action == "toggle": - if self.a: - self.handle_connection_state("stop") - else: - self.handle_connection_state("start") - - def update_connection_display(self) -> None: - """ - Updated the display after connection changes - """ - self.a = Tunnel.get_active() - if not hasattr(self, "str_var"): - self.str_var = tk.StringVar() - self.str_var.set(self.a) - self.color_label() - self.show_data() + def toggle_log_window(self): + self.log_window.toggle() if __name__ == "__main__": @@ -1209,7 +89,7 @@ if __name__ == "__main__": LxTools.sigi(AppConfig.TEMP_DIR) CryptoUtil.decrypt(getpass.getuser()) window = Wirepy() - LogConfig.logger(ConfigManager.get("logfile")) + """ the hidden files are hidden in Filedialog """ @@ -1221,5 +101,5 @@ if __name__ == "__main__": window.tk.call("set", "::tk::dialog::file::showHiddenVar", "0") window.mainloop() -LxTools.clean_files(AppConfig.TEMP_DIR) -sys.exit(0) + LxTools.clean_files(AppConfig.TEMP_DIR) + sys.exit(0) \ No newline at end of file diff --git a/wp_app_config.py b/wp_app_config.py index 2dc4a48..6f0c4c6 100755 --- a/wp_app_config.py +++ b/wp_app_config.py @@ -1,6 +1,6 @@ #!/usr/bin/python3 """App configuration for Wire-Py""" -import logging +from logger import app_logger from pathlib import Path from subprocess import CompletedProcess, run from typing import Dict, Any @@ -27,11 +27,6 @@ class AppConfig: consistently and perform system-level setup tasks. """ - # Logging - LOG_DIR = Path.home() / ".local/share/lxlogs" - Path(LOG_DIR).mkdir(parents=True, exist_ok=True) - LOG_FILE_PATH = LOG_DIR / "wirepy.log" - # Base paths BASE_DIR: Path = Path.home() CONFIG_DIR: Path = BASE_DIR / ".config/wire_py" @@ -47,7 +42,7 @@ class AppConfig: "# Theme": "dark", "# Tooltips": True, "# Autostart": "off", - "# Logfile": LOG_FILE_PATH, + } # Updates @@ -59,7 +54,7 @@ class AppConfig: # UI configuration UI_CONFIG: Dict[str, Any] = { "window_title": "WirePy", - "window_title2": "LogViewer", + "window_size": (590, 460), "font_family": "Ubuntu", "font_size": 11, @@ -121,18 +116,12 @@ class AppConfig: check=False, ) if process.returncode == 0: - logging.info(process.stdout) + app_logger.log(process.stdout) if process.stderr: - logging.error( + app_logger.log( f"{process.stderr}") - @classmethod - def ensure_log(cls) -> None: - """Ensures that the log file exists""" - if not cls.LOG_FILE_PATH.exists(): - cls.LOG_FILE_PATH.touch() - # here is initializing the class for translation strings _ = Translate.setup_translations("wirepy") @@ -247,4 +236,4 @@ class Msg: "no_server_conn_tt": _("Could not connect to update server"), "up_to_date": _("Congratulations! Wire-Py is up to date"), "install_new_version": _("Click to install new version"), - } \ No newline at end of file + }