Files
Wire-Py/ui/main_frame.py

273 lines
10 KiB
Python

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=0)
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()
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"])