273 lines
10 KiB
Python
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"])
|