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

12
logger.py Normal file
View File

@@ -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()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 906 B

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -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

View File

@@ -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

View File

@@ -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

0
ui/__init__.py Normal file
View File

87
ui/controls.py Normal file
View File

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

58
ui/header.py Normal file
View File

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

47
ui/log_window.py Normal file
View File

@@ -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()

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

179
ui/menu_bar.py Normal file
View File

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

100
ui/status_panel.py Normal file
View File

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

73
ui/tunnel_details.py Normal file
View File

@@ -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()

57
ui/tunnel_list.py Normal file
View File

@@ -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("<<ClickEvent>>", "<Button-1>")
self.list_box.bind("<<ClickEvent>>", 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])

1156
wirepy.py

File diff suppressed because it is too large Load Diff

View File

@@ -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"),
}
}