ui fixes expand widget on x and y works

This commit is contained in:
2025-08-10 20:51:12 +02:00
parent 8fcad26789
commit 052ed0a828
19 changed files with 930 additions and 1181 deletions

272
ui/main_frame.py Normal file
View File

@@ -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"])